chiark / gitweb /
4be5f25802187cc422dc4ae8fa38f03af895bb4f
[disorder] / server / trackdb.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include <config.h>
22 #include "types.h"
23
24 #include <string.h>
25 #include <stdio.h>
26 #include <db.h>
27 #include <sys/socket.h>
28 #include <pcre.h>
29 #include <assert.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <stddef.h>
33 #include <sys/time.h>
34 #include <sys/resource.h>
35 #include <time.h>
36 #include <arpa/inet.h>
37
38 #include "event.h"
39 #include "mem.h"
40 #include "kvp.h"
41 #include "log.h"
42 #include "vector.h"
43 #include "trackdb.h"
44 #include "configuration.h"
45 #include "syscalls.h"
46 #include "wstat.h"
47 #include "words.h"
48 #include "printf.h"
49 #include "filepart.h"
50 #include "trackname.h"
51 #include "trackdb-int.h"
52 #include "logfd.h"
53 #include "cache.h"
54 #include "eventlog.h"
55 #include "hash.h"
56
57 #define RESCAN "disorder-rescan"
58 #define DEADLOCK "disorder-deadlock"
59
60 static const char *getpart(const char *track,
61                            const char *context,
62                            const char *part,
63                            const struct kvp *p,
64                            int *used_db);
65 static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp);
66 static char **trackdb_new_tid(int *ntracksp,
67                               int maxtracks,
68                               DB_TXN *tid);
69 static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid);
70
71 const struct cache_type cache_files_type = { 86400 };
72 unsigned long cache_files_hits, cache_files_misses;
73
74 /* setup and teardown ********************************************************/
75
76 static const char *home;                /* home had better not change */
77 DB_ENV *trackdb_env;                    /* db environment */
78
79 /** @brief The tracks database
80  * - Keys are UTF-8(NFC(unicode(path name)))
81  * - Values are encoded key-value pairs
82  * - Data is reconstructable data about tracks that currently exist
83  */
84 DB *trackdb_tracksdb;
85
86 /** @brief The preferences database
87  *
88  * - Keys are UTF-8(NFC(unicode(path name)))
89  * - Values are encoded key-value pairs
90  * - Data is user data about tracks (that might not exist any more)
91  * and cannot be reconstructed
92  */
93 DB *trackdb_prefsdb;
94
95 /** @brief The search database
96  *
97  * - Keys are UTF-8(NFKC(casefold(search term)))
98  * - Values are UTF-8(NFC(unicode(path name)))
99  * - There can be more than one value per key
100  * - Presence of key,value means that path matches the search terms
101  * - Only tracks fond in @ref tracks_tracksdb are represented here
102  * - This database can be reconstructed, it contains no user data
103  */
104 DB *trackdb_searchdb;
105
106 /** @brief The tags database
107  *
108  * - Keys are UTF-8(NFKC(casefold(tag)))
109  * - Values are UTF-8(NFC(unicode(path name)))
110  * - There can be more than one value per key
111  * - Presence of key,value means that path matches the tag
112  * - This is always in sync with the tags preference
113  * - This database can be reconstructed, it contains no user data
114  */
115 DB *trackdb_tagsdb;                     /* the tags database */
116
117 /** @brief The global preferences database
118  * - Keys are UTF-8(NFC(preference))
119  * - Values are global preference values
120  * - Data is user data and cannot be reconstructed
121  */
122 DB *trackdb_globaldb;                   /* global preferences */
123
124 /** @brief The noticed database
125  * - Keys are 64-bit big-endian timestamps
126  * - Values are UTF-8(NFC(unicode(path name)))
127  * - There can be more than one value per key
128  * - Presence of key,value means that path was added at the given time
129  * - Data cannot be reconstructed (but isn't THAT important)
130  */
131 DB *trackdb_noticeddb;                   /* when track noticed */
132 static pid_t db_deadlock_pid = -1;      /* deadlock manager PID */
133 static pid_t rescan_pid = -1;           /* rescanner PID */
134 static int initialized, opened;         /* state */
135
136 /* tracks matched by required_tags */
137 static char **reqtracks;
138 static size_t nreqtracks;
139
140 /* comparison function for keys */
141 static int compare(DB attribute((unused)) *db_,
142                    const DBT *a, const DBT *b) {
143   return compare_path_raw(a->data, a->size, b->data, b->size);
144 }
145
146 /* open environment */
147 void trackdb_init(int recover) {
148   int err;
149   static int recover_type[] = { 0, DB_RECOVER, DB_RECOVER_FATAL };
150
151   /* sanity checks */
152   assert(initialized == 0);
153   ++initialized;
154   if(home) {
155     if(strcmp(home, config->home))
156       fatal(0, "cannot change db home without server restart");
157     home = config->home;
158   }
159
160   /* create environment */
161   if((err = db_env_create(&trackdb_env, 0))) fatal(0, "db_env_create: %s",
162                                                    db_strerror(err));
163   if((err = trackdb_env->set_alloc(trackdb_env,
164                                    xmalloc_noptr, xrealloc_noptr, xfree)))
165     fatal(0, "trackdb_env->set_alloc: %s", db_strerror(err));
166   if((err = trackdb_env->set_lk_max_locks(trackdb_env, 10000)))
167     fatal(0, "trackdb_env->set_lk_max_locks: %s", db_strerror(err));
168   if((err = trackdb_env->set_lk_max_objects(trackdb_env, 10000)))
169     fatal(0, "trackdb_env->set_lk_max_objects: %s", db_strerror(err));
170   if((err = trackdb_env->open(trackdb_env, config->home,
171                               DB_INIT_LOG
172                               |DB_INIT_LOCK
173                               |DB_INIT_MPOOL
174                               |DB_INIT_TXN
175                               |DB_CREATE
176                               |recover_type[recover],
177                               0666)))
178     fatal(0, "trackdb_env->open %s: %s", config->home, db_strerror(err));
179   trackdb_env->set_errpfx(trackdb_env, "DB");
180   trackdb_env->set_errfile(trackdb_env, stderr);
181   trackdb_env->set_verbose(trackdb_env, DB_VERB_DEADLOCK, 1);
182   trackdb_env->set_verbose(trackdb_env, DB_VERB_RECOVERY, 1);
183   trackdb_env->set_verbose(trackdb_env, DB_VERB_REPLICATION, 1);
184   D(("initialized database environment"));
185 }
186
187 /* called when deadlock manager terminates */
188 static int reap_db_deadlock(ev_source attribute((unused)) *ev,
189                             pid_t attribute((unused)) pid,
190                             int status,
191                             const struct rusage attribute((unused)) *rusage,
192                             void attribute((unused)) *u) {
193   db_deadlock_pid = -1;
194   if(initialized)
195     fatal(0, "deadlock manager unexpectedly terminated: %s",
196           wstat(status));
197   else
198     D(("deadlock manager terminated: %s", wstat(status)));
199   return 0;
200 }
201
202 static pid_t subprogram(ev_source *ev, const char *prog,
203                         int outputfd) {
204   pid_t pid;
205
206   /* If we're in the background then trap subprocess stdout/stderr */
207   if(!(pid = xfork())) {
208     exitfn = _exit;
209     ev_signal_atfork(ev);
210     signal(SIGPIPE, SIG_DFL);
211     if(outputfd != -1) {
212       xdup2(outputfd, 1);
213       xclose(outputfd);
214     }
215     /* If we were negatively niced, undo it.  We don't bother checking for
216      * error, it's not that important. */
217     setpriority(PRIO_PROCESS, 0, 0);
218     execlp(prog, prog, "--config", configfile,
219            debugging ? "--debug" : "--no-debug",
220            log_default == &log_syslog ? "--syslog" : "--no-syslog",
221            (char *)0);
222     fatal(errno, "error invoking %s", prog);
223   }
224   return pid;
225 }
226
227 /* start deadlock manager */
228 void trackdb_master(ev_source *ev) {
229   assert(db_deadlock_pid == -1);
230   db_deadlock_pid = subprogram(ev, DEADLOCK, -1);
231   ev_child(ev, db_deadlock_pid, 0, reap_db_deadlock, 0);
232   D(("started deadlock manager"));
233 }
234
235 /* close environment */
236 void trackdb_deinit(void) {
237   int err;
238
239   /* sanity checks */
240   assert(initialized == 1);
241   --initialized;
242
243   /* close the environment */
244   if((err = trackdb_env->close(trackdb_env, 0)))
245     fatal(0, "trackdb_env->close: %s", db_strerror(err));
246
247   if(rescan_pid != -1 && kill(rescan_pid, SIGTERM) < 0)
248     fatal(errno, "error killing rescanner");
249
250   /* terminate the deadlock manager */
251   if(db_deadlock_pid != -1 && kill(db_deadlock_pid, SIGTERM) < 0)
252     fatal(errno, "error killing deadlock manager");
253   db_deadlock_pid = -1;
254
255   D(("deinitialized database environment"));
256 }
257
258 /* open a specific database */
259 static DB *open_db(const char *path,
260                    u_int32_t dbflags,
261                    DBTYPE dbtype,
262                    u_int32_t openflags,
263                    int mode) {
264   int err;
265   DB *db;
266
267   D(("open %s", path));
268   path = config_get_file(path);
269   if((err = db_create(&db, trackdb_env, 0)))
270     fatal(0, "db_create %s: %s", path, db_strerror(err));
271   if(dbflags)
272     if((err = db->set_flags(db, dbflags)))
273       fatal(0, "db->set_flags %s: %s", path, db_strerror(err));
274   if(dbtype == DB_BTREE)
275     if((err = db->set_bt_compare(db, compare)))
276       fatal(0, "db->set_bt_compare %s: %s", path, db_strerror(err));
277   if((err = db->open(db, 0, path, 0, dbtype,
278                      openflags | DB_AUTO_COMMIT, mode))) {
279     if((openflags & DB_CREATE) || errno != ENOENT)
280       fatal(0, "db->open %s: %s", path, db_strerror(err));
281     db->close(db, 0);
282     db = 0;
283   }
284   return db;
285 }
286
287 /* open track databases */
288 void trackdb_open(void) {
289   int newdb, err;
290
291   /* sanity checks */
292   assert(opened == 0);
293   ++opened;
294   /* check the database version first */
295   trackdb_globaldb = open_db("global.db", 0, DB_HASH, 0, 0666);
296   if(trackdb_globaldb) {
297     /* This is an existing database */
298     const char *s;
299     long oldversion;
300
301     s = trackdb_get_global("_dbversion");
302     oldversion = s ? atol(s) : 1;
303     if(oldversion != config->dbversion) {
304       /* This database needs upgrading.  This isn't implemented yet so we just
305        * fail. */
306       fatal(0, "database needs upgrading from %ld to %ld",
307             oldversion, config->dbversion);
308     }
309     newdb = 0;
310     /* Close the database again,  we'll open it property below */
311     if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
312       fatal(0, "error closing global.db: %s", db_strerror(err));
313     trackdb_globaldb = 0;
314   } else {
315     /* This is a brand new database */
316     newdb = 1;
317   }
318   /* open the databases */
319   trackdb_tracksdb = open_db("tracks.db",
320                              DB_RECNUM, DB_BTREE, DB_CREATE, 0666);
321   trackdb_searchdb = open_db("search.db",
322                              DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
323   trackdb_tagsdb = open_db("tags.db",
324                            DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
325   trackdb_prefsdb = open_db("prefs.db", 0, DB_HASH, DB_CREATE, 0666);
326   trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_CREATE, 0666);
327   trackdb_noticeddb = open_db("noticed.db",
328                              DB_DUPSORT, DB_BTREE, DB_CREATE, 0666);
329   /* Stash the database version */
330   if(newdb) {
331     char buf[32];
332
333     snprintf(buf, sizeof buf, "%ld", config->dbversion);
334     trackdb_set_global("_dbversion", buf, 0);
335   }
336   D(("opened databases"));
337 }
338
339 /* close track databases */
340 void trackdb_close(void) {
341   int err;
342   
343   /* sanity checks */
344   assert(opened == 1);
345   --opened;
346   if((err = trackdb_tracksdb->close(trackdb_tracksdb, 0)))
347     fatal(0, "error closing tracks.db: %s", db_strerror(err));
348   if((err = trackdb_searchdb->close(trackdb_searchdb, 0)))
349     fatal(0, "error closing search.db: %s", db_strerror(err));
350   if((err = trackdb_tagsdb->close(trackdb_tagsdb, 0)))
351     fatal(0, "error closing tags.db: %s", db_strerror(err));
352   if((err = trackdb_prefsdb->close(trackdb_prefsdb, 0)))
353     fatal(0, "error closing prefs.db: %s", db_strerror(err));
354   if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
355     fatal(0, "error closing global.db: %s", db_strerror(err));
356   if((err = trackdb_noticeddb->close(trackdb_noticeddb, 0)))
357     fatal(0, "error closing noticed.db: %s", db_strerror(err));
358   trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
359   trackdb_tagsdb = trackdb_globaldb = 0;
360   D(("closed databases"));
361 }
362
363 /* generic db routines *******************************************************/
364
365 /* fetch and decode a database entry.  Returns 0, DB_NOTFOUND or
366  * DB_LOCK_DEADLOCK. */
367 int trackdb_getdata(DB *db,
368                     const char *track,
369                     struct kvp **kp,
370                     DB_TXN *tid) {
371   int err;
372   DBT key, data;
373
374   switch(err = db->get(db, tid, make_key(&key, track),
375                        prepare_data(&data), 0)) {
376   case 0:
377     *kp = kvp_urldecode(data.data, data.size);
378     return 0;
379   case DB_NOTFOUND:
380     *kp = 0;
381     return err;
382   case DB_LOCK_DEADLOCK:
383     error(0, "error querying database: %s", db_strerror(err));
384     return err;
385   default:
386     fatal(0, "error querying database: %s", db_strerror(err));
387   }
388 }
389
390 /* encode and store a database entry.  Returns 0, DB_KEYEXIST or
391  * DB_LOCK_DEADLOCK. */
392 int trackdb_putdata(DB *db,
393                     const char *track,
394                     const struct kvp *k,
395                     DB_TXN *tid,
396                     u_int32_t flags) {
397   int err;
398   DBT key, data;
399
400   switch(err = db->put(db, tid, make_key(&key, track),
401                        encode_data(&data, k), flags)) {
402   case 0:
403   case DB_KEYEXIST:
404     return err;
405   case DB_LOCK_DEADLOCK:
406     error(0, "error updating database: %s", db_strerror(err));
407     return err;
408   default:
409     fatal(0, "error updating database: %s", db_strerror(err));
410   }
411 }
412
413 /* delete a database entry */
414 int trackdb_delkey(DB *db,
415                    const char *track,
416                    DB_TXN *tid) {
417   int err;
418
419   DBT key;
420   switch(err = db->del(db, tid, make_key(&key, track), 0)) {
421   case 0:
422   case DB_NOTFOUND:
423     return 0;
424   case DB_LOCK_DEADLOCK:
425     error(0, "error updating database: %s", db_strerror(err));
426     return err;
427   default:
428     fatal(0, "error updating database: %s", db_strerror(err));
429   }
430 }
431
432 /* open a database cursor */
433 DBC *trackdb_opencursor(DB *db, DB_TXN *tid) {
434   int err;
435   DBC *c;
436
437   switch(err = db->cursor(db, tid, &c, 0)) {
438   case 0: break;
439   default: fatal(0, "error creating cursor: %s", db_strerror(err));
440   }
441   return c;
442 }
443
444 /* close a database cursor; returns 0 or DB_LOCK_DEADLOCK */
445 int trackdb_closecursor(DBC *c) {
446   int err;
447
448   if(!c) return 0;
449   switch(err = c->c_close(c)) {
450   case 0:
451     return err;
452   case DB_LOCK_DEADLOCK:
453     error(0, "error closing cursor: %s", db_strerror(err));
454     return err;
455   default:
456     fatal(0, "error closing cursor: %s", db_strerror(err));
457   }
458 }
459
460 /* delete a (key,data) pair.  Returns 0, DB_NOTFOUND or DB_LOCK_DEADLOCK. */
461 int trackdb_delkeydata(DB *db,
462                        const char *word,
463                        const char *track,
464                        DB_TXN *tid) {
465   int err;
466   DBC *c;
467   DBT key, data;
468
469   c = trackdb_opencursor(db, tid);
470   switch(err = c->c_get(c, make_key(&key, word),
471                         make_key(&data, track), DB_GET_BOTH)) {
472   case 0:
473     switch(err = c->c_del(c, 0)) {
474     case 0:
475       break;
476     case DB_KEYEMPTY:
477       err = 0;
478       break;
479     case DB_LOCK_DEADLOCK:
480       error(0, "error updating database: %s", db_strerror(err));
481       break;
482     default:
483       fatal(0, "c->c_del: %s", db_strerror(err));
484     }
485     break;
486   case DB_NOTFOUND:
487     break;
488   case DB_LOCK_DEADLOCK:
489     error(0, "error updating database: %s", db_strerror(err));
490     break;
491   default:
492     fatal(0, "c->c_get: %s", db_strerror(err));
493   }
494   if(trackdb_closecursor(c)) err = DB_LOCK_DEADLOCK;
495   return err;
496 }
497
498 /* start a transaction */
499 DB_TXN *trackdb_begin_transaction(void) {
500   DB_TXN *tid;
501   int err;
502
503   if((err = trackdb_env->txn_begin(trackdb_env, 0, &tid, 0)))
504     fatal(0, "trackdb_env->txn_begin: %s", db_strerror(err));
505   return tid;
506 }
507
508 /* abort transaction */
509 void trackdb_abort_transaction(DB_TXN *tid) {
510   int err;
511
512   if(tid)
513     if((err = tid->abort(tid)))
514       fatal(0, "tid->abort: %s", db_strerror(err));
515 }
516
517 /* commit transaction */
518 void trackdb_commit_transaction(DB_TXN *tid) {
519   int err;
520
521   if((err = tid->commit(tid, 0)))
522     fatal(0, "tid->commit: %s", db_strerror(err));
523 }
524
525 /* search/tags shared code ***************************************************/
526
527 /* comparison function used by dedupe() */
528 static int wordcmp(const void *a, const void *b) {
529   return strcmp(*(const char **)a, *(const char **)b);
530 }
531
532 /* sort and de-dupe VEC */
533 static char **dedupe(char **vec, int nvec) {
534   int m, n;
535
536   qsort(vec, nvec, sizeof (char *), wordcmp);
537   m = n = 0;
538   if(nvec) {
539     vec[m++] = vec[0];
540     for(n = 1; n < nvec; ++n)
541       if(strcmp(vec[n], vec[m - 1]))
542         vec[m++] = vec[n];
543   }
544   vec[m] = 0;
545   return vec;
546 }
547
548 /* update a key/track database.  Returns 0 or DB_DEADLOCK. */
549 static int register_word(DB *db, const char *what,
550                          const char *track, const char *word,
551                          DB_TXN *tid) {
552   int err;
553   DBT key, data;
554
555   switch(err = db->put(db, tid, make_key(&key, word),
556                        make_key(&data, track), DB_NODUPDATA)) {
557   case 0:
558   case DB_KEYEXIST:
559     return 0;
560   case DB_LOCK_DEADLOCK:
561     error(0, "error updating %s.db: %s", what, db_strerror(err));
562     return err;
563   default:
564     fatal(0, "error updating %s.db: %s", what,  db_strerror(err));
565   }
566 }
567
568 /* search primitives *********************************************************/
569
570 /* return true iff NAME is a trackname_display_ pref */
571 static int is_display_pref(const char *name) {
572   static const char prefix[] = "trackname_display_";
573   return !strncmp(name, prefix, (sizeof prefix) - 1);
574 }
575
576 /* compute the words of a track name */
577 static char **track_to_words(const char *track,
578                              const struct kvp *p) {
579   struct vector v;
580   char **w;
581   int nw;
582   const char *rootless = track_rootless(track);
583
584   if(!rootless)
585     rootless = track;                   /* bodge */
586   vector_init(&v);
587   if((w = words(casefold(strip_extension(rootless)), &nw)))
588     vector_append_many(&v, w, nw);
589
590   for(; p; p = p->next)
591     if(is_display_pref(p->name))
592       if((w = words(casefold(p->value), &nw)))
593         vector_append_many(&v, w, nw);
594   vector_terminate(&v);
595   return dedupe(v.vec, v.nvec);
596 }
597
598 /* return nonzero iff WORD is a stopword */
599 static int stopword(const char *word) {
600   int n;
601
602   for(n = 0; n < config->stopword.n
603         && strcmp(word, config->stopword.s[n]); ++n)
604     ;
605   return n < config->stopword.n;
606 }
607
608 /* record that WORD appears in TRACK.  Returns 0 or DB_LOCK_DEADLOCK. */
609 static int register_search_word(const char *track, const char *word,
610                                 DB_TXN *tid) {
611   if(stopword(word)) return 0;
612   return register_word(trackdb_searchdb, "search", track, word, tid);
613 }
614
615 /* Tags **********************************************************************/
616
617 /* Return nonzero if C is a valid tag character */
618 static int tagchar(int c) {
619   switch(c) {
620   case ',':
621     return 0;
622   default:
623     return c >= ' ';
624   }
625 }
626
627 /* Parse and de-dupe a tag list.  If S=0 then assumes "". */
628 static char **parsetags(const char *s) {
629   const char *t;
630   struct vector v;
631
632   vector_init(&v);
633   if(s) {
634     /* skip initial separators */
635     while(*s && (!tagchar(*s) || *s == ' '))
636       ++s;
637     while(*s) {
638       /* find the extent of the tag */
639       t = s;
640       while(*s && tagchar(*s))
641         ++s;
642       /* strip trailing spaces */
643       while(s > t && s[-1] == ' ')
644         --s;
645       vector_append(&v, xstrndup(t, s - t));
646       /* skip intermediate and trailing separators */
647       while(*s && (!tagchar(*s) || *s == ' '))
648         ++s;
649     }
650   }
651   vector_terminate(&v);
652   return dedupe(v.vec, v.nvec);
653 }
654
655 /* Record that TRACK has TAG.  Returns 0 or DB_LOCK_DEADLOCK. */
656 static int register_tag(const char *track, const char *tag, DB_TXN *tid) {
657   return register_word(trackdb_tagsdb, "tags", track, tag, tid);
658 }
659
660 /* aliases *******************************************************************/
661
662 /* compute the alias and store at aliasp.  Returns 0 or DB_LOCK_DEADLOCK.  If
663  * there is no alias sets *aliasp to 0. */
664 static int compute_alias(char **aliasp,
665                          const char *track,
666                          const struct kvp *p,
667                          DB_TXN *tid) {
668   struct dynstr d;
669   const char *s = config->alias, *t, *expansion, *part;
670   int c, used_db = 0, slash_prefix, err;
671   struct kvp *at;
672   const char *const root = find_track_root(track);
673
674   if(!root) {
675     /* Bodge for tracks with no root */
676     *aliasp = 0;
677     return 0;
678   }
679   dynstr_init(&d);
680   dynstr_append_string(&d, root);
681   while((c = (unsigned char)*s++)) {
682     if(c != '{') {
683       dynstr_append(&d, c);
684       continue;
685     }
686     if((slash_prefix = (*s == '/')))
687       s++;
688     t = strchr(s, '}');
689     assert(t != 0);                     /* validated at startup */
690     part = xstrndup(s, t - s);
691     expansion = getpart(track, "display", part, p, &used_db);
692     if(*expansion) {
693       if(slash_prefix) dynstr_append(&d, '/');
694       dynstr_append_string(&d, expansion);
695     }
696     s = t + 1;                          /* skip {part} */
697   }
698   /* only admit to the alias if we used the db... */
699   if(!used_db) {
700     *aliasp = 0;
701     return 0;
702   }
703   dynstr_terminate(&d);
704   /* ...and the answer differs from the original... */
705   if(!strcmp(track, d.vec)) {
706     *aliasp = 0;
707     return 0;
708   }
709   /* ...and there isn't already a different track with that name (including as
710    * an alias) */
711   switch(err = trackdb_getdata(trackdb_tracksdb, d.vec, &at, tid)) {
712   case 0:
713     if((s = kvp_get(at, "_alias_for"))
714        && !strcmp(s, track)) {
715     case DB_NOTFOUND:
716       *aliasp = d.vec;
717     } else {
718       *aliasp = 0;
719     }
720     return 0;
721   default:
722     return err;
723   }
724 }
725
726 /* get track and prefs data (if tp/pp not null pointers).  Returns 0 on
727  * success, DB_NOTFOUND if the track does not exist or DB_LOCK_DEADLOCK.
728  * Always sets the return values, even if only to null pointers. */
729 static int gettrackdata(const char *track,
730                         struct kvp **tp,
731                         struct kvp **pp,
732                         const char **actualp,
733                         unsigned flags,
734 #define GTD_NOALIAS 0x0001
735                         DB_TXN *tid) {
736   int err;
737   const char *actual = track;
738   struct kvp *t = 0, *p = 0;
739   
740   if((err = trackdb_getdata(trackdb_tracksdb, track, &t, tid))) goto done;
741   if((actual = kvp_get(t, "_alias_for"))) {
742     if(flags & GTD_NOALIAS) {
743       error(0, "alias passed to gettrackdata where real path required");
744       abort();
745     }
746     if((err = trackdb_getdata(trackdb_tracksdb, actual, &t, tid))) goto done;
747   } else
748     actual = track;
749   assert(actual != 0);
750   if(pp) {
751     if((err = trackdb_getdata(trackdb_prefsdb, actual, &p, tid)) == DB_LOCK_DEADLOCK)
752       goto done;
753   }
754   err = 0;
755 done:
756   if(actualp) *actualp = actual;
757   if(tp) *tp = t;
758   if(pp) *pp = p;
759   return err;
760 }
761
762 /* trackdb_notice() **********************************************************/
763
764 /** @brief notice a possibly new track
765  * @return @c DB_NOTFOUND if new, 0 if already known
766  */
767 int trackdb_notice(const char *track,
768                    const char *path) {
769   int err;
770   DB_TXN *tid;
771   
772   for(;;) {
773     tid = trackdb_begin_transaction();
774     err = trackdb_notice_tid(track, path, tid);
775     if((err == DB_LOCK_DEADLOCK)) goto fail;
776     break;
777   fail:
778     trackdb_abort_transaction(tid);
779   }
780   trackdb_commit_transaction(tid);
781   return err;
782 }
783
784 /** @brief notice a possibly new track
785  * @param track NFC UTF-8 track name
786  * @param path Raw path name
787  * @param tid Transaction ID
788  * @return @c DB_NOTFOUND if new, 0 if already known, @c DB_LOCK_DEADLOCK also
789  */
790 int trackdb_notice_tid(const char *track,
791                        const char *path,
792                        DB_TXN *tid) {
793   int err, n;
794   struct kvp *t, *a, *p;
795   int t_changed, ret;
796   char *alias, **w;
797
798   /* notice whether the tracks.db entry changes */
799   t_changed = 0;
800   /* get any existing tracks entry */
801   if((err = gettrackdata(track, &t, &p, 0, 0, tid)) == DB_LOCK_DEADLOCK)
802     return err;
803   ret = err;                            /* 0 or DB_NOTFOUND */
804   /* this is a real track */
805   t_changed += kvp_set(&t, "_alias_for", 0);
806   t_changed += kvp_set(&t, "_path", path);
807   /* if we have an alias record it in the database */
808   if((err = compute_alias(&alias, track, p, tid))) return err;
809   if(alias) {
810     /* won't overwrite someone else's alias as compute_alias() checks */
811     D(("%s: alias %s", track, alias));
812     a = 0;
813     kvp_set(&a, "_alias_for", track);
814     if((err = trackdb_putdata(trackdb_tracksdb, alias, a, tid, 0))) return err;
815   }
816   /* update search.db */
817   w = track_to_words(track, p);
818   for(n = 0; w[n]; ++n)
819     if((err = register_search_word(track, w[n], tid)))
820       return err;
821   /* update tags.db */
822   w = parsetags(kvp_get(p, "tags"));
823   for(n = 0; w[n]; ++n)
824     if((err = register_tag(track, w[n], tid)))
825       return err;
826   reqtracks = 0;
827   /* only store the tracks.db entry if it has changed */
828   if(t_changed && (err = trackdb_putdata(trackdb_tracksdb, track, t, tid, 0)))
829     return err;
830   if(ret == DB_NOTFOUND) {
831     uint32_t timestamp[2];
832     time_t now;
833     DBT key, data;
834
835     time(&now);
836     timestamp[0] = htonl((uint64_t)now >> 32);
837     timestamp[1] = htonl((uint32_t)now);
838     memset(&key, 0, sizeof key);
839     key.data = timestamp;
840     key.size = sizeof timestamp;
841     switch(err = trackdb_noticeddb->put(trackdb_noticeddb, tid, &key,
842                                         make_key(&data, track), 0)) {
843     case 0: break;
844     case DB_LOCK_DEADLOCK: return err;
845     default: fatal(0, "error updating noticed.db: %s", db_strerror(err));
846     }
847   }
848   return ret;
849 }
850
851 /* trackdb_obsolete() ********************************************************/
852
853 /* obsolete a track */
854 int trackdb_obsolete(const char *track, DB_TXN *tid) {
855   int err, n;
856   struct kvp *p;
857   char *alias, **w;
858
859   if((err = gettrackdata(track, 0, &p, 0,
860                          GTD_NOALIAS, tid)) == DB_LOCK_DEADLOCK)
861     return err;
862   else if(err == DB_NOTFOUND) return 0;
863   /* compute the alias, if any, and delete it */
864   if(compute_alias(&alias, track, p, tid)) return err;
865   if(alias) {
866     /* if the alias points to some other track then compute_alias won't
867      * return it */
868     if(trackdb_delkey(trackdb_tracksdb, alias, tid))
869       return err;
870   }
871   /* update search.db */
872   w = track_to_words(track, p);
873   for(n = 0; w[n]; ++n)
874     if(trackdb_delkeydata(trackdb_searchdb,
875                           w[n], track, tid) == DB_LOCK_DEADLOCK)
876       return err;
877   /* update tags.db */
878   w = parsetags(kvp_get(p, "tags"));
879   for(n = 0; w[n]; ++n)
880     if(trackdb_delkeydata(trackdb_tagsdb,
881                           w[n], track, tid) == DB_LOCK_DEADLOCK)
882       return err;
883   reqtracks = 0;
884   /* update tracks.db */
885   if(trackdb_delkey(trackdb_tracksdb, track, tid) == DB_LOCK_DEADLOCK)
886     return err;
887   /* We don't delete the prefs, so they survive temporary outages of the
888    * (possibly virtual) track filesystem */
889   return 0;
890 }
891
892 /* trackdb_stats() ***********************************************************/
893
894 #define H(name) { #name, offsetof(DB_HASH_STAT, name) }
895 #define B(name) { #name, offsetof(DB_BTREE_STAT, name) }
896
897 static const struct statinfo {
898   const char *name;
899   size_t offset;
900 } statinfo_hash[] = {
901   H(hash_magic),
902   H(hash_version),
903   H(hash_nkeys),
904   H(hash_ndata),
905   H(hash_pagesize),
906   H(hash_ffactor),
907   H(hash_buckets),
908   H(hash_free),
909   H(hash_bfree),
910   H(hash_bigpages),
911   H(hash_big_bfree),
912   H(hash_overflows),
913   H(hash_ovfl_free),
914   H(hash_dup),
915   H(hash_dup_free),
916 }, statinfo_btree[] = {
917   B(bt_magic),
918   B(bt_version),
919   B(bt_nkeys),
920   B(bt_ndata),
921   B(bt_pagesize),
922   B(bt_minkey),
923   B(bt_re_len),
924   B(bt_re_pad),
925   B(bt_levels),
926   B(bt_int_pg),
927   B(bt_leaf_pg),
928   B(bt_dup_pg),
929   B(bt_over_pg),
930   B(bt_free),
931   B(bt_int_pgfree),
932   B(bt_leaf_pgfree),
933   B(bt_dup_pgfree),
934   B(bt_over_pgfree),
935 };
936
937 /* look up stats for DB */
938 static int get_stats(struct vector *v,
939                      DB *database,
940                      const struct statinfo *si,
941                      size_t nsi,
942                      DB_TXN *tid) {
943   void *sp;
944   size_t n;
945   char *str;
946   int err;
947
948   if(database) {
949     switch(err = database->stat(database, tid, &sp, 0)) {
950     case 0:
951       break;
952     case DB_LOCK_DEADLOCK:
953       error(0, "error querying database: %s", db_strerror(err));
954       return err;
955     default:
956       fatal(0, "error querying database: %s", db_strerror(err));
957     }
958     for(n = 0; n < nsi; ++n) {
959       byte_xasprintf(&str, "%s=%"PRIuMAX, si[n].name,
960                      (uintmax_t)*(u_int32_t *)((char *)sp + si[n].offset));
961       vector_append(v, str);
962     }
963   }
964   return 0;
965 }
966
967 struct search_entry {
968   char *word;
969   int n;
970 };
971
972 /* find the top COUNT words in the search database */
973 static int search_league(struct vector *v, int count, DB_TXN *tid) {
974   struct search_entry *se;
975   DBT k, d;
976   DBC *cursor;
977   int err, n = 0, nse = 0, i;
978   char *word = 0;
979   size_t wl = 0;
980   char *str;
981
982   cursor = trackdb_opencursor(trackdb_searchdb, tid);
983   se = xmalloc(count * sizeof *se);
984   while(!(err = cursor->c_get(cursor, prepare_data(&k), prepare_data(&d),
985                               DB_NEXT))) {
986     if(word && wl == k.size && !strncmp(word, k.data, wl))
987       ++n;
988     else {
989 #define FINALIZE() do {                                         \
990   if(word && (nse < count || n > se[nse - 1].n)) {              \
991     if(nse == count)                                            \
992       i = nse - 1;                                              \
993     else                                                        \
994       i = nse++;                                                \
995     while(i > 0 && n > se[i - 1].n)                             \
996       --i;                                                      \
997     memmove(&se[i + 1], &se[i], (nse - i) * sizeof *se);        \
998     se[i].word = word;                                          \
999     se[i].n = n;                                                \
1000   }                                                             \
1001 } while(0)
1002       FINALIZE();
1003       word = xstrndup(k.data, wl = k.size);
1004       n = 1;
1005     }
1006   }
1007   switch(err) {
1008   case DB_NOTFOUND:
1009     err = 0;
1010     break;
1011   case DB_LOCK_DEADLOCK:
1012     error(0, "error querying search database: %s", db_strerror(err));
1013     break;
1014   default:
1015     fatal(0, "error querying search database: %s", db_strerror(err));
1016   }
1017   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1018   if(err) return err;
1019   FINALIZE();
1020   byte_xasprintf(&str, "Top %d search words:", nse);
1021   vector_append(v, str);
1022   for(i = 0; i < nse; ++i) {
1023     byte_xasprintf(&str, "%4d: %5d %s", i + 1, se[i].n, se[i].word);
1024     vector_append(v, str);
1025   }
1026   return 0;
1027 }
1028
1029 #define SI(what) statinfo_##what, \
1030                  sizeof statinfo_##what / sizeof (struct statinfo)
1031
1032 /* return a list of database stats */
1033 char **trackdb_stats(int *nstatsp) {
1034   DB_TXN *tid;
1035   struct vector v;
1036   
1037   vector_init(&v);
1038   for(;;) {
1039     tid = trackdb_begin_transaction();
1040     v.nvec = 0;
1041     vector_append(&v, (char *)"Tracks database stats:");
1042     if(get_stats(&v, trackdb_tracksdb, SI(btree), tid)) goto fail;
1043     vector_append(&v, (char *)"");
1044     vector_append(&v, (char *)"Search database stats:");
1045     if(get_stats(&v, trackdb_searchdb, SI(hash), tid)) goto fail;
1046     vector_append(&v, (char *)"");
1047     vector_append(&v, (char *)"Prefs database stats:");
1048     if(get_stats(&v, trackdb_prefsdb, SI(hash), tid)) goto fail;
1049     vector_append(&v, (char *)"");
1050     if(search_league(&v, 10, tid)) goto fail;
1051     vector_terminate(&v);
1052     break;
1053 fail:
1054     trackdb_abort_transaction(tid);
1055   }
1056   trackdb_commit_transaction(tid);
1057   if(nstatsp) *nstatsp = v.nvec;
1058   return v.vec;
1059 }
1060
1061 struct stats_details {
1062   void (*done)(char *data, void *u);
1063   void *u;
1064   int exited;                           /* subprocess exited */
1065   int closed;                           /* pipe close */
1066   int wstat;                            /* wait status from subprocess */
1067   struct dynstr data[1];                /* data read from pipe */
1068 };
1069
1070 static void stats_complete(struct stats_details *d) {
1071   char *s;
1072   
1073   if(!(d->exited && d->closed))
1074     return;
1075   byte_xasprintf(&s, "\n"
1076                  "Server stats:\n"
1077                  "track lookup cache hits: %lu\n"
1078                  "track lookup cache misses: %lu\n",
1079                  cache_files_hits,
1080                  cache_files_misses);
1081   dynstr_append_string(d->data, s);
1082   dynstr_terminate(d->data);
1083   d->done(d->data->vec, d->u);
1084 }
1085
1086 static int stats_finished(ev_source attribute((unused)) *ev,
1087                           pid_t attribute((unused)) pid,
1088                           int status,
1089                           const struct rusage attribute((unused)) *rusage,
1090                           void *u) {
1091   struct stats_details *const d = u;
1092
1093   d->exited = 1;
1094   if(status)
1095     error(0, "disorder-stats %s", wstat(status));
1096   stats_complete(d);
1097   return 0;
1098 }
1099
1100 static int stats_read(ev_source attribute((unused)) *ev,
1101                       ev_reader *reader,
1102                       void *ptr,
1103                       size_t bytes,
1104                       int eof,
1105                       void *u) {
1106   struct stats_details *const d = u;
1107
1108   dynstr_append_bytes(d->data, ptr, bytes);
1109   ev_reader_consume(reader, bytes);
1110   if(eof)
1111     d->closed = 1;
1112   stats_complete(d);
1113   return 0;
1114 }
1115
1116 static int stats_error(ev_source attribute((unused)) *ev,
1117                        int errno_value,
1118                        void *u) {
1119   struct stats_details *const d = u;
1120
1121   error(errno_value, "error reading from pipe to disorder-stats");
1122   d->closed = 1;
1123   stats_complete(d);
1124   return 0;
1125 }
1126
1127 void trackdb_stats_subprocess(ev_source *ev,
1128                               void (*done)(char *data, void *u),
1129                               void *u) {
1130   int p[2];
1131   pid_t pid;
1132   struct stats_details *d = xmalloc(sizeof *d);
1133
1134   dynstr_init(d->data);
1135   d->done = done;
1136   d->u = u;
1137   xpipe(p);
1138   pid = subprogram(ev, "disorder-stats", p[1]);
1139   xclose(p[1]);
1140   ev_child(ev, pid, 0, stats_finished, d);
1141   ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader");
1142 }
1143
1144 /* set a pref (remove if value=0) */
1145 int trackdb_set(const char *track,
1146                 const char *name,
1147                 const char *value) {
1148   struct kvp *t, *p, *a;
1149   DB_TXN *tid;
1150   int err, cmp;
1151   char *oldalias, *newalias, **oldtags = 0, **newtags;
1152
1153   if(value) {
1154     /* TODO: if value matches default then set value=0 */
1155   }
1156   
1157   for(;;) {
1158     tid = trackdb_begin_transaction();
1159     if((err = gettrackdata(track, &t, &p, 0,
1160                            0, tid)) == DB_LOCK_DEADLOCK)
1161       goto fail;
1162     if(err == DB_NOTFOUND) break;
1163     if(name[0] == '_') {
1164       if(kvp_set(&t, name, value))
1165         if(trackdb_putdata(trackdb_tracksdb, track, t, tid, 0))
1166           goto fail;
1167     } else {
1168       /* get the old alias name */
1169       if(compute_alias(&oldalias, track, p, tid)) goto fail;
1170       /* get the old tags */
1171       if(!strcmp(name, "tags"))
1172         oldtags = parsetags(kvp_get(p, "tags"));
1173       /* set the value */
1174       if(kvp_set(&p, name, value))
1175         if(trackdb_putdata(trackdb_prefsdb, track, p, tid, 0))
1176           goto fail;
1177       /* compute the new alias name */
1178       if((err = compute_alias(&newalias, track, p, tid))) goto fail;
1179       /* check whether alias has changed */
1180       if(!(oldalias == newalias
1181            || (oldalias && newalias && !strcmp(oldalias, newalias)))) {
1182         /* adjust alias records to fit change */
1183         if(oldalias
1184            && trackdb_delkey(trackdb_tracksdb, oldalias, tid)) goto fail;
1185         if(newalias) {
1186           a = 0;
1187           kvp_set(&a, "_alias_for", track);
1188           if(trackdb_putdata(trackdb_tracksdb, newalias, a, tid, 0)) goto fail;
1189         }
1190       }
1191       /* check whether tags have changed */
1192       if(!strcmp(name, "tags")) {
1193         newtags = parsetags(value);
1194         while(*oldtags || *newtags) {
1195           if(*oldtags && *newtags) {
1196             cmp = strcmp(*oldtags, *newtags);
1197             if(!cmp) {
1198               /* keeping this tag */
1199               ++oldtags;
1200               ++newtags;
1201             } else if(cmp < 0)
1202               /* old tag fits into a gap in the new list, so delete old */
1203               goto delete_old;
1204             else
1205               /* new tag fits into a gap in the old list, so insert new */
1206               goto insert_new;
1207           } else if(*oldtags) {
1208             /* we've run out of new tags, so remaining old ones are to be
1209              * deleted */
1210           delete_old:
1211             if(trackdb_delkeydata(trackdb_tagsdb,
1212                                   *oldtags, track, tid) == DB_LOCK_DEADLOCK)
1213               goto fail;
1214             ++oldtags;
1215           } else {
1216             /* we've run out of old tags, so remainig new ones are to be
1217              * inserted */
1218           insert_new:
1219             if(register_tag(track, *newtags, tid)) goto fail;
1220             ++newtags;
1221           }
1222         }
1223         reqtracks = 0;
1224       }
1225     }
1226     err = 0;
1227     break;
1228 fail:
1229     trackdb_abort_transaction(tid);
1230   }
1231   trackdb_commit_transaction(tid);
1232   return err == 0 ? 0 : -1;
1233 }
1234
1235 /* get a pref */
1236 const char *trackdb_get(const char *track,
1237                         const char *name) {
1238   return kvp_get(trackdb_get_all(track), name);
1239 }
1240
1241 /* get all prefs as a 0-terminated array */
1242 struct kvp *trackdb_get_all(const char *track) {
1243   struct kvp *t, *p, **pp;
1244   DB_TXN *tid;
1245
1246   for(;;) {
1247     tid = trackdb_begin_transaction();
1248     if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1249       goto fail;
1250     break;
1251 fail:
1252     trackdb_abort_transaction(tid);
1253   }
1254   trackdb_commit_transaction(tid);
1255   for(pp = &p; *pp; pp = &(*pp)->next)
1256     ;
1257   *pp = t;
1258   return p;
1259 }
1260
1261 /* resolve alias */
1262 const char *trackdb_resolve(const char *track) {
1263   DB_TXN *tid;
1264   const char *actual;
1265   
1266   for(;;) {
1267     tid = trackdb_begin_transaction();
1268     if(gettrackdata(track, 0, 0, &actual, 0, tid) == DB_LOCK_DEADLOCK)
1269       goto fail;
1270     break;
1271 fail:
1272     trackdb_abort_transaction(tid);
1273   }
1274   trackdb_commit_transaction(tid);
1275   return actual;
1276 }
1277
1278 int trackdb_isalias(const char *track) {
1279   const char *actual = trackdb_resolve(track);
1280
1281   return strcmp(actual, track);
1282 }
1283
1284 /* test whether a track exists (perhaps an alias) */
1285 int trackdb_exists(const char *track) {
1286   DB_TXN *tid;
1287   int err;
1288
1289   for(;;) {
1290     tid = trackdb_begin_transaction();
1291     /* unusually, here we want the return value */
1292     if((err = gettrackdata(track, 0, 0, 0, 0, tid)) == DB_LOCK_DEADLOCK)
1293       goto fail;
1294     break;
1295 fail:
1296     trackdb_abort_transaction(tid);
1297   }
1298   trackdb_commit_transaction(tid);
1299   return (err == 0);
1300 }
1301
1302 /* return the list of tags */
1303 char **trackdb_alltags(void) {
1304   DB_TXN *tid;
1305   int err;
1306   char **taglist;
1307
1308   for(;;) {
1309     tid = trackdb_begin_transaction();
1310     err = trackdb_alltags_tid(tid, &taglist);
1311     if(!err) break;
1312     trackdb_abort_transaction(tid);
1313   }
1314   trackdb_commit_transaction(tid);
1315   return taglist;
1316 }
1317
1318 static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp) {
1319   struct vector v;
1320   DBC *c;
1321   DBT k, d;
1322   int err;
1323
1324   vector_init(&v);
1325   c = trackdb_opencursor(trackdb_tagsdb, tid);
1326   memset(&k, 0, sizeof k);
1327   while(!(err = c->c_get(c, &k, prepare_data(&d), DB_NEXT_NODUP)))
1328     vector_append(&v, xstrndup(k.data, k.size));
1329   switch(err) {
1330   case DB_NOTFOUND:
1331     break;
1332   case DB_LOCK_DEADLOCK:
1333       return err;
1334   default:
1335     fatal(0, "c->c_get: %s", db_strerror(err));
1336   }
1337   if((err = trackdb_closecursor(c))) return err;
1338   vector_terminate(&v);
1339   *taglistp = v.vec;
1340   return 0;
1341 }
1342
1343 /* return 1 iff sorted tag lists A and B have at least one member in common */
1344 static int tag_intersection(char **a, char **b) {
1345   int cmp;
1346
1347   /* Same sort of logic as trackdb_set() above */
1348   while(*a && *b) {
1349     if(!(cmp = strcmp(*a, *b))) return 1;
1350     else if(cmp < 0) ++a;
1351     else ++b;
1352   }
1353   return 0;
1354 }
1355
1356 /* Check whether a track is suitable for random play.  Returns 0 if it is,
1357  * DB_NOTFOUND if it is not or DB_LOCK_DEADLOCK if the database gave us
1358  * that. */
1359 static int check_suitable(const char *track,
1360                           DB_TXN *tid,
1361                           char **required_tags,
1362                           char **prohibited_tags) {
1363   char **track_tags;
1364   time_t last, now;
1365   struct kvp *p, *t;
1366   const char *pick_at_random, *played_time;
1367
1368   /* don't pick tracks that aren't in any surviving collection (for instance
1369    * you've edited the config but the rescan hasn't done its job yet) */
1370   if(!find_track_root(track)) {
1371     info("found track not in any collection: %s", track);
1372     return DB_NOTFOUND;
1373   }
1374   /* don't pick aliases - only pick the canonical form */
1375   if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1376     return DB_LOCK_DEADLOCK;
1377   if(kvp_get(t, "_alias_for"))
1378     return DB_NOTFOUND;
1379   /* check that random play is not suppressed for this track */
1380   if((pick_at_random = kvp_get(p, "pick_at_random"))
1381      && !strcmp(pick_at_random, "0"))
1382     return DB_NOTFOUND;
1383   /* don't pick a track that's been played in the last 8 hours */
1384   if((played_time = kvp_get(p, "played_time"))) {
1385     last = atoll(played_time);
1386     now = time(0);
1387     if(now < last + 8 * 3600)       /* TODO configurable */
1388       return DB_NOTFOUND;
1389   }
1390   track_tags = parsetags(kvp_get(p, "tags"));
1391   /* check that no prohibited tag is present for this track */
1392   if(prohibited_tags && tag_intersection(track_tags, prohibited_tags))
1393     return DB_NOTFOUND;
1394   /* check that at least one required tags is present for this track */
1395   if(*required_tags && !tag_intersection(track_tags, required_tags))
1396     return DB_NOTFOUND;
1397   return 0;
1398 }
1399
1400 /* attempt to pick a random non-alias track */
1401 const char *trackdb_random(int tries) {
1402   DBT key, data;
1403   DB_BTREE_STAT *sp;
1404   int err, n;
1405   DB_TXN *tid;
1406   const char *track, *candidate;
1407   db_recno_t r;
1408   const char *tags;
1409   char **required_tags, **prohibited_tags, **tp;
1410   hash *h;
1411   DBC *c = 0;
1412
1413   for(;;) {
1414     tid = trackdb_begin_transaction();
1415     if((err = trackdb_get_global_tid("required-tags", tid, &tags)))
1416       goto fail;
1417     required_tags = parsetags(tags);
1418     if((err = trackdb_get_global_tid("prohibited-tags", tid, &tags)))
1419       goto fail;
1420     prohibited_tags = parsetags(tags);
1421     track = 0;
1422     if(*required_tags) {
1423       /* Bung all the suitable tracks into a hash and convert to a list of keys
1424        * (to eliminate duplicates).  We cache this list since it is possible
1425        * that it will be very large. */
1426       if(!reqtracks) {
1427         h = hash_new(0);
1428         for(tp = required_tags; *tp; ++tp) {
1429           c = trackdb_opencursor(trackdb_tagsdb, tid);
1430           memset(&key, 0, sizeof key);
1431           key.data = *tp;
1432           key.size = strlen(*tp);
1433           n = 0;
1434           err = c->c_get(c, &key, prepare_data(&data), DB_SET);
1435           while(err == 0) {
1436             hash_add(h, xstrndup(data.data, data.size), 0,
1437                      HASH_INSERT_OR_REPLACE);
1438             ++n;
1439             err = c->c_get(c, &key, prepare_data(&data), DB_NEXT_DUP);
1440           }
1441           switch(err) {
1442           case 0:
1443           case DB_NOTFOUND:
1444             break;
1445           case DB_LOCK_DEADLOCK:
1446             goto fail;
1447           default:
1448             fatal(0, "error querying tags.db: %s", db_strerror(err));
1449           }
1450           trackdb_closecursor(c);
1451           c = 0;
1452           if(!n)
1453             error(0, "required tag %s does not match any tracks", *tp);
1454         }
1455         nreqtracks = hash_count(h);
1456         reqtracks = hash_keys(h);
1457       }
1458       while(nreqtracks && !track && tries-- > 0) {
1459         r = (rand() * (double)nreqtracks / (RAND_MAX + 1.0));
1460         candidate = reqtracks[r];
1461         switch(check_suitable(candidate, tid,
1462                               required_tags, prohibited_tags)) {
1463         case 0:
1464           track = candidate;
1465           break;
1466         case DB_NOTFOUND:
1467           break;
1468         case DB_LOCK_DEADLOCK:
1469           goto fail;
1470         }
1471       }
1472     } else {
1473       /* No required tags.  We pick random record numbers in the database
1474        * instead. */
1475       switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp, 0)) {
1476       case 0:
1477         break;
1478       case DB_LOCK_DEADLOCK:
1479         error(0, "error querying tracks.db: %s", db_strerror(err));
1480         goto fail;
1481       default:
1482         fatal(0, "error querying tracks.db: %s", db_strerror(err));
1483       }
1484       if(!sp->bt_nkeys)
1485         error(0, "cannot pick tracks at random from an empty database");
1486       while(sp->bt_nkeys && !track && tries-- > 0) {
1487         /* record numbers count from 1 upwards */
1488         r = 1 + (rand() * (double)sp->bt_nkeys / (RAND_MAX + 1.0));
1489         memset(&key, sizeof key, 0);
1490         key.flags = DB_DBT_MALLOC;
1491         key.size = sizeof r;
1492         key.data = &r;
1493         switch(err = trackdb_tracksdb->get(trackdb_tracksdb, tid, &key, prepare_data(&data),
1494                                            DB_SET_RECNO)) {
1495         case 0:
1496           break;
1497         case DB_LOCK_DEADLOCK:
1498           error(0, "error querying tracks.db: %s", db_strerror(err));
1499           goto fail;
1500         default:
1501           fatal(0, "error querying tracks.db: %s", db_strerror(err));
1502         }
1503         candidate = xstrndup(key.data, key.size);
1504         switch(check_suitable(candidate, tid,
1505                               required_tags, prohibited_tags)) {
1506         case 0:
1507           track = candidate;
1508           break;
1509         case DB_NOTFOUND:
1510           break;
1511         case DB_LOCK_DEADLOCK:
1512           goto fail;
1513         }
1514       }
1515     }
1516     break;
1517 fail:
1518     trackdb_closecursor(c);
1519     c = 0;
1520     trackdb_abort_transaction(tid);
1521   }
1522   trackdb_commit_transaction(tid);
1523   if(!track)
1524     error(0, "could not pick a random track");
1525   return track;
1526 }
1527
1528 /* get a track name given the prefs.  Set *used_db to 1 if we got the answer
1529  * from the prefs. */
1530 static const char *getpart(const char *track,
1531                            const char *context,
1532                            const char *part,
1533                            const struct kvp *p,
1534                            int *used_db) {
1535   const char *result;
1536   char *pref;
1537
1538   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1539   if((result = kvp_get(p, pref)))
1540     *used_db = 1;
1541   else
1542     result = trackname_part(track, context, part);
1543   assert(result != 0);
1544   return result;
1545 }
1546
1547 /* get a track name part, like trackname_part(), but taking the database into
1548  * account. */
1549 const char *trackdb_getpart(const char *track,
1550                             const char *context,
1551                             const char *part) {
1552   struct kvp *p;
1553   DB_TXN *tid;
1554   char *pref;
1555   const char *actual;
1556   int used_db, err;
1557
1558   /* construct the full pref */
1559   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1560   for(;;) {
1561     tid = trackdb_begin_transaction();
1562     if((err = gettrackdata(track, 0, &p, &actual, 0, tid)) == DB_LOCK_DEADLOCK)
1563       goto fail;
1564     break;
1565 fail:
1566     trackdb_abort_transaction(tid);
1567   }
1568   trackdb_commit_transaction(tid);
1569   return getpart(actual, context, part, p, &used_db);
1570 }
1571
1572 /* get the raw path name for @track@ (might be an alias) */
1573 const char *trackdb_rawpath(const char *track) {
1574   DB_TXN *tid;
1575   struct kvp *t;
1576   const char *path;
1577
1578   for(;;) {
1579     tid = trackdb_begin_transaction();
1580     if(gettrackdata(track, &t, 0, 0, 0, tid) == DB_LOCK_DEADLOCK)
1581       goto fail;
1582     break;
1583 fail:
1584     trackdb_abort_transaction(tid);
1585   }
1586   trackdb_commit_transaction(tid);
1587   if(!(path = kvp_get(t, "_path"))) path = track;
1588   return path;
1589 }
1590
1591 /* trackdb_list **************************************************************/
1592
1593 /* this is incredibly ugly, sorry, perhaps it will be rewritten to be actually
1594  * readable at some point */
1595
1596 /* return true if the basename of TRACK[0..TL-1], as defined by DL, matches RE.
1597  * If RE is a null pointer then it matches everything. */
1598 static int track_matches(size_t dl, const char *track, size_t tl,
1599                          const pcre *re) {
1600   int ovec[3], rc;
1601
1602   if(!re)
1603     return 1;
1604   track += dl + 1;
1605   tl -= (dl + 1);
1606   switch(rc = pcre_exec(re, 0, track, tl, 0, 0, ovec, 3)) {
1607   case PCRE_ERROR_NOMATCH: return 0;
1608   default:
1609     if(rc < 0) {
1610       error(0, "pcre_exec returned %d, subject '%s'", rc, track);
1611       return 0;
1612     }
1613     return 1;
1614   }
1615 }
1616
1617 static int do_list(struct vector *v, const char *dir,
1618                    enum trackdb_listable what, const pcre *re, DB_TXN *tid) {
1619   DBC *cursor;
1620   DBT k, d;
1621   size_t dl;
1622   char *ptr;
1623   int err;
1624   size_t l, last_dir_len = 0;
1625   char *last_dir = 0, *track, *alias;
1626   struct kvp *p;
1627   
1628   dl = strlen(dir);
1629   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1630   make_key(&k, dir);
1631   prepare_data(&d);
1632   /* find the first key >= dir */
1633   err = cursor->c_get(cursor, &k, &d, DB_SET_RANGE);
1634   /* keep going while we're dealing with <dir/anything> */
1635   while(err == 0
1636         && k.size > dl
1637         && ((char *)k.data)[dl] == '/'
1638         && !memcmp(k.data, dir, dl)) {
1639     ptr = memchr((char *)k.data + dl + 1, '/', k.size - (dl + 1));
1640     if(ptr) {
1641       /* we have <dir/component/anything>, so <dir/component> is a directory */
1642       l = ptr - (char *)k.data;
1643       if(what & trackdb_directories)
1644         if(!(last_dir
1645              && l == last_dir_len
1646              && !memcmp(last_dir, k.data, l))) {
1647           last_dir = xstrndup(k.data, last_dir_len = l);
1648           if(track_matches(dl, k.data, l, re))
1649             vector_append(v, last_dir);
1650         }
1651     } else {
1652       /* found a plain file */
1653       if((what & trackdb_files)) {
1654         track = xstrndup(k.data, k.size);
1655         if((err = trackdb_getdata(trackdb_prefsdb,
1656                                   track, &p, tid)) == DB_LOCK_DEADLOCK)
1657           goto deadlocked;
1658         /* if this file has an alias in the same directory then we skip it */
1659         if((err = compute_alias(&alias, track, p, tid)))
1660           goto deadlocked;
1661         if(!(alias && !strcmp(d_dirname(alias), d_dirname(track))))
1662           if(track_matches(dl, k.data, k.size, re))
1663             vector_append(v, track);
1664       }
1665     }
1666     err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1667   }
1668   switch(err) {
1669   case 0:
1670     break;
1671   case DB_NOTFOUND:
1672     err = 0;
1673     break;
1674   case DB_LOCK_DEADLOCK:
1675     error(0, "error querying database: %s", db_strerror(err));
1676     break;
1677   default:
1678     fatal(0, "error querying database: %s", db_strerror(err));
1679   }
1680 deadlocked:
1681   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1682   return err;
1683 }
1684
1685 /* return the directories or files below @dir@ */
1686 char **trackdb_list(const char *dir, int *np, enum trackdb_listable what,
1687                     const pcre *re) {
1688   DB_TXN *tid;
1689   int n;
1690   struct vector v;
1691
1692   vector_init(&v);
1693   for(;;) {
1694     tid = trackdb_begin_transaction();
1695     v.nvec = 0;
1696     if(dir) {
1697       if(do_list(&v, dir, what, re, tid))
1698         goto fail;
1699     } else {
1700       for(n = 0; n < config->collection.n; ++n)
1701         if(do_list(&v, config->collection.s[n].root, what, re, tid))
1702           goto fail;
1703     }
1704     break;
1705 fail:
1706     trackdb_abort_transaction(tid);
1707   }
1708   trackdb_commit_transaction(tid);
1709   vector_terminate(&v);
1710   if(np)
1711     *np = v.nvec;
1712   return v.vec;
1713 }  
1714
1715 /* If S is tag:something, return something.  Else return 0. */
1716 static const char *checktag(const char *s) {
1717   if(!strncmp(s, "tag:", 4))
1718     return s + 4;
1719   else
1720     return 0;
1721 }
1722
1723 /* return a list of tracks containing all of the words given.  If you
1724  * ask for only stopwords you get no tracks. */
1725 char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) {
1726   const char **w, *best = 0, *tag;
1727   char **twords, **tags;
1728   int i, j, n, err, what;
1729   DBC *cursor = 0;
1730   DBT k, d;
1731   struct vector u, v;
1732   DB_TXN *tid;
1733   struct kvp *p;
1734   int ntags = 0;
1735   DB *db;
1736   const char *dbname;
1737
1738   *ntracks = 0;                         /* for early returns */
1739   /* casefold all the words */
1740   w = xmalloc(nwordlist * sizeof (char *));
1741   for(n = 0; n < nwordlist; ++n) {
1742     w[n] = casefold(wordlist[n]);
1743     if(checktag(w[n])) ++ntags;         /* count up tags */
1744   }
1745   /* find the longest non-stopword */
1746   for(n = 0; n < nwordlist; ++n)
1747     if(!stopword(w[n]) && !checktag(w[n]))
1748       if(!best || strlen(w[n]) > strlen(best))
1749         best = w[n];
1750   /* TODO: we should at least in principal be able to identify the word or tag
1751    * with the least matches in log time, and choose that as our primary search
1752    * term. */
1753   if(ntags && !best) {
1754     /* Only tags are listed.  We limit to the first and narrow down with the
1755      * rest. */
1756     best = checktag(w[0]);
1757     db = trackdb_tagsdb;
1758     dbname = "tags";
1759   } else if(best) {
1760     /* We can limit to some word. */
1761     db = trackdb_searchdb;
1762     dbname = "search";
1763   } else {
1764     /* Only stopwords */
1765     return 0;
1766   }
1767   vector_init(&u);
1768   vector_init(&v);
1769   for(;;) {
1770     tid = trackdb_begin_transaction();
1771     /* find all the tracks that have that word */
1772     make_key(&k, best);
1773     prepare_data(&d);
1774     what = DB_SET;
1775     v.nvec = 0;
1776     cursor = trackdb_opencursor(db, tid);
1777     while(!(err = cursor->c_get(cursor, &k, &d, what))) {
1778       vector_append(&v, xstrndup(d.data, d.size));
1779       what = DB_NEXT_DUP;
1780     }
1781     switch(err) {
1782     case DB_NOTFOUND:
1783       err = 0;
1784       break;
1785     case DB_LOCK_DEADLOCK:
1786       error(0, "error querying %s database: %s", dbname, db_strerror(err));
1787       break;
1788     default:
1789       fatal(0, "error querying %s database: %s", dbname, db_strerror(err));
1790     }
1791     if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1792     cursor = 0;
1793     /* do a naive search over that (hopefuly fairly small) list of tracks */
1794     u.nvec = 0;
1795     for(n = 0; n < v.nvec; ++n) {
1796       if((err = gettrackdata(v.vec[n], 0, &p, 0, 0, tid) == DB_LOCK_DEADLOCK))
1797         goto fail;
1798       else if(err) {
1799         error(0, "track %s unexpected error: %s", v.vec[n], db_strerror(err));
1800         continue;
1801       }
1802       twords = track_to_words(v.vec[n], p);
1803       tags = parsetags(kvp_get(p, "tags"));
1804       for(i = 0; i < nwordlist; ++i) {
1805         if((tag = checktag(w[i]))) {
1806           /* Track must have this tag */
1807           for(j = 0; tags[j]; ++j)
1808             if(!strcmp(tag, tags[j])) break; /* tag found */
1809           if(!tags[j]) break;           /* tag not found */
1810         } else {
1811           /* Track must contain this word */
1812           for(j = 0; twords[j]; ++j)
1813             if(!strcmp(w[i], twords[j])) break; /* word found */
1814           if(!twords[j]) break;         /* word not found */
1815         }
1816       }
1817       if(i >= nwordlist)                /* all words found */
1818         vector_append(&u, v.vec[n]);
1819     }
1820     break;
1821   fail:
1822     trackdb_closecursor(cursor);
1823     cursor = 0;
1824     trackdb_abort_transaction(tid);
1825     info("retrying search");
1826   }
1827   trackdb_commit_transaction(tid);
1828   vector_terminate(&u);
1829   if(ntracks)
1830     *ntracks = u.nvec;
1831   return u.vec;
1832 }
1833
1834 /* trackdb_scan **************************************************************/
1835
1836 int trackdb_scan(const char *root,
1837                  int (*callback)(const char *track,
1838                                  struct kvp *data,
1839                                  void *u,
1840                                  DB_TXN *tid),
1841                  void *u,
1842                  DB_TXN *tid) {
1843   DBC *cursor;
1844   DBT k, d;
1845   const size_t root_len = root ? strlen(root) : 0;
1846   int err, cberr;
1847   struct kvp *data;
1848   const char *track;
1849
1850   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1851   if(root)
1852     err = cursor->c_get(cursor, make_key(&k, root), prepare_data(&d),
1853                         DB_SET_RANGE);
1854   else {
1855     memset(&k, 0, sizeof k);
1856     err = cursor->c_get(cursor, &k, prepare_data(&d),
1857                         DB_FIRST);
1858   }
1859   while(!err) {
1860     if(!root
1861        || (k.size > root_len
1862            && !strncmp(k.data, root, root_len)
1863            && ((char *)k.data)[root_len] == '/')) {
1864       data = kvp_urldecode(d.data, d.size);
1865       if(kvp_get(data, "_path")) {
1866         track = xstrndup(k.data, k.size);
1867         /* Advance to the next track before the callback so that the callback
1868          * may safely delete the track */
1869         err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1870         if((cberr = callback(track, data, u, tid))) {
1871           err = cberr;
1872           break;
1873         }
1874       } else
1875         err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1876     } else
1877       break;
1878   }
1879   trackdb_closecursor(cursor);
1880   switch(err) {
1881   case EINTR:
1882     return err;
1883   case 0:
1884   case DB_NOTFOUND:
1885     return 0;
1886   case DB_LOCK_DEADLOCK:
1887     error(0, "c->c_get: %s", db_strerror(err));
1888     return err;
1889   default:
1890     fatal(0, "c->c_get: %s", db_strerror(err));
1891   }
1892 }
1893
1894 /* trackdb_rescan ************************************************************/
1895
1896 /* called when the rescanner terminates */
1897 static int reap_rescan(ev_source attribute((unused)) *ev,
1898                        pid_t pid,
1899                        int status,
1900                        const struct rusage attribute((unused)) *rusage,
1901                        void attribute((unused)) *u) {
1902   if(pid == rescan_pid) rescan_pid = -1;
1903   if(status)
1904     error(0, "disorderd-rescan: %s", wstat(status));
1905   else
1906     D(("disorderd-rescan terminate: %s", wstat(status)));
1907   /* Our cache of file lookups is out of date now */
1908   cache_clean(&cache_files_type);
1909   eventlog("rescanned", (char *)0);
1910   return 0;
1911 }
1912
1913 void trackdb_rescan(ev_source *ev) {
1914   if(rescan_pid != -1) {
1915     error(0, "rescan already underway");
1916     return;
1917   }
1918   rescan_pid = subprogram(ev, RESCAN, -1);
1919   ev_child(ev, rescan_pid, 0, reap_rescan, 0);
1920   D(("started rescanner"));
1921   
1922 }
1923
1924 int trackdb_rescan_cancel(void) {
1925   if(rescan_pid == -1) return 0;
1926   if(kill(rescan_pid, SIGTERM) < 0)
1927     fatal(errno, "error killing rescanner");
1928   rescan_pid = -1;
1929   return 1;
1930 }
1931
1932 /* global prefs **************************************************************/
1933
1934 void trackdb_set_global(const char *name,
1935                         const char *value,
1936                         const char *who) {
1937   DB_TXN *tid;
1938   int err;
1939   int state;
1940
1941   for(;;) {
1942     tid = trackdb_begin_transaction();
1943     if(!(err = trackdb_set_global_tid(name, value, tid)))
1944       break;
1945     trackdb_abort_transaction(tid);
1946   }
1947   trackdb_commit_transaction(tid);
1948   /* log important state changes */
1949   if(!strcmp(name, "playing")) {
1950     state = !value || !strcmp(value, "yes");
1951     info("playing %s by %s",
1952          state ? "enabled" : "disabled",
1953          who ? who : "-");
1954     eventlog("state", state ? "enable_play" : "disable_play", (char *)0);
1955   }
1956   if(!strcmp(name, "random-play")) {
1957     state = !value || !strcmp(value, "yes");
1958     info("random play %s by %s",
1959          state ? "enabled" : "disabled",
1960          who ? who : "-");
1961     eventlog("state", state ? "enable_random" : "disable_random", (char *)0);
1962   }
1963   if(!strcmp(name, "required-tags"))
1964     reqtracks = 0;
1965 }
1966
1967 int trackdb_set_global_tid(const char *name,
1968                            const char *value,
1969                            DB_TXN *tid) {
1970   DBT k, d;
1971   int err;
1972
1973   memset(&k, 0, sizeof k);
1974   memset(&d, 0, sizeof d);
1975   k.data = (void *)name;
1976   k.size = strlen(name);
1977   if(value) {
1978     d.data = (void *)value;
1979     d.size = strlen(value);
1980   }
1981   if(value)
1982     err = trackdb_globaldb->put(trackdb_globaldb, tid, &k, &d, 0);
1983   else
1984     err = trackdb_globaldb->del(trackdb_globaldb, tid, &k, 0);
1985   if(err == DB_LOCK_DEADLOCK) return err;
1986   if(err)
1987     fatal(0, "error updating database: %s", db_strerror(err));
1988   return 0;
1989 }
1990
1991 const char *trackdb_get_global(const char *name) {
1992   DB_TXN *tid;
1993   int err;
1994   const char *r;
1995
1996   for(;;) {
1997     tid = trackdb_begin_transaction();
1998     if(!(err = trackdb_get_global_tid(name, tid, &r)))
1999       break;
2000     trackdb_abort_transaction(tid);
2001   }
2002   trackdb_commit_transaction(tid);
2003   return r;
2004 }
2005
2006 int trackdb_get_global_tid(const char *name,
2007                            DB_TXN *tid,
2008                            const char **rp) {
2009   DBT k, d;
2010   int err;
2011
2012   memset(&k, 0, sizeof k);
2013   k.data = (void *)name;
2014   k.size = strlen(name);
2015   switch(err = trackdb_globaldb->get(trackdb_globaldb, tid, &k,
2016                                      prepare_data(&d), 0)) {
2017   case 0:
2018     *rp = xstrndup(d.data, d.size);
2019     return 0;
2020   case DB_NOTFOUND:
2021     *rp = 0;
2022     return 0;
2023   case DB_LOCK_DEADLOCK:
2024     return err;
2025   default:
2026     fatal(0, "error reading database: %s", db_strerror(err));
2027   }
2028 }
2029
2030 /** @brief Retrieve the most recently added tracks
2031  * @param ntracksp Where to put count, or 0
2032  * @param maxtracks Maximum number of tracks to retrieve
2033  * @return null-terminated array of track names
2034  *
2035  * The most recently added track is first in the array.
2036  */
2037 char **trackdb_new(int *ntracksp,
2038                    int maxtracks) {
2039   DB_TXN *tid;
2040   char **tracks;
2041
2042   for(;;) {
2043     tid = trackdb_begin_transaction();
2044     tracks = trackdb_new_tid(ntracksp, maxtracks, tid);
2045     if(tracks)
2046       break;
2047     trackdb_abort_transaction(tid);
2048   }
2049   trackdb_commit_transaction(tid);
2050   return tracks;
2051 }
2052
2053 /** @brief Retrieve the most recently added tracks
2054  * @param ntracksp Where to put count, or 0
2055  * @param maxtracks Maximum number of tracks to retrieve, or 0 for all
2056  * @param tid Transaction ID
2057  * @return null-terminated array of track names, or NULL on deadlock
2058  *
2059  * The most recently added track is first in the array.
2060  */
2061 static char **trackdb_new_tid(int *ntracksp,
2062                               int maxtracks,
2063                               DB_TXN *tid) {
2064   DBC *c;
2065   DBT k, d;
2066   int err = 0;
2067   struct vector tracks[1];
2068
2069   vector_init(tracks);
2070   c = trackdb_opencursor(trackdb_noticeddb, tid);
2071   while((maxtracks <= 0 || tracks->nvec < maxtracks)
2072         && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV)))
2073     vector_append(tracks, xstrndup(d.data, d.size));
2074   switch(err) {
2075   case 0:                               /* hit maxtracks */
2076   case DB_NOTFOUND:                     /* ran out of tracks */
2077     break;
2078   case DB_LOCK_DEADLOCK:
2079     trackdb_closecursor(c);
2080     return 0;
2081   default:
2082     fatal(0, "error reading noticed.db: %s", db_strerror(err));
2083   }
2084   if((err = trackdb_closecursor(c)))
2085     return 0;                           /* deadlock */
2086   vector_terminate(tracks);
2087   if(ntracksp)
2088     *ntracksp = tracks->nvec;
2089   return tracks->vec;
2090 }
2091
2092 /** @brief Expire noticed.db
2093  * @param earliest Earliest timestamp to keep
2094  */
2095 void trackdb_expire_noticed(time_t earliest) {
2096   DB_TXN *tid;
2097
2098   for(;;) {
2099     tid = trackdb_begin_transaction();
2100     if(!trackdb_expire_noticed_tid(earliest, tid))
2101       break;
2102     trackdb_abort_transaction(tid);
2103   }
2104   trackdb_commit_transaction(tid);
2105 }
2106
2107 /** @brief Expire noticed.db
2108  * @param earliest Earliest timestamp to keep
2109  * @param tid Transaction ID
2110  * @return 0 or DB_LOCK_DEADLOCK
2111  */
2112 static int trackdb_expire_noticed_tid(time_t earliest, DB_TXN *tid) {
2113   DBC *c;
2114   DBT k, d;
2115   int err = 0, ret;
2116   time_t when;
2117   uint32_t *kk;
2118   int count = 0;
2119
2120   c = trackdb_opencursor(trackdb_noticeddb, tid);
2121   while(!(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_NEXT))) {
2122     kk = k.data;
2123     when = (time_t)(((uint64_t)ntohl(kk[0]) << 32) + ntohl(kk[1]));
2124     if(when >= earliest)
2125       break;
2126     if((err = c->c_del(c, 0))) {
2127       if(err != DB_LOCK_DEADLOCK)
2128         fatal(0, "error deleting expired noticed.db entry: %s",
2129               db_strerror(err));
2130       break;
2131     }
2132     ++count;
2133   }
2134   if(err == DB_NOTFOUND)
2135     err = 0;
2136   if(err && err != DB_LOCK_DEADLOCK)
2137     fatal(0, "error expiring noticed.db: %s", db_strerror(err));
2138   ret = err;
2139   if((err = trackdb_closecursor(c))) {
2140     if(err != DB_LOCK_DEADLOCK)
2141       fatal(0, "error closing cursor: %s", db_strerror(err));
2142     ret = err;
2143   }
2144   if(!ret && count)
2145     info("expired %d tracks from noticed.db", count);
2146   return ret;
2147 }
2148
2149 /* tidying up ****************************************************************/
2150
2151 void trackdb_gc(void) {
2152   int err;
2153   char **logfiles;
2154
2155   if((err = trackdb_env->txn_checkpoint(trackdb_env,
2156                                         config->checkpoint_kbyte,
2157                                         config->checkpoint_min,
2158                                         0)))
2159     fatal(0, "trackdb_env->txn_checkpoint: %s", db_strerror(err));
2160   if((err = trackdb_env->log_archive(trackdb_env, &logfiles, DB_ARCH_REMOVE)))
2161     fatal(0, "trackdb_env->log_archive: %s", db_strerror(err));
2162   /* This makes catastrophic recovery impossible.  However, the user can still
2163    * preserve the important data by using disorder-dump to snapshot their
2164    * prefs, and later to restore it.  This is likely to have much small
2165    * long-term storage requirements than record the db logfiles. */
2166 }
2167
2168 /*
2169 Local Variables:
2170 c-basic-offset:2
2171 comment-column:40
2172 fill-column:79
2173 indent-tabs-mode:nil
2174 End:
2175 */