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