#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>
+#include <arpa/inet.h>
#include "event.h"
#include "mem.h"
static int trackdb_get_global_tid(const char *name,
DB_TXN *tid,
const char **rp);
+static char **trackdb_new_tid(int *ntracksp,
+ int maxtracks,
+ DB_TXN *tid);
+static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid);
const struct cache_type cache_files_type = { 86400 };
unsigned long cache_files_hits, cache_files_misses;
DB *trackdb_searchdb; /* the search database */
DB *trackdb_tagsdb; /* the tags database */
DB *trackdb_globaldb; /* global preferences */
+DB *trackdb_noticeddb; /* when track noticed */
static pid_t db_deadlock_pid = -1; /* deadlock manager PID */
static pid_t rescan_pid = -1; /* rescanner PID */
static int initialized, opened; /* state */
|DB_CREATE
|recover_type[recover],
0666)))
- fatal(0, "trackdb_env->open: %s", db_strerror(err));
+ fatal(0, "trackdb_env->open %s: %s", config->home, db_strerror(err));
trackdb_env->set_errpfx(trackdb_env, "DB");
trackdb_env->set_errfile(trackdb_env, stderr);
trackdb_env->set_verbose(trackdb_env, DB_VERB_DEADLOCK, 1);
return 0;
}
-static pid_t subprogram(ev_source *ev, const char *prog) {
+static pid_t subprogram(ev_source *ev, const char *prog,
+ int outputfd) {
pid_t pid;
int lfd;
if(lfd != -1) {
xdup2(lfd, 1);
xdup2(lfd, 2);
+ xclose(lfd);
+ }
+ if(outputfd != -1) {
+ xdup2(outputfd, 1);
+ xclose(outputfd);
}
/* If we were negatively niced, undo it. We don't bother checking for
* error, it's not that important. */
/* start deadlock manager */
void trackdb_master(ev_source *ev) {
assert(db_deadlock_pid == -1);
- db_deadlock_pid = subprogram(ev, DEADLOCK);
+ db_deadlock_pid = subprogram(ev, DEADLOCK, -1);
ev_child(ev, db_deadlock_pid, 0, reap_db_deadlock, 0);
D(("started deadlock manager"));
}
DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
trackdb_prefsdb = open_db("prefs.db", 0, DB_HASH, DB_CREATE, 0666);
trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_CREATE, 0666);
+ trackdb_noticeddb = open_db("noticed.db",
+ DB_DUPSORT, DB_BTREE, DB_CREATE, 0666);
D(("opened databases"));
}
fatal(0, "error closing prefs.db: %s", db_strerror(err));
if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
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));
trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
trackdb_tagsdb = trackdb_globaldb = 0;
D(("closed databases"));
/* trackdb_notice() **********************************************************/
-/* notice a track */
+/** @brief notice a possibly new track
+ * @return @c DB_NOTFOUND if new, 0 if already known
+ */
int trackdb_notice(const char *track,
const char *path) {
int err;
return err;
}
+/** @brief notice a possibly new track
+ * @return @c DB_NOTFOUND if new, 0 if already known, @c DB_LOCK_DEADLOCK also
+ */
int trackdb_notice_tid(const char *track,
const char *path,
DB_TXN *tid) {
struct kvp *t, *a, *p;
int t_changed, ret;
char *alias, **w;
-
+
/* notice whether the tracks.db entry changes */
t_changed = 0;
/* get any existing tracks entry */
if((err = gettrackdata(track, &t, &p, 0, 0, tid)) == DB_LOCK_DEADLOCK)
return err;
- ret = err;
+ ret = err; /* 0 or DB_NOTFOUND */
/* this is a real track */
t_changed += kvp_set(&t, "_alias_for", 0);
t_changed += kvp_set(&t, "_path", path);
/* only store the tracks.db entry if it has changed */
if(t_changed && (err = trackdb_putdata(trackdb_tracksdb, track, t, tid, 0)))
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);
+ key.data = timestamp;
+ key.size = sizeof timestamp;
+ switch(err = trackdb_noticeddb->put(trackdb_noticeddb, tid, &key,
+ make_key(&data, track), 0)) {
+ case 0: break;
+ case DB_LOCK_DEADLOCK: return err;
+ default: fatal(0, "error updating noticed.db: %s", db_strerror(err));
+ }
+ }
return ret;
}
char **trackdb_stats(int *nstatsp) {
DB_TXN *tid;
struct vector v;
- char *s;
vector_init(&v);
for(;;) {
if(get_stats(&v, trackdb_prefsdb, SI(hash), tid)) goto fail;
vector_append(&v, (char *)"");
if(search_league(&v, 10, tid)) goto fail;
- vector_append(&v, (char *)"");
- vector_append(&v, (char *)"Server stats:");
- byte_xasprintf(&s, "track lookup cache hits: %lu", cache_files_hits);
- vector_append(&v, (char *)s);
- byte_xasprintf(&s, "track lookup cache misses: %lu", cache_files_misses);
- vector_append(&v, (char *)s);
vector_terminate(&v);
break;
fail:
return v.vec;
}
+struct stats_details {
+ void (*done)(char *data, void *u);
+ void *u;
+ int exited; /* subprocess exited */
+ int closed; /* pipe close */
+ int wstat; /* wait status from subprocess */
+ struct dynstr data[1]; /* data read from pipe */
+};
+
+static void stats_complete(struct stats_details *d) {
+ char *s;
+
+ if(!(d->exited && d->closed))
+ return;
+ byte_xasprintf(&s, "\n"
+ "Server stats:\n"
+ "track lookup cache hits: %lu\n"
+ "track lookup cache misses: %lu\n",
+ cache_files_hits,
+ cache_files_misses);
+ dynstr_append_string(d->data, s);
+ dynstr_terminate(d->data);
+ d->done(d->data->vec, d->u);
+}
+
+static int stats_finished(ev_source attribute((unused)) *ev,
+ pid_t attribute((unused)) pid,
+ int status,
+ const struct rusage attribute((unused)) *rusage,
+ void *u) {
+ struct stats_details *const d = u;
+
+ d->exited = 1;
+ if(status)
+ error(0, "disorder-stats %s", wstat(status));
+ stats_complete(d);
+ return 0;
+}
+
+static int stats_read(ev_source attribute((unused)) *ev,
+ ev_reader *reader,
+ int attribute((unused)) fd,
+ void *ptr,
+ size_t bytes,
+ int eof,
+ void *u) {
+ struct stats_details *const d = u;
+
+ dynstr_append_bytes(d->data, ptr, bytes);
+ ev_reader_consume(reader, bytes);
+ if(eof)
+ d->closed = 1;
+ stats_complete(d);
+ return 0;
+}
+
+static int stats_error(ev_source attribute((unused)) *ev,
+ int attribute((unused)) fd,
+ int errno_value,
+ void *u) {
+ struct stats_details *const d = u;
+
+ error(errno_value, "error reading from pipe to disorder-stats");
+ d->closed = 1;
+ stats_complete(d);
+ return 0;
+}
+
+void trackdb_stats_subprocess(ev_source *ev,
+ void (*done)(char *data, void *u),
+ void *u) {
+ int p[2];
+ pid_t pid;
+ struct stats_details *d = xmalloc(sizeof *d);
+
+ dynstr_init(d->data);
+ d->done = done;
+ d->u = u;
+ xpipe(p);
+ pid = subprogram(ev, "disorder-stats", p[1]);
+ xclose(p[1]);
+ ev_child(ev, pid, 0, stats_finished, d);
+ ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader");
+}
+
/* set a pref (remove if value=0) */
int trackdb_set(const char *track,
const char *name,
int err, cmp;
char *oldalias, *newalias, **oldtags = 0, **newtags;
+ if(value) {
+ /* TODO: if value matches default then set value=0 */
+ }
+
for(;;) {
tid = trackdb_begin_transaction();
if((err = gettrackdata(track, &t, &p, 0,
D(("disorderd-rescan terminate: %s", wstat(status)));
/* Our cache of file lookups is out of date now */
cache_clean(&cache_files_type);
+ eventlog("rescanned", (char *)0);
return 0;
}
error(0, "rescan already underway");
return;
}
- rescan_pid = subprogram(ev, RESCAN);
+ rescan_pid = subprogram(ev, RESCAN, -1);
ev_child(ev, rescan_pid, 0, reap_rescan, 0);
D(("started rescanner"));
}
}
+/** @brief Retrieve the most recently added tracks
+ * @param ntracksp Where to put count, or 0
+ * @param maxtracks Maximum number of tracks to retrieve
+ * @return null-terminated array of track names
+ *
+ * The most recently added track is first in the array.
+ */
+char **trackdb_new(int *ntracksp,
+ int maxtracks) {
+ DB_TXN *tid;
+ char **tracks;
+
+ for(;;) {
+ tid = trackdb_begin_transaction();
+ tracks = trackdb_new_tid(ntracksp, maxtracks, tid);
+ if(tracks)
+ break;
+ trackdb_abort_transaction(tid);
+ }
+ trackdb_commit_transaction(tid);
+ return tracks;
+}
+
+/** @brief Retrieve the most recently added tracks
+ * @param ntracksp Where to put count, or 0
+ * @param maxtracks Maximum number of tracks to retrieve, or 0 for all
+ * @param tid Transaction ID
+ * @return null-terminated array of track names, or NULL on deadlock
+ *
+ * The most recently added track is first in the array.
+ */
+static char **trackdb_new_tid(int *ntracksp,
+ int maxtracks,
+ DB_TXN *tid) {
+ DBC *c;
+ DBT k, d;
+ int err = 0;
+ struct vector tracks[1];
+
+ vector_init(tracks);
+ c = trackdb_opencursor(trackdb_noticeddb, tid);
+ while((maxtracks <= 0 || tracks->nvec < maxtracks)
+ && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV)))
+ vector_append(tracks, xstrndup(d.data, d.size));
+ switch(err) {
+ case 0: /* hit maxtracks */
+ case DB_NOTFOUND: /* ran out of tracks */
+ break;
+ case DB_LOCK_DEADLOCK:
+ trackdb_closecursor(c);
+ return 0;
+ default:
+ fatal(0, "error reading noticed.db: %s", db_strerror(err));
+ }
+ if((err = trackdb_closecursor(c)))
+ return 0; /* deadlock */
+ vector_terminate(tracks);
+ if(ntracksp)
+ *ntracksp = tracks->nvec;
+ return tracks->vec;
+}
+
+/** @brief Expire noticed.db
+ * @param earliest Earliest timestamp to keep
+ */
+void trackdb_expire_noticed(time_t earliest) {
+ DB_TXN *tid;
+
+ for(;;) {
+ tid = trackdb_begin_transaction();
+ if(!trackdb_expire_noticed_tid(earliest, tid))
+ break;
+ trackdb_abort_transaction(tid);
+ }
+ trackdb_commit_transaction(tid);
+}
+
+/** @brief Expire noticed.db
+ * @param earliest Earliest timestamp to keep
+ * @param tid Transaction ID
+ * @return 0 or DB_LOCK_DEADLOCK
+ */
+static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid) {
+ DBC *c;
+ DBT k, d;
+ int err = 0, ret;
+ time_t when;
+ uint32_t *kk;
+ int count = 0;
+
+ c = trackdb_opencursor(trackdb_noticeddb, tid);
+ while(!(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_NEXT))) {
+ kk = k.data;
+ when = (time_t)(((uint64_t)ntohl(kk[0]) << 32) + ntohl(kk[1]));
+ if(when >= earliest)
+ break;
+ if((err = c->c_del(c, 0))) {
+ if(err != DB_LOCK_DEADLOCK)
+ fatal(0, "error deleting expired noticed.db entry: %s",
+ db_strerror(err));
+ break;
+ }
+ ++count;
+ }
+ if(err == DB_NOTFOUND)
+ err = 0;
+ if(err && err != DB_LOCK_DEADLOCK)
+ fatal(0, "error expiring noticed.db: %s", db_strerror(err));
+ ret = err;
+ if((err = trackdb_closecursor(c))) {
+ if(err != DB_LOCK_DEADLOCK)
+ fatal(0, "error closing cursor: %s", db_strerror(err));
+ ret = err;
+ }
+ if(!ret && count)
+ info("expired %d tracks from noticed.db", count);
+ return ret;
+}
+
/* tidying up ****************************************************************/
void trackdb_gc(void) {