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