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