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