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