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