diff --git a/src/api.c b/src/api.c index a4d03ae..c58012c 100644 --- a/src/api.c +++ b/src/api.c @@ -880,6 +880,44 @@ enum error api_cmd_uptime(struct api_result *res) 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, 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 * expect us to use common sense lmao */ } 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; } +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, struct api_result *res) { diff --git a/src/api.h b/src/api.h index 9d43a9b..e7f43e5 100644 --- a/src/api.h +++ b/src/api.h @@ -230,17 +230,15 @@ struct api_encrypt_result { struct api_uptime_result { 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 { - 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 { uint32_t n_edits; @@ -253,6 +251,7 @@ struct api_result { e(version); e(auth); e(uptime); + e(mylist); e(mylistadd); e(encrypt); e(mylistmod); @@ -265,6 +264,7 @@ void api_free(); enum error api_cmd_version(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, struct api_mylistadd_opts *opts, struct api_result *res); enum error api_cmd_mylistmod(uint64_t lid, struct api_mylistadd_opts *opts, diff --git a/src/cache.c b/src/cache.c index f49f495..c69a96e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -41,6 +41,9 @@ static const char sql_mylist_add[] = "INSERT INTO mylist " "(:lid, :fname, :fsize, :ed2k, :watchdate, :state)"; static const char sql_mylist_get[] = "SELECT * FROM mylist WHERE " "fsize=:fsize AND fname=:fname"; +static const char sql_mylist_update[] = "UPDATE mylist " + "SET %s " + "WHERE lid=:lid"; #if 0 @@ -247,6 +250,89 @@ fail: 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) { return cache_get(fname, size, 0, NULL) == NOERR; diff --git a/src/cache.h b/src/cache.h index f1e0618..947b3d1 100644 --- a/src/cache.h +++ b/src/cache.h @@ -13,10 +13,9 @@ enum cache_select { CACHE_S_FNAME = 1 << 1, CACHE_S_FSIZE = 1 << 2, 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 { @@ -50,9 +49,9 @@ enum error cache_add(uint64_t lid, const char *fname, 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 diff --git a/src/cmd_add.c b/src/cmd_add.c index 4121c0e..2f2cbdf 100644 --- a/src/cmd_add.c +++ b/src/cmd_add.c @@ -13,17 +13,54 @@ #include "cache.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) { + struct cache_entry ce; const char *bname = util_basename(path); 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) { /* We could get the entry, so it exists already */ 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; } else if (err != ERR_CACHE_NO_EXISTS) { 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; } -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) { struct api_result r; @@ -45,8 +82,8 @@ enum error cmd_add_apisend(const char *path, const uint8_t *hash, != NOERR) 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_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); return NOERR; } - if (r.code != 210) { + if (r.code != APICODE_MYLIST_ENTRY_ADDED) { uio_error("Mylistadd failure: %hu", r.code); return ERR_CMD_FAILED; } diff --git a/src/cmd_modify.c b/src/cmd_modify.c index 3d0a5db..c5b40bb 100644 --- a/src/cmd_modify.c +++ b/src/cmd_modify.c @@ -13,16 +13,38 @@ 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; - const char *sep = strchr(str, '|'); struct cache_entry ce; 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; - if (!sep) + if (!fn_st && !size_st) /* Only lid format */ return val; if (!cache_is_init()) { @@ -31,7 +53,7 @@ uint64_t cmd_modify_getlid(const char *str) 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) return 0; return ce.lid; @@ -69,21 +91,42 @@ enum error cmd_modify(void *data) for (int i = 0; i < fcount; i++) { struct api_result res; + struct api_mylistadd_opts l_opts = mopt; 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) { uio_error("Argument '%s' is not valid. Skipping", arg); 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) break; if (res.code == APICODE_NO_SUCH_MYLIST_ENTRY) { 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) { diff --git a/src/config.c b/src/config.c index ec7cf24..e1f6673 100644 --- a/src/config.c +++ b/src/config.c @@ -137,8 +137,8 @@ static struct conf_entry options[] = { .type = OTYPE_B, .handle_order = 1 }, /* Arguments are either mylist id's, or file sizes and names - * in the format '|'. The filename can contain - * '|' characters */ + * in the format '[watch_date/]/'. The filename can't contain + * '/' characters. */ { .l_name = "modify", .s_name = 'W', .has_arg = no_argument, .set_func = config_set_bool, .in_args = true, .type = OTYPE_B, .handle_order = 1 },