@@ -880,6 +880,44 @@ enum error api_cmd_uptime(struct api_result *res) | |||||
return err; | return err; | ||||
} | } | ||||
static enum error api_cmd_mylist_resp_parse(const char *buffer, | |||||
struct api_mylist_result *mr) | |||||
{ | |||||
/* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}| | |||||
* {int4 date}|{int2 state}|{int4 viewdate}|{str storage}| | |||||
* {str source}|{str other}|{int2 filestate} */ | |||||
int fc; /* the Freedom Club */ | |||||
char *ls; | |||||
size_t ll; | |||||
enum error err = NOERR; | |||||
bool glr = api_get_line(buffer, 2, &ls, &ll); | |||||
assert(glr); | |||||
assert(ll < API_BUFSIZE - 1); | |||||
(void)glr; | |||||
fc = api_field_parse(ls, | |||||
"%Lu", &mr->lid, "%Lu", &mr->fid, "%Lu", &mr->eid, | |||||
"%Lu", &mr->aid, "%Lu", &mr->gid, "%Lu", &mr->date, | |||||
"%hu", &mr->state, "%Lu", &mr->viewdate, "%s", &mr->storage, | |||||
"%s", &mr->source, "%s", &mr->other, "%hu", &mr->filestate, | |||||
NULL); | |||||
uio_debug("Fc is: %d", fc); | |||||
if (fc != 12) { | |||||
if (fc >= 9) | |||||
free(mr->storage); | |||||
if (fc >= 10) | |||||
free(mr->source); | |||||
if (fc >= 11) | |||||
free(mr->other); | |||||
uio_error("Scanf only parsed %d", fc); | |||||
err = ERR_API_RESP_INVALID; | |||||
} | |||||
return err; | |||||
} | |||||
enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | ||||
struct api_mylistadd_opts *opts, struct api_result *res) | struct api_mylistadd_opts *opts, struct api_result *res) | ||||
{ | { | ||||
@@ -921,41 +959,27 @@ enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | |||||
* that page may be a little out of date (or they just | * that page may be a little out of date (or they just | ||||
* expect us to use common sense lmao */ | * expect us to use common sense lmao */ | ||||
} else if (res->code == APICODE_FILE_ALREADY_IN_MYLIST) { | } else if (res->code == APICODE_FILE_ALREADY_IN_MYLIST) { | ||||
/* {int4 lid}|{int4 fid}|{int4 eid}|{int4 aid}|{int4 gid}| | |||||
* {int4 date}|{int2 state}|{int4 viewdate}|{str storage}| | |||||
* {str source}|{str other}|{int2 filestate} */ | |||||
int fc; /* the Freedom Club */ | |||||
char *ls; | |||||
size_t ll; | |||||
struct api_mylistadd_result *mr = &res->mylistadd; | |||||
bool glr = api_get_line(buffer, 2, &ls, &ll); | |||||
assert(glr); | |||||
assert(ll < API_BUFSIZE - 1); | |||||
(void)glr; | |||||
fc = api_field_parse(ls, | |||||
"%Lu", &mr->lid, "%Lu", &mr->fid, "%Lu", &mr->eid, | |||||
"%Lu", &mr->aid, "%Lu", &mr->gid, "%Lu", &mr->date, | |||||
"%hu", &mr->state, "%Lu", &mr->viewdate, "%s", &mr->storage, | |||||
"%s", &mr->source, "%s", &mr->other, "%hu", &mr->filestate, | |||||
NULL); | |||||
uio_debug("Fc is: %d", fc); | |||||
if (fc != 12) { | |||||
if (fc >= 9) | |||||
free(mr->storage); | |||||
if (fc >= 10) | |||||
free(mr->source); | |||||
if (fc >= 11) | |||||
free(mr->other); | |||||
uio_error("Scanf only parsed %d", fc); | |||||
err = ERR_API_RESP_INVALID; | |||||
} | |||||
err = api_cmd_mylist_resp_parse(buffer, &res->mylist); | |||||
} | } | ||||
return err; | return err; | ||||
} | } | ||||
enum error api_cmd_mylist(uint64_t lid, struct api_result *res) | |||||
{ | |||||
char buffer[API_BUFSIZE]; | |||||
enum error err = NOERR; | |||||
err = api_cmd_base(buffer, res, "MYLIST s=%s&lid=%lu", api_session, lid); | |||||
if (err != NOERR) | |||||
return err; | |||||
if (res->code == APICODE_MYLIST) | |||||
err = api_cmd_mylist_resp_parse(buffer, &res->mylist); | |||||
return err; | |||||
} | |||||
enum error api_cmd_mylistmod(uint64_t lid, struct api_mylistadd_opts *opts, | enum error api_cmd_mylistmod(uint64_t lid, struct api_mylistadd_opts *opts, | ||||
struct api_result *res) | struct api_result *res) | ||||
{ | { | ||||
@@ -230,17 +230,15 @@ struct api_encrypt_result { | |||||
struct api_uptime_result { | struct api_uptime_result { | ||||
int32_t ms; | int32_t ms; | ||||
}; | }; | ||||
struct api_mylist_result { | |||||
uint64_t lid, fid, eid, aid, gid, date, viewdate; | |||||
/* free() if != NULL ofc */ | |||||
char *storage, *source, *other; | |||||
enum mylist_state state; | |||||
enum file_state filestate; | |||||
}; | |||||
struct api_mylistadd_result { | struct api_mylistadd_result { | ||||
union { | |||||
uint64_t new_id; | |||||
struct { | |||||
uint64_t lid, fid, eid, aid, gid, date, viewdate; | |||||
/* free() if != NULL ofc */ | |||||
char *storage, *source, *other; | |||||
enum mylist_state state; | |||||
enum file_state filestate; | |||||
}; | |||||
}; | |||||
uint64_t new_id; | |||||
}; | }; | ||||
struct api_mylistmod_result { | struct api_mylistmod_result { | ||||
uint32_t n_edits; | uint32_t n_edits; | ||||
@@ -253,6 +251,7 @@ struct api_result { | |||||
e(version); | e(version); | ||||
e(auth); | e(auth); | ||||
e(uptime); | e(uptime); | ||||
e(mylist); | |||||
e(mylistadd); | e(mylistadd); | ||||
e(encrypt); | e(encrypt); | ||||
e(mylistmod); | e(mylistmod); | ||||
@@ -265,6 +264,7 @@ void api_free(); | |||||
enum error api_cmd_version(struct api_result *res); | enum error api_cmd_version(struct api_result *res); | ||||
enum error api_cmd_uptime(struct api_result *res); | enum error api_cmd_uptime(struct api_result *res); | ||||
enum error api_cmd_mylist(uint64_t lid, struct api_result *res); | |||||
enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | enum error api_cmd_mylistadd(int64_t size, const uint8_t *hash, | ||||
struct api_mylistadd_opts *opts, struct api_result *res); | struct api_mylistadd_opts *opts, struct api_result *res); | ||||
enum error api_cmd_mylistmod(uint64_t lid, struct api_mylistadd_opts *opts, | enum error api_cmd_mylistmod(uint64_t lid, struct api_mylistadd_opts *opts, | ||||
@@ -41,6 +41,9 @@ static const char sql_mylist_add[] = "INSERT INTO mylist " | |||||
"(:lid, :fname, :fsize, :ed2k, :watchdate, :state)"; | "(:lid, :fname, :fsize, :ed2k, :watchdate, :state)"; | ||||
static const char sql_mylist_get[] = "SELECT * FROM mylist WHERE " | static const char sql_mylist_get[] = "SELECT * FROM mylist WHERE " | ||||
"fsize=:fsize AND fname=:fname"; | "fsize=:fsize AND fname=:fname"; | ||||
static const char sql_mylist_update[] = "UPDATE mylist " | |||||
"SET %s " | |||||
"WHERE lid=:lid"; | |||||
#if 0 | #if 0 | ||||
@@ -247,6 +250,89 @@ fail: | |||||
return err; | return err; | ||||
} | } | ||||
static size_t cache_update_sqlprep_set(char *out, size_t out_size, | |||||
const struct api_mylistadd_opts *mods) | |||||
{ | |||||
size_t wr = 0; | |||||
if (mods->state_set) | |||||
wr += snprintf(out + wr, out ? out_size - wr : 0, "%s, ", | |||||
"state = :state"); | |||||
if (mods->watched_set) { | |||||
if (!mods->watched) | |||||
wr += snprintf(out + wr, out ? out_size - wr : 0, "%s, ", | |||||
"watchdate = 0"); | |||||
else if (mods->wdate_set) | |||||
wr += snprintf(out + wr, out ? out_size - wr : 0, | |||||
"%s, ", "watchdate = :watchdate"); | |||||
else | |||||
wr += snprintf(out + wr, out ? out_size - wr : 0, "%s, ", | |||||
"watchdate = strftime('%s')"); | |||||
} | |||||
wr += snprintf(out + wr, out ? out_size - wr : 0, "%s", | |||||
"moddate = strftime('%s')"); | |||||
return wr; | |||||
} | |||||
static size_t cache_update_sqlprep(char *out, | |||||
const struct api_mylistadd_opts *mods) | |||||
{ | |||||
size_t wr = cache_update_sqlprep_set(NULL, 0, mods); | |||||
if (!out) | |||||
return snprintf(NULL, 0, sql_mylist_update, "") + wr; | |||||
char set_str[wr + 1]; | |||||
cache_update_sqlprep_set(set_str, wr + 1, mods); | |||||
return sprintf(out, sql_mylist_update, (char*)set_str); | |||||
} | |||||
enum error cache_update(uint64_t lid, const struct api_mylistadd_opts *mods) | |||||
{ | |||||
sqlite3_stmt *smt; | |||||
int sret; | |||||
enum error err = NOERR; | |||||
size_t sql_len = cache_update_sqlprep(NULL, mods); | |||||
char sql[sql_len + 1]; | |||||
cache_update_sqlprep(sql, mods); | |||||
uio_debug("Update sql: '%s'", sql); | |||||
sret = sqlite3_prepare_v2(cache_db, sql, | |||||
sql_len, &smt, NULL); | |||||
if (sret != SQLITE_OK) { | |||||
uio_error("Cannot prepare statement: %s", sqlite3_errmsg(cache_db)); | |||||
return ERR_CACHE_SQLITE; | |||||
} | |||||
sqlite_bind_goto(smt, ":lid", int64, lid); | |||||
if (mods->state_set) { | |||||
sqlite_bind_goto(smt, ":state", int, mods->state); | |||||
} | |||||
if (mods->watched_set && mods->wdate_set) { | |||||
sqlite_bind_goto(smt, ":watchdate", int64, mods->wdate); | |||||
} | |||||
sret = sqlite3_step(smt); | |||||
if (sret == SQLITE_DONE) { | |||||
uio_debug("Cache entry (%lu) succesfully updated", lid); | |||||
} else { | |||||
uio_error("sqlite_step failed: %s", sqlite3_errmsg(cache_db)); | |||||
err = ERR_CACHE_SQLITE; | |||||
goto fail; | |||||
} | |||||
fail: | |||||
sqlite3_finalize(smt); | |||||
return err; | |||||
} | |||||
bool cache_exists(const char *fname, uint64_t size) | bool cache_exists(const char *fname, uint64_t size) | ||||
{ | { | ||||
return cache_get(fname, size, 0, NULL) == NOERR; | return cache_get(fname, size, 0, NULL) == NOERR; | ||||
@@ -13,10 +13,9 @@ enum cache_select { | |||||
CACHE_S_FNAME = 1 << 1, | CACHE_S_FNAME = 1 << 1, | ||||
CACHE_S_FSIZE = 1 << 2, | CACHE_S_FSIZE = 1 << 2, | ||||
CACHE_S_ED2K = 1 << 3, | CACHE_S_ED2K = 1 << 3, | ||||
CACHE_S_WATCHED = 1 << 4, | |||||
CACHE_S_WATCHDATE = 1 << 5, | |||||
CACHE_S_STATE = 1 << 6, | |||||
CACHE_S_MODDATE = 1 << 7, | |||||
CACHE_S_WATCHDATE = 1 << 4, | |||||
CACHE_S_STATE = 1 << 5, | |||||
CACHE_S_MODDATE = 1 << 6, | |||||
}; | }; | ||||
struct cache_entry { | struct cache_entry { | ||||
@@ -50,9 +49,9 @@ enum error cache_add(uint64_t lid, const char *fname, | |||||
enum mylist_state state); | enum mylist_state state); | ||||
/* | /* | ||||
* Update an already existing cache entry | |||||
* Update an already existing cache entry by lid | |||||
*/ | */ | ||||
enum error cache_update(); | |||||
enum error cache_update(uint64_t lid, const struct api_mylistadd_opts *mods); | |||||
/* | /* | ||||
* Get a cache entry | * Get a cache entry | ||||
@@ -13,17 +13,54 @@ | |||||
#include "cache.h" | #include "cache.h" | ||||
#include "util.h" | #include "util.h" | ||||
enum error cmd_add_cachecheck(const char *path, const struct stat *st, | |||||
static enum error cmd_add_update(uint64_t lid) | |||||
{ | |||||
enum error err; | |||||
struct api_result res; | |||||
struct api_mylistadd_opts mods; | |||||
err = api_cmd_mylist(lid, &res); | |||||
if (err != NOERR) | |||||
return err; | |||||
mods.state = res.mylist.state; | |||||
mods.state_set = true; | |||||
mods.watched = res.mylist.viewdate != 0; | |||||
mods.watched_set = true; | |||||
if (mods.watched) { | |||||
mods.wdate = res.mylist.viewdate; | |||||
mods.wdate_set = true; | |||||
} | |||||
err = cache_update(lid, &mods); | |||||
return err; | |||||
} | |||||
static enum error cmd_add_cachecheck(const char *path, const struct stat *st, | |||||
void *data) | void *data) | ||||
{ | { | ||||
struct cache_entry ce; | |||||
const char *bname = util_basename(path); | const char *bname = util_basename(path); | ||||
enum error err; | enum error err; | ||||
err = cache_get(bname, st->st_size, 0, NULL); | |||||
err = cache_get(bname, st->st_size, | |||||
CACHE_S_LID | CACHE_S_WATCHDATE | CACHE_S_MODDATE, &ce); | |||||
if (err == NOERR) { | if (err == NOERR) { | ||||
/* We could get the entry, so it exists already */ | /* We could get the entry, so it exists already */ | ||||
uio_user("This file (%s) with size (%lu) already exists in cache." | uio_user("This file (%s) with size (%lu) already exists in cache." | ||||
" Skipping", bname, st->st_size); | |||||
" Skipping hashing", bname, st->st_size); | |||||
/* Update time is older than 5 days and it's not watched yet */ | |||||
if (ce.wdate == 0 && ce.moddate - time(NULL) > 5 * 24 * 60 * 60) { | |||||
uio_debug("Updating entry with id: %lu", ce.lid); | |||||
if (cmd_add_update(ce.lid) != NOERR) { | |||||
uio_warning("Cannot update file that is in cache"); | |||||
} | |||||
} | |||||
return ED2KUTIL_DONTHASH; | return ED2KUTIL_DONTHASH; | ||||
} else if (err != ERR_CACHE_NO_EXISTS) { | } else if (err != ERR_CACHE_NO_EXISTS) { | ||||
uio_error("Some error when trying to get from cache: %s", | uio_error("Some error when trying to get from cache: %s", | ||||
@@ -35,7 +72,7 @@ enum error cmd_add_cachecheck(const char *path, const struct stat *st, | |||||
return NOERR; | return NOERR; | ||||
} | } | ||||
enum error cmd_add_apisend(const char *path, const uint8_t *hash, | |||||
static enum error cmd_add_apisend(const char *path, const uint8_t *hash, | |||||
const struct stat *st, void *data) | const struct stat *st, void *data) | ||||
{ | { | ||||
struct api_result r; | struct api_result r; | ||||
@@ -45,8 +82,8 @@ enum error cmd_add_apisend(const char *path, const uint8_t *hash, | |||||
!= NOERR) | != NOERR) | ||||
return ERR_CMD_FAILED; | return ERR_CMD_FAILED; | ||||
if (r.code == 310) { | |||||
struct api_mylistadd_result *x = &r.mylistadd; | |||||
if (r.code == APICODE_FILE_ALREADY_IN_MYLIST) { | |||||
struct api_mylist_result *x = &r.mylist; | |||||
uio_warning("File already added! Adding it to cache"); | uio_warning("File already added! Adding it to cache"); | ||||
uio_debug("File info: lid: %ld, fid: %ld, eid: %ld, aid: %ld," | uio_debug("File info: lid: %ld, fid: %ld, eid: %ld, aid: %ld," | ||||
@@ -66,7 +103,7 @@ enum error cmd_add_apisend(const char *path, const uint8_t *hash, | |||||
free(x->other); | free(x->other); | ||||
return NOERR; | return NOERR; | ||||
} | } | ||||
if (r.code != 210) { | |||||
if (r.code != APICODE_MYLIST_ENTRY_ADDED) { | |||||
uio_error("Mylistadd failure: %hu", r.code); | uio_error("Mylistadd failure: %hu", r.code); | ||||
return ERR_CMD_FAILED; | return ERR_CMD_FAILED; | ||||
} | } | ||||
@@ -13,16 +13,38 @@ | |||||
bool did_cache_init = false; | bool did_cache_init = false; | ||||
uint64_t cmd_modify_getlid(const char *str) | |||||
uint64_t cmd_modify_arg_parse(const char *str, uint64_t *out_wdate) | |||||
{ | { | ||||
uint64_t val; | uint64_t val; | ||||
const char *sep = strchr(str, '|'); | |||||
struct cache_entry ce; | struct cache_entry ce; | ||||
enum error err; | enum error err; | ||||
const char *fn_st = strrchr(str, '/'); | |||||
const char *size_st = strchr(str, '/'); | |||||
if (sscanf(str, "%lu", &val) != 1) | |||||
*out_wdate = 0; | |||||
/* Skip the '/' */ | |||||
if (fn_st) | |||||
fn_st++; | |||||
if (size_st) | |||||
size_st++; | |||||
if (fn_st == size_st) { | |||||
/* size/filename format */ | |||||
size_st = str; | |||||
} else { | |||||
size_t timelen = size_st - str - 1; /* -1 for the skipped '/' */ | |||||
char timstr[timelen + 1]; | |||||
memcpy(timstr, str, timelen); | |||||
timstr[timelen] = '\0'; | |||||
*out_wdate = util_iso2unix(timstr); | |||||
} | |||||
if (sscanf(size_st, "%lu", &val) != 1) | |||||
return 0; | return 0; | ||||
if (!sep) | |||||
if (!fn_st && !size_st) /* Only lid format */ | |||||
return val; | return val; | ||||
if (!cache_is_init()) { | if (!cache_is_init()) { | ||||
@@ -31,7 +53,7 @@ uint64_t cmd_modify_getlid(const char *str) | |||||
did_cache_init = true; | did_cache_init = true; | ||||
} | } | ||||
err = cache_get(sep + 1, val, CACHE_S_LID, &ce); | |||||
err = cache_get(fn_st, val, CACHE_S_LID, &ce); | |||||
if (err != NOERR) | if (err != NOERR) | ||||
return 0; | return 0; | ||||
return ce.lid; | return ce.lid; | ||||
@@ -69,21 +91,42 @@ enum error cmd_modify(void *data) | |||||
for (int i = 0; i < fcount; i++) { | for (int i = 0; i < fcount; i++) { | ||||
struct api_result res; | struct api_result res; | ||||
struct api_mylistadd_opts l_opts = mopt; | |||||
const char *arg = config_get_nonopt(i); | const char *arg = config_get_nonopt(i); | ||||
uint64_t lid = cmd_modify_getlid(arg); | |||||
uint64_t wdate; | |||||
uint64_t lid = cmd_modify_arg_parse(arg, &wdate); | |||||
if (lid == 0) { | if (lid == 0) { | ||||
uio_error("Argument '%s' is not valid. Skipping", arg); | uio_error("Argument '%s' is not valid. Skipping", arg); | ||||
continue; | continue; | ||||
} | } | ||||
err = api_cmd_mylistmod(lid, &mopt, &res); | |||||
if (wdate != 0) { | |||||
l_opts.watched = true; | |||||
l_opts.watched_set = true; | |||||
l_opts.wdate = wdate; | |||||
l_opts.wdate_set = true; | |||||
} | |||||
err = api_cmd_mylistmod(lid, &l_opts, &res); | |||||
if (err != NOERR) | if (err != NOERR) | ||||
break; | break; | ||||
if (res.code == APICODE_NO_SUCH_MYLIST_ENTRY) { | if (res.code == APICODE_NO_SUCH_MYLIST_ENTRY) { | ||||
uio_error("No mylist entry with id: '%lu'", lid); | uio_error("No mylist entry with id: '%lu'", lid); | ||||
continue; | |||||
} | |||||
if (!cache_is_init()) { | |||||
if ((err = cache_init()) != NOERR) | |||||
return err; | |||||
did_cache_init = true; | |||||
} | } | ||||
err = cache_update(lid, &l_opts); | |||||
if (err != NOERR) | |||||
break; | |||||
} | } | ||||
if (did_cache_init) { | if (did_cache_init) { | ||||
@@ -137,8 +137,8 @@ static struct conf_entry options[] = { | |||||
.type = OTYPE_B, .handle_order = 1 }, | .type = OTYPE_B, .handle_order = 1 }, | ||||
/* Arguments are either mylist id's, or file sizes and names | /* Arguments are either mylist id's, or file sizes and names | ||||
* in the format '<size>|<filename>'. The filename can contain | |||||
* '|' characters */ | |||||
* in the format '[watch_date/]<size>/<filename>'. The filename can't contain | |||||
* '/' characters. */ | |||||
{ .l_name = "modify", .s_name = 'W', | { .l_name = "modify", .s_name = 'W', | ||||
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | .has_arg = no_argument, .set_func = config_set_bool, .in_args = true, | ||||
.type = OTYPE_B, .handle_order = 1 }, | .type = OTYPE_B, .handle_order = 1 }, | ||||