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