X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/7736e1cc4f9caf63686a425cf716ab54c67647e4..b9bbb6c8bd8630fe4321045ce57174c4890bfa46:/lib/trackdb.c diff --git a/lib/trackdb.c b/lib/trackdb.c index 6a93fba..7cc3719 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -145,6 +145,17 @@ DB *trackdb_globaldb; /* global preferences */ */ DB *trackdb_noticeddb; /* when track noticed */ +/** @brief The schedule database + * + * - Keys are ID strings, generated at random + * - Values are encoded key-value pairs + * - There can be more than one value per key + * - Data cannot be reconstructed + * + * See @ref server/schedule.c for further information. + */ +DB *trackdb_scheduledb; + /** @brief The user database * - Keys are usernames * - Values are encoded key-value pairs @@ -162,6 +173,16 @@ static int compare(DB attribute((unused)) *db_, return compare_path_raw(a->data, a->size, b->data, b->size); } +/** @brief Test whether the track database can be read + * @return 1 if it can, 0 if it cannot + */ +int trackdb_readable(void) { + char *usersdb; + + byte_xasprintf(&usersdb, "%s/users.db", config->home); + return access(usersdb, R_OK) == 0; +} + /** @brief Open database environment * @param flags Flags word * @@ -450,6 +471,7 @@ void trackdb_open(int flags) { trackdb_globaldb = open_db("global.db", 0, DB_HASH, dbflags, 0666); trackdb_noticeddb = open_db("noticed.db", DB_DUPSORT, DB_BTREE, dbflags, 0666); + trackdb_scheduledb = open_db("schedule.db", 0, DB_HASH, dbflags, 0666); if(!trackdb_existing_database) { /* Stash the database version */ char buf[32]; @@ -480,6 +502,8 @@ void trackdb_close(void) { fatal(0, "error closing global.db: %s", db_strerror(err)); if((err = trackdb_noticeddb->close(trackdb_noticeddb, 0))) fatal(0, "error closing noticed.db: %s", db_strerror(err)); + if((err = trackdb_scheduledb->close(trackdb_scheduledb, 0))) + fatal(0, "error closing schedule.db: %s", db_strerror(err)); if((err = trackdb_usersdb->close(trackdb_usersdb, 0))) fatal(0, "error closing users.db: %s", db_strerror(err)); trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0; @@ -1004,7 +1028,8 @@ int trackdb_notice_tid(const char *track, int err, n; struct kvp *t, *a, *p; int t_changed, ret; - char *alias, **w; + char *alias, **w, *noticed; + time_t now; /* notice whether the tracks.db entry changes */ t_changed = 0; @@ -1015,6 +1040,12 @@ int trackdb_notice_tid(const char *track, /* this is a real track */ t_changed += kvp_set(&t, "_alias_for", 0); t_changed += kvp_set(&t, "_path", path); + time(&now); + if(ret == DB_NOTFOUND) { + /* It's a new track; record the time */ + byte_xasprintf(¬iced, "%lld", (long long)now); + t_changed += kvp_set(&t, "_noticed", noticed); + } /* if we have an alias record it in the database */ if((err = compute_alias(&alias, track, p, tid))) return err; if(alias) { @@ -1039,10 +1070,8 @@ int trackdb_notice_tid(const char *track, return err; if(ret == DB_NOTFOUND) { uint32_t timestamp[2]; - time_t now; DBT key, data; - time(&now); timestamp[0] = htonl((uint64_t)now >> 32); timestamp[1] = htonl((uint32_t)now); memset(&key, 0, sizeof key); @@ -1372,6 +1401,60 @@ void trackdb_stats_subprocess(ev_source *ev, ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader"); } +/** @brief Parse a track name part preference + * @param name Preference name + * @param partp Where to store part name + * @param contextp Where to store context name + * @return 0 on success, non-0 if parse fails + */ +static int trackdb__parse_namepref(const char *name, + char **partp, + char **contextp) { + char *c; + static const char prefix[] = "trackname_"; + + if(strncmp(name, prefix, strlen(prefix))) + return -1; /* not trackname_* at all */ + name += strlen(prefix); + /* There had better be a _ between context and part */ + c = strchr(name, '_'); + if(!c) + return -1; + /* Context is first in the pref name even though most APIs have the part + * first. Confusing; sorry. */ + *contextp = xstrndup(name, c - name); + ++c; + /* There had better NOT be a second _ */ + if(strchr(c, '_')) + return -1; + *partp = xstrdup(c); + return 0; +} + +/** @brief Compute the default value for a track preference + * @param track Track name + * @param name Preference name + * @return Default value or 0 if none/not known + */ +static const char *trackdb__default(const char *track, const char *name) { + char *context, *part; + + if(!trackdb__parse_namepref(name, &part, &context)) { + /* We can work out the default for a trackname_ pref */ + return trackname_part(track, context, part); + } else if(!strcmp(name, "weight")) { + /* We know the default weight */ + return "90000"; + } else if(!strcmp(name, "pick_at_random")) { + /* By default everything is eligible for picking at random */ + return "1"; + } else if(!strcmp(name, "tags")) { + /* By default everything no track has any tags */ + return ""; + } + return 0; +} + /* set a pref (remove if value=0) */ int trackdb_set(const char *track, const char *name, @@ -1380,9 +1463,15 @@ int trackdb_set(const char *track, DB_TXN *tid; int err, cmp; char *oldalias, *newalias, **oldtags = 0, **newtags; + const char *def; + /* If the value matches the default then unset instead, to keep the database + * tidy. Older versions did not have this feature so your database may yet + * have some default values stored in it. */ if(value) { - /* TODO: if value matches default then set value=0 */ + def = trackdb__default(track, name); + if(def && !strcmp(value, def)) + value = 0; } for(;;) { @@ -2082,6 +2171,28 @@ int trackdb_scan(const char *root, /* trackdb_rescan ************************************************************/ +/** @brief Node in the list of rescan-complete callbacks */ +struct rescanned_node { + struct rescanned_node *next; + void (*rescanned)(void *ru); + void *ru; +}; + +/** @brief List of rescan-complete callbacks */ +static struct rescanned_node *rescanned_list; + +/** @brief Add a rescan completion callback */ +void trackdb_add_rescanned(void (*rescanned)(void *ru), + void *ru) { + if(rescanned) { + struct rescanned_node *n = xmalloc(sizeof *n); + n->next = rescanned_list; + n->rescanned = rescanned; + n->ru = ru; + rescanned_list = n; + } +} + /* called when the rescanner terminates */ static int reap_rescan(ev_source attribute((unused)) *ev, pid_t pid, @@ -2096,23 +2207,37 @@ static int reap_rescan(ev_source attribute((unused)) *ev, /* Our cache of file lookups is out of date now */ cache_clean(&cache_files_type); eventlog("rescanned", (char *)0); + /* Call rescanned callbacks */ + while(rescanned_list) { + void (*rescanned)(void *u) = rescanned_list->rescanned; + void *ru = rescanned_list->ru; + + rescanned_list = rescanned_list->next; + rescanned(ru); + } return 0; } /** @brief Initiate a rescan * @param ev Event loop or 0 to block * @param recheck 1 to recheck lengths, 0 to suppress check + * @param rescanned Called on completion (if not NULL) + * @param u Passed to @p rescanned */ -void trackdb_rescan(ev_source *ev, int recheck) { +void trackdb_rescan(ev_source *ev, int recheck, + void (*rescanned)(void *ru), + void *ru) { int w; if(rescan_pid != -1) { + trackdb_add_rescanned(rescanned, ru); error(0, "rescan already underway"); return; } rescan_pid = subprogram(ev, -1, RESCAN, recheck ? "--check" : "--no-check", (char *)0); + trackdb_add_rescanned(rescanned, ru); if(ev) { ev_child(ev, rescan_pid, 0, reap_rescan, 0); D(("started rescanner")); @@ -2132,6 +2257,11 @@ int trackdb_rescan_cancel(void) { return 1; } +/** @brief Return true if a rescan is underway */ +int trackdb_rescan_underway(void) { + return rescan_pid != -1; +} + /* global prefs **************************************************************/ void trackdb_set_global(const char *name, @@ -2630,10 +2760,13 @@ int trackdb_edituserinfo(const char *user, return -1; } } else if(!strcmp(key, "email")) { - if(!strchr(value, '@')) { - error(0, "invalid email address '%s' for user '%s'", user, value); - return -1; - } + if(*value) { + if(!strchr(value, '@')) { + error(0, "invalid email address '%s' for user '%s'", user, value); + return -1; + } + } else + value = 0; /* no email -> remove key */ } else if(!strcmp(key, "created")) { error(0, "cannot change creation date for user '%s'", user); return -1;