chiark / gitweb /
find_track_root() now issues a log message if it finds a track outside
[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   const char *rootless = track_rootless(track);
495
496   if(!rootless)
497     rootless = track;                   /* bodge */
498   vector_init(&v);
499   if((w = words(casefold(strip_extension(rootless)), &nw)))
500     vector_append_many(&v, w, nw);
501
502   for(; p; p = p->next)
503     if(is_display_pref(p->name))
504       if((w = words(casefold(p->value), &nw)))
505         vector_append_many(&v, w, nw);
506   vector_terminate(&v);
507   return dedupe(v.vec, v.nvec);
508 }
509
510 /* return nonzero iff WORD is a stopword */
511 static int stopword(const char *word) {
512   int n;
513
514   for(n = 0; n < config->stopword.n
515         && strcmp(word, config->stopword.s[n]); ++n)
516     ;
517   return n < config->stopword.n;
518 }
519
520 /* record that WORD appears in TRACK.  Returns 0 or DB_LOCK_DEADLOCK. */
521 static int register_search_word(const char *track, const char *word,
522                                 DB_TXN *tid) {
523   if(stopword(word)) return 0;
524   return register_word(trackdb_searchdb, "search", track, word, tid);
525 }
526
527 /* Tags **********************************************************************/
528
529 /* Return nonzero if C is a valid tag character */
530 static int tagchar(int c) {
531   switch(c) {
532   case ',':
533     return 0;
534   default:
535     return c >= ' ';
536   }
537 }
538
539 /* Parse and de-dupe a tag list.  If S=0 then assumes "". */
540 static char **parsetags(const char *s) {
541   const char *t;
542   struct vector v;
543
544   vector_init(&v);
545   if(s) {
546     /* skip initial separators */
547     while(*s && (!tagchar(*s) || *s == ' '))
548       ++s;
549     while(*s) {
550       /* find the extent of the tag */
551       t = s;
552       while(*s && tagchar(*s))
553         ++s;
554       /* strip trailing spaces */
555       while(s > t && s[-1] == ' ')
556         --s;
557       vector_append(&v, xstrndup(t, s - t));
558       /* skip intermediate and trailing separators */
559       while(*s && (!tagchar(*s) || *s == ' '))
560         ++s;
561     }
562   }
563   vector_terminate(&v);
564   return dedupe(v.vec, v.nvec);
565 }
566
567 /* Record that TRACK has TAG.  Returns 0 or DB_LOCK_DEADLOCK. */
568 static int register_tag(const char *track, const char *tag, DB_TXN *tid) {
569   return register_word(trackdb_tagsdb, "tags", track, tag, tid);
570 }
571
572 /* aliases *******************************************************************/
573
574 /* compute the alias and store at aliasp.  Returns 0 or DB_LOCK_DEADLOCK.  If
575  * there is no alias sets *aliasp to 0. */
576 static int compute_alias(char **aliasp,
577                          const char *track,
578                          const struct kvp *p,
579                          DB_TXN *tid) {
580   struct dynstr d;
581   const char *s = config->alias, *t, *expansion, *part;
582   int c, used_db = 0, slash_prefix, err;
583   struct kvp *at;
584
585   dynstr_init(&d);
586   dynstr_append_string(&d, find_track_root(track));
587   while((c = (unsigned char)*s++)) {
588     if(c != '{') {
589       dynstr_append(&d, c);
590       continue;
591     }
592     if((slash_prefix = (*s == '/')))
593       s++;
594     t = strchr(s, '}');
595     assert(t != 0);                     /* validated at startup */
596     part = xstrndup(s, t - s);
597     expansion = getpart(track, "display", part, p, &used_db);
598     if(*expansion) {
599       if(slash_prefix) dynstr_append(&d, '/');
600       dynstr_append_string(&d, expansion);
601     }
602     s = t + 1;                          /* skip {part} */
603   }
604   /* only admit to the alias if we used the db... */
605   if(!used_db) {
606     *aliasp = 0;
607     return 0;
608   }
609   dynstr_terminate(&d);
610   /* ...and the answer differs from the original... */
611   if(!strcmp(track, d.vec)) {
612     *aliasp = 0;
613     return 0;
614   }
615   /* ...and there isn't already a different track with that name (including as
616    * an alias) */
617   switch(err = trackdb_getdata(trackdb_tracksdb, d.vec, &at, tid)) {
618   case 0:
619     if((s = kvp_get(at, "_alias_for"))
620        && !strcmp(s, track)) {
621     case DB_NOTFOUND:
622       *aliasp = d.vec;
623     } else {
624       *aliasp = 0;
625     }
626     return 0;
627   default:
628     return err;
629   }
630 }
631
632 /* get track and prefs data (if tp/pp not null pointers).  Returns 0 on
633  * success, DB_NOTFOUND if the track does not exist or DB_LOCK_DEADLOCK.
634  * Always sets the return values, even if only to null pointers. */
635 static int gettrackdata(const char *track,
636                         struct kvp **tp,
637                         struct kvp **pp,
638                         const char **actualp,
639                         unsigned flags,
640 #define GTD_NOALIAS 0x0001
641                         DB_TXN *tid) {
642   int err;
643   const char *actual = track;
644   struct kvp *t = 0, *p = 0;
645   
646   if((err = trackdb_getdata(trackdb_tracksdb, track, &t, tid))) goto done;
647   if((actual = kvp_get(t, "_alias_for"))) {
648     if(flags & GTD_NOALIAS) {
649       error(0, "alias passed to gettrackdata where real path required");
650       abort();
651     }
652     if((err = trackdb_getdata(trackdb_tracksdb, actual, &t, tid))) goto done;
653   } else
654     actual = track;
655   assert(actual != 0);
656   if(pp) {
657     if((err = trackdb_getdata(trackdb_prefsdb, actual, &p, tid)) == DB_LOCK_DEADLOCK)
658       goto done;
659   }
660   err = 0;
661 done:
662   if(actualp) *actualp = actual;
663   if(tp) *tp = t;
664   if(pp) *pp = p;
665   return err;
666 }
667
668 /* trackdb_notice() **********************************************************/
669
670 /* notice a track */
671 int trackdb_notice(const char *track,
672                    const char *path) {
673   int err;
674   DB_TXN *tid;
675   
676   for(;;) {
677     tid = trackdb_begin_transaction();
678     err = trackdb_notice_tid(track, path, tid);
679     if((err == DB_LOCK_DEADLOCK)) goto fail;
680     break;
681   fail:
682     trackdb_abort_transaction(tid);
683   }
684   trackdb_commit_transaction(tid);
685   return err;
686 }
687
688 int trackdb_notice_tid(const char *track,
689                        const char *path,
690                        DB_TXN *tid) {
691   int err, n;
692   struct kvp *t, *a, *p;
693   int t_changed, ret;
694   char *alias, **w;
695   
696   /* notice whether the tracks.db entry changes */
697   t_changed = 0;
698   /* get any existing tracks entry */
699   if((err = gettrackdata(track, &t, &p, 0, 0, tid)) == DB_LOCK_DEADLOCK)
700     return err;
701   ret = err;
702   /* this is a real track */
703   t_changed += kvp_set(&t, "_alias_for", 0);
704   t_changed += kvp_set(&t, "_path", path);
705   /* if we have an alias record it in the database */
706   if((err = compute_alias(&alias, track, p, tid))) return err;
707   if(alias) {
708     /* won't overwrite someone else's alias as compute_alias() checks */
709     D(("%s: alias %s", track, alias));
710     a = 0;
711     kvp_set(&a, "_alias_for", track);
712     if((err = trackdb_putdata(trackdb_tracksdb, alias, a, tid, 0))) return err;
713   }
714   /* update search.db */
715   w = track_to_words(track, p);
716   for(n = 0; w[n]; ++n)
717     if((err = register_search_word(track, w[n], tid)))
718       return err;
719   /* update tags.db */
720   w = parsetags(kvp_get(p, "tags"));
721   for(n = 0; w[n]; ++n)
722     if((err = register_tag(track, w[n], tid)))
723       return err;
724   reqtracks = 0;
725   /* only store the tracks.db entry if it has changed */
726   if(t_changed && (err = trackdb_putdata(trackdb_tracksdb, track, t, tid, 0)))
727     return err;
728   return ret;
729 }
730
731 /* trackdb_obsolete() ********************************************************/
732
733 /* obsolete a track */
734 int trackdb_obsolete(const char *track, DB_TXN *tid) {
735   int err, n;
736   struct kvp *p;
737   char *alias, **w;
738
739   if((err = gettrackdata(track, 0, &p, 0,
740                          GTD_NOALIAS, tid)) == DB_LOCK_DEADLOCK)
741     return err;
742   else if(err == DB_NOTFOUND) return 0;
743   /* compute the alias, if any, and delete it */
744   if(compute_alias(&alias, track, p, tid)) return err;
745   if(alias) {
746     /* if the alias points to some other track then compute_alias won't
747      * return it */
748     if(trackdb_delkey(trackdb_tracksdb, alias, tid))
749       return err;
750   }
751   /* update search.db */
752   w = track_to_words(track, p);
753   for(n = 0; w[n]; ++n)
754     if(trackdb_delkeydata(trackdb_searchdb,
755                           w[n], track, tid) == DB_LOCK_DEADLOCK)
756       return err;
757   /* update tags.db */
758   w = parsetags(kvp_get(p, "tags"));
759   for(n = 0; w[n]; ++n)
760     if(trackdb_delkeydata(trackdb_tagsdb,
761                           w[n], track, tid) == DB_LOCK_DEADLOCK)
762       return err;
763   reqtracks = 0;
764   /* update tracks.db */
765   if(trackdb_delkey(trackdb_tracksdb, track, tid) == DB_LOCK_DEADLOCK)
766     return err;
767   /* We don't delete the prefs, so they survive temporary outages of the
768    * (possibly virtual) track filesystem */
769   return 0;
770 }
771
772 /* trackdb_stats() ***********************************************************/
773
774 #define H(name) { #name, offsetof(DB_HASH_STAT, name) }
775 #define B(name) { #name, offsetof(DB_BTREE_STAT, name) }
776
777 static const struct statinfo {
778   const char *name;
779   size_t offset;
780 } statinfo_hash[] = {
781   H(hash_magic),
782   H(hash_version),
783   H(hash_nkeys),
784   H(hash_ndata),
785   H(hash_pagesize),
786   H(hash_ffactor),
787   H(hash_buckets),
788   H(hash_free),
789   H(hash_bfree),
790   H(hash_bigpages),
791   H(hash_big_bfree),
792   H(hash_overflows),
793   H(hash_ovfl_free),
794   H(hash_dup),
795   H(hash_dup_free),
796 }, statinfo_btree[] = {
797   B(bt_magic),
798   B(bt_version),
799   B(bt_nkeys),
800   B(bt_ndata),
801   B(bt_pagesize),
802   B(bt_minkey),
803   B(bt_re_len),
804   B(bt_re_pad),
805   B(bt_levels),
806   B(bt_int_pg),
807   B(bt_leaf_pg),
808   B(bt_dup_pg),
809   B(bt_over_pg),
810   B(bt_free),
811   B(bt_int_pgfree),
812   B(bt_leaf_pgfree),
813   B(bt_dup_pgfree),
814   B(bt_over_pgfree),
815 };
816
817 /* look up stats for DB */
818 static int get_stats(struct vector *v,
819                      DB *database,
820                      const struct statinfo *si,
821                      size_t nsi,
822                      DB_TXN *tid) {
823   void *sp;
824   size_t n;
825   char *str;
826   int err;
827
828   if(database) {
829     switch(err = database->stat(database, tid, &sp, 0)) {
830     case 0:
831       break;
832     case DB_LOCK_DEADLOCK:
833       error(0, "error querying database: %s", db_strerror(err));
834       return err;
835     default:
836       fatal(0, "error querying database: %s", db_strerror(err));
837     }
838     for(n = 0; n < nsi; ++n) {
839       byte_xasprintf(&str, "%s=%"PRIuMAX, si[n].name,
840                      (uintmax_t)*(u_int32_t *)((char *)sp + si[n].offset));
841       vector_append(v, str);
842     }
843   }
844   return 0;
845 }
846
847 struct search_entry {
848   char *word;
849   int n;
850 };
851
852 /* find the top COUNT words in the search database */
853 static int search_league(struct vector *v, int count, DB_TXN *tid) {
854   struct search_entry *se;
855   DBT k, d;
856   DBC *cursor;
857   int err, n = 0, nse = 0, i;
858   char *word = 0;
859   size_t wl = 0;
860   char *str;
861
862   cursor = trackdb_opencursor(trackdb_searchdb, tid);
863   se = xmalloc(count * sizeof *se);
864   while(!(err = cursor->c_get(cursor, prepare_data(&k), prepare_data(&d),
865                               DB_NEXT))) {
866     if(word && wl == k.size && !strncmp(word, k.data, wl))
867       ++n;
868     else {
869 #define FINALIZE() do {                                         \
870   if(word && (nse < count || n > se[nse - 1].n)) {              \
871     if(nse == count)                                            \
872       i = nse - 1;                                              \
873     else                                                        \
874       i = nse++;                                                \
875     while(i > 0 && n > se[i - 1].n)                             \
876       --i;                                                      \
877     memmove(&se[i + 1], &se[i], (nse - i) * sizeof *se);        \
878     se[i].word = word;                                          \
879     se[i].n = n;                                                \
880   }                                                             \
881 } while(0)
882       FINALIZE();
883       word = xstrndup(k.data, wl = k.size);
884       n = 1;
885     }
886   }
887   switch(err) {
888   case DB_NOTFOUND:
889     err = 0;
890     break;
891   case DB_LOCK_DEADLOCK:
892     error(0, "error querying search database: %s", db_strerror(err));
893     break;
894   default:
895     fatal(0, "error querying search database: %s", db_strerror(err));
896   }
897   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
898   if(err) return err;
899   FINALIZE();
900   byte_xasprintf(&str, "Top %d search words:", nse);
901   vector_append(v, str);
902   for(i = 0; i < nse; ++i) {
903     byte_xasprintf(&str, "%4d: %5d %s", i + 1, se[i].n, se[i].word);
904     vector_append(v, str);
905   }
906   return 0;
907 }
908
909 #define SI(what) statinfo_##what, \
910                  sizeof statinfo_##what / sizeof (struct statinfo)
911
912 /* return a list of database stats */
913 char **trackdb_stats(int *nstatsp) {
914   DB_TXN *tid;
915   struct vector v;
916   char *s;
917   
918   vector_init(&v);
919   for(;;) {
920     tid = trackdb_begin_transaction();
921     v.nvec = 0;
922     vector_append(&v, (char *)"Tracks database stats:");
923     if(get_stats(&v, trackdb_tracksdb, SI(btree), tid)) goto fail;
924     vector_append(&v, (char *)"");
925     vector_append(&v, (char *)"Search database stats:");
926     if(get_stats(&v, trackdb_searchdb, SI(hash), tid)) goto fail;
927     vector_append(&v, (char *)"");
928     vector_append(&v, (char *)"Prefs database stats:");
929     if(get_stats(&v, trackdb_prefsdb, SI(hash), tid)) goto fail;
930     vector_append(&v, (char *)"");
931     if(search_league(&v, 10, tid)) goto fail;
932     vector_append(&v, (char *)"");
933     vector_append(&v, (char *)"Server stats:");
934     byte_xasprintf(&s, "track lookup cache hits: %lu", cache_files_hits);
935     vector_append(&v, (char *)s);
936     byte_xasprintf(&s, "track lookup cache misses: %lu", cache_files_misses);
937     vector_append(&v, (char *)s);
938     vector_terminate(&v);
939     break;
940 fail:
941     trackdb_abort_transaction(tid);
942   }
943   trackdb_commit_transaction(tid);
944   if(nstatsp) *nstatsp = v.nvec;
945   return v.vec;
946 }
947
948 /* set a pref (remove if value=0) */
949 int trackdb_set(const char *track,
950                 const char *name,
951                 const char *value) {
952   struct kvp *t, *p, *a;
953   DB_TXN *tid;
954   int err, cmp;
955   char *oldalias, *newalias, **oldtags = 0, **newtags;
956
957   for(;;) {
958     tid = trackdb_begin_transaction();
959     if((err = gettrackdata(track, &t, &p, 0,
960                            0, tid)) == DB_LOCK_DEADLOCK)
961       goto fail;
962     if(err == DB_NOTFOUND) break;
963     if(name[0] == '_') {
964       if(kvp_set(&t, name, value))
965         if(trackdb_putdata(trackdb_tracksdb, track, t, tid, 0))
966           goto fail;
967     } else {
968       /* get the old alias name */
969       if(compute_alias(&oldalias, track, p, tid)) goto fail;
970       /* get the old tags */
971       if(!strcmp(name, "tags"))
972         oldtags = parsetags(kvp_get(p, "tags"));
973       /* set the value */
974       if(kvp_set(&p, name, value))
975         if(trackdb_putdata(trackdb_prefsdb, track, p, tid, 0))
976           goto fail;
977       /* compute the new alias name */
978       if((err = compute_alias(&newalias, track, p, tid))) goto fail;
979       /* check whether alias has changed */
980       if(!(oldalias == newalias
981            || (oldalias && newalias && !strcmp(oldalias, newalias)))) {
982         /* adjust alias records to fit change */
983         if(oldalias
984            && trackdb_delkey(trackdb_tracksdb, oldalias, tid)) goto fail;
985         if(newalias) {
986           a = 0;
987           kvp_set(&a, "_alias_for", track);
988           if(trackdb_putdata(trackdb_tracksdb, newalias, a, tid, 0)) goto fail;
989         }
990       }
991       /* check whether tags have changed */
992       if(!strcmp(name, "tags")) {
993         newtags = parsetags(value);
994         while(*oldtags || *newtags) {
995           if(*oldtags && *newtags) {
996             cmp = strcmp(*oldtags, *newtags);
997             if(!cmp) {
998               /* keeping this tag */
999               ++oldtags;
1000               ++newtags;
1001             } else if(cmp < 0)
1002               /* old tag fits into a gap in the new list, so delete old */
1003               goto delete_old;
1004             else
1005               /* new tag fits into a gap in the old list, so insert new */
1006               goto insert_new;
1007           } else if(*oldtags) {
1008             /* we've run out of new tags, so remaining old ones are to be
1009              * deleted */
1010           delete_old:
1011             if(trackdb_delkeydata(trackdb_tagsdb,
1012                                   *oldtags, track, tid) == DB_LOCK_DEADLOCK)
1013               goto fail;
1014             ++oldtags;
1015           } else {
1016             /* we've run out of old tags, so remainig new ones are to be
1017              * inserted */
1018           insert_new:
1019             if(register_tag(track, *newtags, tid)) goto fail;
1020             ++newtags;
1021           }
1022         }
1023         reqtracks = 0;
1024       }
1025     }
1026     err = 0;
1027     break;
1028 fail:
1029     trackdb_abort_transaction(tid);
1030   }
1031   trackdb_commit_transaction(tid);
1032   return err == 0 ? 0 : -1;
1033 }
1034
1035 /* get a pref */
1036 const char *trackdb_get(const char *track,
1037                         const char *name) {
1038   return kvp_get(trackdb_get_all(track), name);
1039 }
1040
1041 /* get all prefs as a 0-terminated array */
1042 struct kvp *trackdb_get_all(const char *track) {
1043   struct kvp *t, *p, **pp;
1044   DB_TXN *tid;
1045
1046   for(;;) {
1047     tid = trackdb_begin_transaction();
1048     if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1049       goto fail;
1050     break;
1051 fail:
1052     trackdb_abort_transaction(tid);
1053   }
1054   trackdb_commit_transaction(tid);
1055   for(pp = &p; *pp; pp = &(*pp)->next)
1056     ;
1057   *pp = t;
1058   return p;
1059 }
1060
1061 /* resolve alias */
1062 const char *trackdb_resolve(const char *track) {
1063   DB_TXN *tid;
1064   const char *actual;
1065   
1066   for(;;) {
1067     tid = trackdb_begin_transaction();
1068     if(gettrackdata(track, 0, 0, &actual, 0, tid) == DB_LOCK_DEADLOCK)
1069       goto fail;
1070     break;
1071 fail:
1072     trackdb_abort_transaction(tid);
1073   }
1074   trackdb_commit_transaction(tid);
1075   return actual;
1076 }
1077
1078 int trackdb_isalias(const char *track) {
1079   const char *actual = trackdb_resolve(track);
1080
1081   return strcmp(actual, track);
1082 }
1083
1084 /* test whether a track exists (perhaps an alias) */
1085 int trackdb_exists(const char *track) {
1086   DB_TXN *tid;
1087   int err;
1088
1089   for(;;) {
1090     tid = trackdb_begin_transaction();
1091     /* unusually, here we want the return value */
1092     if((err = gettrackdata(track, 0, 0, 0, 0, tid)) == DB_LOCK_DEADLOCK)
1093       goto fail;
1094     break;
1095 fail:
1096     trackdb_abort_transaction(tid);
1097   }
1098   trackdb_commit_transaction(tid);
1099   return (err == 0);
1100 }
1101
1102 /* return the list of tags */
1103 char **trackdb_alltags(void) {
1104   DB_TXN *tid;
1105   int err;
1106   char **taglist;
1107
1108   for(;;) {
1109     tid = trackdb_begin_transaction();
1110     err = trackdb_alltags_tid(tid, &taglist);
1111     if(!err) break;
1112     trackdb_abort_transaction(tid);
1113   }
1114   trackdb_commit_transaction(tid);
1115   return taglist;
1116 }
1117
1118 static int trackdb_alltags_tid(DB_TXN *tid, char ***taglistp) {
1119   struct vector v;
1120   DBC *c;
1121   DBT k, d;
1122   int err;
1123
1124   vector_init(&v);
1125   c = trackdb_opencursor(trackdb_tagsdb, tid);
1126   memset(&k, 0, sizeof k);
1127   while(!(err = c->c_get(c, &k, prepare_data(&d), DB_NEXT_NODUP)))
1128     vector_append(&v, xstrndup(k.data, k.size));
1129   switch(err) {
1130   case DB_NOTFOUND:
1131     break;
1132   case DB_LOCK_DEADLOCK:
1133       return err;
1134   default:
1135     fatal(0, "c->c_get: %s", db_strerror(err));
1136   }
1137   if((err = trackdb_closecursor(c))) return err;
1138   vector_terminate(&v);
1139   *taglistp = v.vec;
1140   return 0;
1141 }
1142
1143 /* return 1 iff sorted tag lists A and B have at least one member in common */
1144 static int tag_intersection(char **a, char **b) {
1145   int cmp;
1146
1147   /* Same sort of logic as trackdb_set() above */
1148   while(*a && *b) {
1149     if(!(cmp = strcmp(*a, *b))) return 1;
1150     else if(cmp < 0) ++a;
1151     else ++b;
1152   }
1153   return 0;
1154 }
1155
1156 /* Check whether a track is suitable for random play.  Returns 0 if it is,
1157  * DB_NOTFOUND if it is not or DB_LOCK_DEADLOCK if the database gave us
1158  * that. */
1159 static int check_suitable(const char *track,
1160                           DB_TXN *tid,
1161                           char **required_tags,
1162                           char **prohibited_tags) {
1163   char **track_tags;
1164   time_t last, now;
1165   struct kvp *p, *t;
1166   const char *pick_at_random, *played_time;
1167
1168   /* don't pick tracks that aren't in any surviving collection (for instance
1169    * you've edited the config but the rescan hasn't done its job yet) */
1170   if(!find_track_root(track)) {
1171     info("found track not in any collection: %s", track);
1172     return DB_NOTFOUND;
1173   }
1174   /* don't pick aliases - only pick the canonical form */
1175   if(gettrackdata(track, &t, &p, 0, 0, tid) == DB_LOCK_DEADLOCK)
1176     return DB_LOCK_DEADLOCK;
1177   if(kvp_get(t, "_alias_for"))
1178     return DB_NOTFOUND;
1179   /* check that random play is not suppressed for this track */
1180   if((pick_at_random = kvp_get(p, "pick_at_random"))
1181      && !strcmp(pick_at_random, "0"))
1182     return DB_NOTFOUND;
1183   /* don't pick a track that's been played in the last 8 hours */
1184   if((played_time = kvp_get(p, "played_time"))) {
1185     last = atoll(played_time);
1186     now = time(0);
1187     if(now < last + 8 * 3600)       /* TODO configurable */
1188       return DB_NOTFOUND;
1189   }
1190   track_tags = parsetags(kvp_get(p, "tags"));
1191   /* check that no prohibited tag is present for this track */
1192   if(prohibited_tags && tag_intersection(track_tags, prohibited_tags))
1193     return DB_NOTFOUND;
1194   /* check that at least one required tags is present for this track */
1195   if(*required_tags && !tag_intersection(track_tags, required_tags))
1196     return DB_NOTFOUND;
1197   return 0;
1198 }
1199
1200 /* attempt to pick a random non-alias track */
1201 const char *trackdb_random(int tries) {
1202   DBT key, data;
1203   DB_BTREE_STAT *sp;
1204   int err, n;
1205   DB_TXN *tid;
1206   const char *track, *candidate;
1207   db_recno_t r;
1208   const char *tags;
1209   char **required_tags, **prohibited_tags, **tp;
1210   hash *h;
1211   DBC *c = 0;
1212
1213   for(;;) {
1214     tid = trackdb_begin_transaction();
1215     if((err = trackdb_get_global_tid("required-tags", tid, &tags)))
1216       goto fail;
1217     required_tags = parsetags(tags);
1218     if((err = trackdb_get_global_tid("prohibited-tags", tid, &tags)))
1219       goto fail;
1220     prohibited_tags = parsetags(tags);
1221     track = 0;
1222     if(*required_tags) {
1223       /* Bung all the suitable tracks into a hash and convert to a list of keys
1224        * (to eliminate duplicates).  We cache this list since it is possible
1225        * that it will be very large. */
1226       if(!reqtracks) {
1227         h = hash_new(0);
1228         for(tp = required_tags; *tp; ++tp) {
1229           c = trackdb_opencursor(trackdb_tagsdb, tid);
1230           memset(&key, 0, sizeof key);
1231           key.data = *tp;
1232           key.size = strlen(*tp);
1233           n = 0;
1234           err = c->c_get(c, &key, prepare_data(&data), DB_SET);
1235           while(err == 0) {
1236             hash_add(h, xstrndup(data.data, data.size), 0,
1237                      HASH_INSERT_OR_REPLACE);
1238             ++n;
1239             err = c->c_get(c, &key, prepare_data(&data), DB_NEXT_DUP);
1240           }
1241           switch(err) {
1242           case 0:
1243           case DB_NOTFOUND:
1244             break;
1245           case DB_LOCK_DEADLOCK:
1246             goto fail;
1247           default:
1248             fatal(0, "error querying tags.db: %s", db_strerror(err));
1249           }
1250           trackdb_closecursor(c);
1251           c = 0;
1252           if(!n)
1253             error(0, "required tag %s does not match any tracks", *tp);
1254         }
1255         nreqtracks = hash_count(h);
1256         reqtracks = hash_keys(h);
1257       }
1258       while(nreqtracks && !track && tries-- > 0) {
1259         r = (rand() * (double)nreqtracks / (RAND_MAX + 1.0));
1260         candidate = reqtracks[r];
1261         switch(check_suitable(candidate, tid,
1262                               required_tags, prohibited_tags)) {
1263         case 0:
1264           track = candidate;
1265           break;
1266         case DB_NOTFOUND:
1267           break;
1268         case DB_LOCK_DEADLOCK:
1269           goto fail;
1270         }
1271       }
1272     } else {
1273       /* No required tags.  We pick random record numbers in the database
1274        * instead. */
1275       switch(err = trackdb_tracksdb->stat(trackdb_tracksdb, tid, &sp,
1276                                           DB_RECORDCOUNT)) {
1277       case 0:
1278         break;
1279       case DB_LOCK_DEADLOCK:
1280         error(0, "error querying tracks.db: %s", db_strerror(err));
1281         goto fail;
1282       default:
1283         fatal(0, "error querying tracks.db: %s", db_strerror(err));
1284       }
1285       if(!sp->bt_nkeys)
1286         error(0, "cannot pick tracks at random from an empty database");
1287       while(sp->bt_nkeys && !track && tries-- > 0) {
1288         /* record numbers count from 1 upwards */
1289         r = 1 + (rand() * (double)sp->bt_nkeys / (RAND_MAX + 1.0));
1290         memset(&key, sizeof key, 0);
1291         key.flags = DB_DBT_MALLOC;
1292         key.size = sizeof r;
1293         key.data = &r;
1294         switch(err = trackdb_tracksdb->get(trackdb_tracksdb, tid, &key, prepare_data(&data),
1295                                            DB_SET_RECNO)) {
1296         case 0:
1297           break;
1298         case DB_LOCK_DEADLOCK:
1299           error(0, "error querying tracks.db: %s", db_strerror(err));
1300           goto fail;
1301         default:
1302           fatal(0, "error querying tracks.db: %s", db_strerror(err));
1303         }
1304         candidate = xstrndup(key.data, key.size);
1305         switch(check_suitable(candidate, tid,
1306                               required_tags, prohibited_tags)) {
1307         case 0:
1308           track = candidate;
1309           break;
1310         case DB_NOTFOUND:
1311           break;
1312         case DB_LOCK_DEADLOCK:
1313           goto fail;
1314         }
1315       }
1316     }
1317     break;
1318 fail:
1319     trackdb_closecursor(c);
1320     c = 0;
1321     trackdb_abort_transaction(tid);
1322   }
1323   trackdb_commit_transaction(tid);
1324   if(!track)
1325     error(0, "could not pick a random track");
1326   return track;
1327 }
1328
1329 /* get a track name given the prefs.  Set *used_db to 1 if we got the answer
1330  * from the prefs. */
1331 static const char *getpart(const char *track,
1332                            const char *context,
1333                            const char *part,
1334                            const struct kvp *p,
1335                            int *used_db) {
1336   const char *result;
1337   char *pref;
1338
1339   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1340   if((result = kvp_get(p, pref)))
1341     *used_db = 1;
1342   else
1343     result = trackname_part(track, context, part);
1344   assert(result != 0);
1345   return result;
1346 }
1347
1348 /* get a track name part, like trackname_part(), but taking the database into
1349  * account. */
1350 const char *trackdb_getpart(const char *track,
1351                             const char *context,
1352                             const char *part) {
1353   struct kvp *p;
1354   DB_TXN *tid;
1355   char *pref;
1356   const char *actual;
1357   int used_db, err;
1358
1359   /* construct the full pref */
1360   byte_xasprintf(&pref, "trackname_%s_%s", context, part);
1361   for(;;) {
1362     tid = trackdb_begin_transaction();
1363     if((err = gettrackdata(track, 0, &p, &actual, 0, tid)) == DB_LOCK_DEADLOCK)
1364       goto fail;
1365     break;
1366 fail:
1367     trackdb_abort_transaction(tid);
1368   }
1369   trackdb_commit_transaction(tid);
1370   return getpart(actual, context, part, p, &used_db);
1371 }
1372
1373 /* get the raw path name for @track@ (might be an alias) */
1374 const char *trackdb_rawpath(const char *track) {
1375   DB_TXN *tid;
1376   struct kvp *t;
1377   const char *path;
1378
1379   for(;;) {
1380     tid = trackdb_begin_transaction();
1381     if(gettrackdata(track, &t, 0, 0, 0, tid) == DB_LOCK_DEADLOCK)
1382       goto fail;
1383     break;
1384 fail:
1385     trackdb_abort_transaction(tid);
1386   }
1387   trackdb_commit_transaction(tid);
1388   if(!(path = kvp_get(t, "_path"))) path = track;
1389   return path;
1390 }
1391
1392 /* trackdb_list **************************************************************/
1393
1394 /* this is incredibly ugly, sorry, perhaps it will be rewritten to be actually
1395  * readable at some point */
1396
1397 /* return true if the basename of TRACK[0..TL-1], as defined by DL, matches RE.
1398  * If RE is a null pointer then it matches everything. */
1399 static int track_matches(size_t dl, const char *track, size_t tl,
1400                          const pcre *re) {
1401   int ovec[3], rc;
1402
1403   if(!re)
1404     return 1;
1405   track += dl + 1;
1406   tl -= (dl + 1);
1407   switch(rc = pcre_exec(re, 0, track, tl, 0, 0, ovec, 3)) {
1408   case PCRE_ERROR_NOMATCH: return 0;
1409   default:
1410     if(rc < 0) {
1411       error(0, "pcre_exec returned %d, subject '%s'", rc, track);
1412       return 0;
1413     }
1414     return 1;
1415   }
1416 }
1417
1418 static int do_list(struct vector *v, const char *dir,
1419                    enum trackdb_listable what, const pcre *re, DB_TXN *tid) {
1420   DBC *cursor;
1421   DBT k, d;
1422   size_t dl;
1423   char *ptr;
1424   int err;
1425   size_t l, last_dir_len = 0;
1426   char *last_dir = 0, *track, *alias;
1427   struct kvp *p;
1428   
1429   dl = strlen(dir);
1430   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1431   make_key(&k, dir);
1432   prepare_data(&d);
1433   /* find the first key >= dir */
1434   err = cursor->c_get(cursor, &k, &d, DB_SET_RANGE);
1435   /* keep going while we're dealing with <dir/anything> */
1436   while(err == 0
1437         && k.size > dl
1438         && ((char *)k.data)[dl] == '/'
1439         && !memcmp(k.data, dir, dl)) {
1440     ptr = memchr((char *)k.data + dl + 1, '/', k.size - (dl + 1));
1441     if(ptr) {
1442       /* we have <dir/component/anything>, so <dir/component> is a directory */
1443       l = ptr - (char *)k.data;
1444       if(what & trackdb_directories)
1445         if(!(last_dir
1446              && l == last_dir_len
1447              && !memcmp(last_dir, k.data, l))) {
1448           last_dir = xstrndup(k.data, last_dir_len = l);
1449           if(track_matches(dl, k.data, l, re))
1450             vector_append(v, last_dir);
1451         }
1452     } else {
1453       /* found a plain file */
1454       if((what & trackdb_files)) {
1455         track = xstrndup(k.data, k.size);
1456         if((err = trackdb_getdata(trackdb_prefsdb,
1457                                   track, &p, tid)) == DB_LOCK_DEADLOCK)
1458           goto deadlocked;
1459         /* if this file has an alias in the same directory then we skip it */
1460         if((err = compute_alias(&alias, track, p, tid)))
1461           goto deadlocked;
1462         if(!(alias && !strcmp(d_dirname(alias), d_dirname(track))))
1463           if(track_matches(dl, k.data, k.size, re))
1464             vector_append(v, track);
1465       }
1466     }
1467     err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1468   }
1469   switch(err) {
1470   case 0:
1471     break;
1472   case DB_NOTFOUND:
1473     err = 0;
1474     break;
1475   case DB_LOCK_DEADLOCK:
1476     error(0, "error querying database: %s", db_strerror(err));
1477     break;
1478   default:
1479     fatal(0, "error querying database: %s", db_strerror(err));
1480   }
1481 deadlocked:
1482   if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1483   return err;
1484 }
1485
1486 /* return the directories or files below @dir@ */
1487 char **trackdb_list(const char *dir, int *np, enum trackdb_listable what,
1488                     const pcre *re) {
1489   DB_TXN *tid;
1490   int n;
1491   struct vector v;
1492
1493   vector_init(&v);
1494   for(;;) {
1495     tid = trackdb_begin_transaction();
1496     v.nvec = 0;
1497     if(dir) {
1498       if(do_list(&v, dir, what, re, tid))
1499         goto fail;
1500     } else {
1501       for(n = 0; n < config->collection.n; ++n)
1502         if(do_list(&v, config->collection.s[n].root, what, re, tid))
1503           goto fail;
1504     }
1505     break;
1506 fail:
1507     trackdb_abort_transaction(tid);
1508   }
1509   trackdb_commit_transaction(tid);
1510   vector_terminate(&v);
1511   if(np)
1512     *np = v.nvec;
1513   return v.vec;
1514 }  
1515
1516 /* If S is tag:something, return something.  Else return 0. */
1517 static const char *checktag(const char *s) {
1518   if(!strncmp(s, "tag:", 4))
1519     return s + 4;
1520   else
1521     return 0;
1522 }
1523
1524 /* return a list of tracks containing all of the words given.  If you
1525  * ask for only stopwords you get no tracks. */
1526 char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) {
1527   const char **w, *best = 0, *tag;
1528   char **twords, **tags;
1529   int i, j, n, err, what;
1530   DBC *cursor = 0;
1531   DBT k, d;
1532   struct vector u, v;
1533   DB_TXN *tid;
1534   struct kvp *p;
1535   int ntags = 0;
1536   DB *db;
1537   const char *dbname;
1538
1539   *ntracks = 0;                         /* for early returns */
1540   /* casefold all the words */
1541   w = xmalloc(nwordlist * sizeof (char *));
1542   for(n = 0; n < nwordlist; ++n) {
1543     w[n] = casefold(wordlist[n]);
1544     if(checktag(w[n])) ++ntags;         /* count up tags */
1545   }
1546   /* find the longest non-stopword */
1547   for(n = 0; n < nwordlist; ++n)
1548     if(!stopword(w[n]) && !checktag(w[n]))
1549       if(!best || strlen(w[n]) > strlen(best))
1550         best = w[n];
1551   /* TODO: we should at least in principal be able to identify the word or tag
1552    * with the least matches in log time, and choose that as our primary search
1553    * term. */
1554   if(ntags && !best) {
1555     /* Only tags are listed.  We limit to the first and narrow down with the
1556      * rest. */
1557     best = checktag(w[0]);
1558     db = trackdb_tagsdb;
1559     dbname = "tags";
1560   } else if(best) {
1561     /* We can limit to some word. */
1562     db = trackdb_searchdb;
1563     dbname = "search";
1564   } else {
1565     /* Only stopwords */
1566     return 0;
1567   }
1568   vector_init(&u);
1569   vector_init(&v);
1570   for(;;) {
1571     tid = trackdb_begin_transaction();
1572     /* find all the tracks that have that word */
1573     make_key(&k, best);
1574     prepare_data(&d);
1575     what = DB_SET;
1576     v.nvec = 0;
1577     cursor = trackdb_opencursor(db, tid);
1578     while(!(err = cursor->c_get(cursor, &k, &d, what))) {
1579       vector_append(&v, xstrndup(d.data, d.size));
1580       what = DB_NEXT_DUP;
1581     }
1582     switch(err) {
1583     case DB_NOTFOUND:
1584       err = 0;
1585       break;
1586     case DB_LOCK_DEADLOCK:
1587       error(0, "error querying %s database: %s", dbname, db_strerror(err));
1588       break;
1589     default:
1590       fatal(0, "error querying %s database: %s", dbname, db_strerror(err));
1591     }
1592     if(trackdb_closecursor(cursor)) err = DB_LOCK_DEADLOCK;
1593     cursor = 0;
1594     /* do a naive search over that (hopefuly fairly small) list of tracks */
1595     u.nvec = 0;
1596     for(n = 0; n < v.nvec; ++n) {
1597       if((err = gettrackdata(v.vec[n], 0, &p, 0, 0, tid) == DB_LOCK_DEADLOCK))
1598         goto fail;
1599       else if(err) {
1600         error(0, "track %s unexpected error: %s", v.vec[n], db_strerror(err));
1601         continue;
1602       }
1603       twords = track_to_words(v.vec[n], p);
1604       tags = parsetags(kvp_get(p, "tags"));
1605       for(i = 0; i < nwordlist; ++i) {
1606         if((tag = checktag(w[i]))) {
1607           /* Track must have this tag */
1608           for(j = 0; tags[j]; ++j)
1609             if(!strcmp(tag, tags[j])) break; /* tag found */
1610           if(!tags[j]) break;           /* tag not found */
1611         } else {
1612           /* Track must contain this word */
1613           for(j = 0; twords[j]; ++j)
1614             if(!strcmp(w[i], twords[j])) break; /* word found */
1615           if(!twords[j]) break;         /* word not found */
1616         }
1617       }
1618       if(i >= nwordlist)                /* all words found */
1619         vector_append(&u, v.vec[n]);
1620     }
1621     break;
1622   fail:
1623     trackdb_closecursor(cursor);
1624     cursor = 0;
1625     trackdb_abort_transaction(tid);
1626     info("retrying search");
1627   }
1628   trackdb_commit_transaction(tid);
1629   vector_terminate(&u);
1630   if(ntracks)
1631     *ntracks = u.nvec;
1632   return u.vec;
1633 }
1634
1635 /* trackdb_scan **************************************************************/
1636
1637 int trackdb_scan(const char *root,
1638                  int (*callback)(const char *track,
1639                                  struct kvp *data,
1640                                  void *u,
1641                                  DB_TXN *tid),
1642                  void *u,
1643                  DB_TXN *tid) {
1644   DBC *cursor;
1645   DBT k, d;
1646   size_t root_len = strlen(root);
1647   int err;
1648   struct kvp *data;
1649
1650   cursor = trackdb_opencursor(trackdb_tracksdb, tid);
1651   err = cursor->c_get(cursor, make_key(&k, root), prepare_data(&d),
1652                       DB_SET_RANGE);
1653   while(!err) {
1654     if(k.size > root_len
1655        && !strncmp(k.data, root, root_len)
1656        && ((char *)k.data)[root_len] == '/') {
1657       data = kvp_urldecode(d.data, d.size);
1658       if(kvp_get(data, "_path"))
1659         if((err = callback(xstrndup(k.data, k.size), data, u, tid)))
1660           break;
1661       err = cursor->c_get(cursor, &k, &d, DB_NEXT);
1662     } else
1663       break;
1664   }
1665   trackdb_closecursor(cursor);
1666   switch(err) {
1667   case EINTR:
1668     return err;
1669   case 0:
1670   case DB_NOTFOUND:
1671     return 0;
1672   case DB_LOCK_DEADLOCK:
1673     error(0, "c->c_get: %s", db_strerror(err));
1674     return err;
1675   default:
1676     fatal(0, "c->c_get: %s", db_strerror(err));
1677   }
1678 }
1679
1680 /* trackdb_rescan ************************************************************/
1681
1682 /* called when the rescanner terminates */
1683 static int reap_rescan(ev_source attribute((unused)) *ev,
1684                        pid_t pid,
1685                        int status,
1686                        const struct rusage attribute((unused)) *rusage,
1687                        void attribute((unused)) *u) {
1688   if(pid == rescan_pid) rescan_pid = -1;
1689   if(status)
1690     error(0, "disorderd-rescan: %s", wstat(status));
1691   else
1692     D(("disorderd-rescan terminate: %s", wstat(status)));
1693   /* Our cache of file lookups is out of date now */
1694   cache_clean(&cache_files_type);
1695   return 0;
1696 }
1697
1698 void trackdb_rescan(ev_source *ev) {
1699   if(rescan_pid != -1) {
1700     error(0, "rescan already underway");
1701     return;
1702   }
1703   rescan_pid = subprogram(ev, RESCAN);
1704   ev_child(ev, rescan_pid, 0, reap_rescan, 0);
1705   D(("started rescanner"));
1706   
1707 }
1708
1709 int trackdb_rescan_cancel(void) {
1710   if(rescan_pid == -1) return 0;
1711   if(kill(rescan_pid, SIGTERM) < 0)
1712     fatal(errno, "error killing rescanner");
1713   rescan_pid = -1;
1714   return 1;
1715 }
1716
1717 /* global prefs **************************************************************/
1718
1719 void trackdb_set_global(const char *name,
1720                         const char *value,
1721                         const char *who) {
1722   DB_TXN *tid;
1723   DBT k, d;
1724   int err;
1725   int state;
1726
1727   memset(&k, 0, sizeof k);
1728   memset(&d, 0, sizeof d);
1729   k.data = (void *)name;
1730   k.size = strlen(name);
1731   if(value) {
1732     d.data = (void *)value;
1733     d.size = strlen(value);
1734   }
1735   for(;;) {
1736     tid = trackdb_begin_transaction();
1737     if(value)
1738       err = trackdb_globaldb->put(trackdb_globaldb, tid, &k, &d, 0);
1739     else
1740       err = trackdb_globaldb->del(trackdb_globaldb, tid, &k, 0);
1741     if(!err || err == DB_NOTFOUND) break;
1742     if(err != DB_LOCK_DEADLOCK)
1743       fatal(0, "error updating database: %s", db_strerror(err));
1744     trackdb_abort_transaction(tid);
1745   }
1746   trackdb_commit_transaction(tid);
1747   /* log important state changes */
1748   if(!strcmp(name, "playing")) {
1749     state = !value || !strcmp(value, "yes");
1750     info("playing %s by %s",
1751          state ? "enabled" : "disabled",
1752          who ? who : "-");
1753     eventlog("state", state ? "enable_play" : "disable_play", (char *)0);
1754   }
1755   if(!strcmp(name, "random-play")) {
1756     state = !value || !strcmp(value, "yes");
1757     info("random play %s by %s",
1758          state ? "enabled" : "disabled",
1759          who ? who : "-");
1760     eventlog("state", state ? "enable_random" : "disable_random", (char *)0);
1761   }
1762   if(!strcmp(name, "required-tags"))
1763     reqtracks = 0;
1764 }
1765
1766 const char *trackdb_get_global(const char *name) {
1767   DB_TXN *tid;
1768   int err;
1769   const char *r;
1770
1771   for(;;) {
1772     tid = trackdb_begin_transaction();
1773     if(!(err = trackdb_get_global_tid(name, tid, &r)))
1774       break;
1775     trackdb_abort_transaction(tid);
1776   }
1777   trackdb_commit_transaction(tid);
1778   return r;
1779 }
1780
1781 static int trackdb_get_global_tid(const char *name,
1782                                   DB_TXN *tid,
1783                                   const char **rp) {
1784   DBT k, d;
1785   int err;
1786
1787   memset(&k, 0, sizeof k);
1788   k.data = (void *)name;
1789   k.size = strlen(name);
1790   switch(err = trackdb_globaldb->get(trackdb_globaldb, tid, &k,
1791                                      prepare_data(&d), 0)) {
1792   case 0:
1793     *rp = xstrndup(d.data, d.size);
1794     return 0;
1795   case DB_NOTFOUND:
1796     *rp = 0;
1797     return 0;
1798   case DB_LOCK_DEADLOCK:
1799     return err;
1800   default:
1801     fatal(0, "error updating database: %s", db_strerror(err));
1802   }
1803 }
1804
1805 /* tidying up ****************************************************************/
1806
1807 void trackdb_gc(void) {
1808   int err;
1809   char **logfiles;
1810
1811   if((err = trackdb_env->txn_checkpoint(trackdb_env,
1812                                         config->checkpoint_kbyte,
1813                                         config->checkpoint_min,
1814                                         0)))
1815     fatal(0, "trackdb_env->txn_checkpoint: %s", db_strerror(err));
1816   if((err = trackdb_env->log_archive(trackdb_env, &logfiles, DB_ARCH_REMOVE)))
1817     fatal(0, "trackdb_env->log_archive: %s", db_strerror(err));
1818   /* This makes catastrophic recovery impossible.  However, the user can still
1819    * preserve the important data by using disorder-dump to snapshot their
1820    * prefs, and later to restore it.  This is likely to have much small
1821    * long-term storage requirements than record the db logfiles. */
1822 }
1823
1824 /*
1825 Local Variables:
1826 c-basic-offset:2
1827 comment-column:40
1828 fill-column:79
1829 indent-tabs-mode:nil
1830 End:
1831 */