*/
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
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
*
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];
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;
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;
/* 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) {
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);
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,
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(;;) {
/* 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,
/* 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"));
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,
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;