/*
* This file is part of DisOrder
- * Copyright (C) 2005, 2006 Richard Kettlewell
+ * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
#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);
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_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"));
struct vector v;
char **w;
int nw;
+ const char *rootless = track_rootless(track);
+ if(!rootless)
+ rootless = track; /* bodge */
vector_init(&v);
- if((w = words(casefold(strip_extension(track_rootless(track))), &nw)))
+ if((w = words(casefold(strip_extension(rootless)), &nw)))
vector_append_many(&v, w, nw);
for(; p; p = p->next)
const char *s = config->alias, *t, *expansion, *part;
int c, used_db = 0, slash_prefix, err;
struct kvp *at;
+ const char *const root = find_track_root(track);
+ if(!root) {
+ /* Bodge for tracks with no root */
+ *aliasp = 0;
+ return 0;
+ }
dynstr_init(&d);
- dynstr_append_string(&d, find_track_root(track));
+ dynstr_append_string(&d, root);
while((c = (unsigned char)*s++)) {
if(c != '{') {
dynstr_append(&d, c);
/* trackdb_notice() **********************************************************/
-/* notice a track */
+/** @brief notice a possiby 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 possiby 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;
}
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,
} else {
/* No required tags. We pick random record numbers in the database
* instead. */
- switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp,
- DB_RECORDCOUNT)) {
+ switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp, 0)) {
case 0:
break;
case DB_LOCK_DEADLOCK:
DB_TXN *tid) {
DBC *cursor;
DBT k, d;
- size_t root_len = strlen(root);
- int err;
+ const size_t root_len = root ? strlen(root) : 0;
+ int err, cberr;
struct kvp *data;
+ const char *track;
cursor = trackdb_opencursor(trackdb_tracksdb, tid);
- err = cursor->c_get(cursor, make_key(&k, root), prepare_data(&d),
- DB_SET_RANGE);
+ if(root)
+ err = cursor->c_get(cursor, make_key(&k, root), prepare_data(&d),
+ DB_SET_RANGE);
+ else {
+ memset(&k, 0, sizeof k);
+ err = cursor->c_get(cursor, &k, prepare_data(&d),
+ DB_FIRST);
+ }
while(!err) {
- if(k.size > root_len
- && !strncmp(k.data, root, root_len)
- && ((char *)k.data)[root_len] == '/') {
+ if(!root
+ || (k.size > root_len
+ && !strncmp(k.data, root, root_len)
+ && ((char *)k.data)[root_len] == '/')) {
data = kvp_urldecode(d.data, d.size);
- if(kvp_get(data, "_path"))
- if((err = callback(xstrndup(k.data, k.size), data, u, tid)))
+ if(kvp_get(data, "_path")) {
+ track = xstrndup(k.data, k.size);
+ /* Advance to the next track before the callback so that the callback
+ * may safely delete the track */
+ err = cursor->c_get(cursor, &k, &d, DB_NEXT);
+ if((cberr = callback(track, data, u, tid))) {
+ err = cberr;
break;
- err = cursor->c_get(cursor, &k, &d, DB_NEXT);
+ }
+ } else
+ err = cursor->c_get(cursor, &k, &d, DB_NEXT);
} else
break;
}
}
}
+/** @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;
+}
+
/* tidying up ****************************************************************/
void trackdb_gc(void) {