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