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