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