* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file server/trackdb.c
+ * @brief Track database */
#include <config.h>
#include "types.h"
#include <sys/resource.h>
#include <time.h>
#include <arpa/inet.h>
+#include <sys/wait.h>
#include "event.h"
#include "mem.h"
#include "configuration.h"
#include "syscalls.h"
#include "wstat.h"
-#include "words.h"
#include "printf.h"
#include "filepart.h"
#include "trackname.h"
#include "cache.h"
#include "eventlog.h"
#include "hash.h"
+#include "unicode.h"
+#include "unidata.h"
#define RESCAN "disorder-rescan"
#define DEADLOCK "disorder-deadlock"
return compare_path_raw(a->data, a->size, b->data, b->size);
}
-/* open environment */
-void trackdb_init(int recover) {
+/** @brief Open database environment
+ * @param flags Flags word
+ *
+ * Flags should be one of:
+ * - @ref TRACKDB_NO_RECOVER
+ * - @ref TRACKDB_NORMAL_RECOVER
+ * - @ref TRACKDB_FATAL_RECOVER
+ */
+void trackdb_init(int flags) {
int err;
+ const int recover = flags & TRACKDB_RECOVER_MASK;
static int recover_type[] = { 0, DB_RECOVER, DB_RECOVER_FATAL };
/* sanity checks */
/* If we're in the background then trap subprocess stdout/stderr */
if(!(pid = xfork())) {
exitfn = _exit;
- ev_signal_atfork(ev);
+ if(ev)
+ ev_signal_atfork(ev);
signal(SIGPIPE, SIG_DFL);
if(outputfd != -1) {
xdup2(outputfd, 1);
return db;
}
-/* open track databases */
-void trackdb_open(void) {
+/** @brief Open track databases
+ * @param Flags flags word
+ *
+ * @p flags should be one of:
+ * - @p TRACKDB_NO_UPGRADE, if no upgrade should be attempted
+ * - @p TRACKDB_CAN_UPGRADE, if an upgrade may be attempted
+ * - @p TRACKDB_OPEN_FOR_UPGRADE, if this is disorder-dbupgrade
+ */
+void trackdb_open(int flags) {
int newdb, err;
+ pid_t pid;
/* sanity checks */
assert(opened == 0);
trackdb_globaldb = open_db("global.db", 0, DB_HASH, 0, 0666);
if(trackdb_globaldb) {
/* This is an existing database */
- const char *oldversion;
-
- oldversion = trackdb_get_global("_dbversion");
- if(!oldversion)
- oldversion = "1.x";
- if(strcmp(oldversion, DBVERSION)) {
- /* This database needs upgrading. This isn't implemented yet so we just
- * fail. */
- fatal(0, "database needs upgrading from %s to %s", oldversion, DBVERSION);
- }
- newdb = 0;
+ const char *s;
+ long oldversion;
+
+ s = trackdb_get_global("_dbversion");
/* Close the database again, we'll open it property below */
if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
fatal(0, "error closing global.db: %s", db_strerror(err));
trackdb_globaldb = 0;
+ /* Convert version string to an integer */
+ oldversion = s ? atol(s) : 1;
+ if(oldversion > config->dbversion) {
+ /* Database is from the future; we never allow this. */
+ fatal(0, "this version of DisOrder is too old for database version %ld",
+ oldversion);
+ }
+ if(oldversion < config->dbversion) {
+ /* Database version is out of date */
+ switch(flags & TRACKDB_UPGRADE_MASK) {
+ case TRACKDB_NO_UPGRADE:
+ /* This database needs upgrading but this is not permitted */
+ fatal(0, "database needs upgrading from %ld to %ld",
+ oldversion, config->dbversion);
+ case TRACKDB_CAN_UPGRADE:
+ /* This database needs upgrading */
+ info("invoking disorder-dbupgrade to upgrade from %ld to %ld",
+ oldversion, config->dbversion);
+ pid = subprogram(0, "disorder-dbupgrade", -1);
+ while(waitpid(pid, &err, 0) == -1 && errno == EINTR)
+ ;
+ if(err)
+ fatal(0, "disorder-dbupgrade %s", wstat(err));
+ info("disorder-dbupgrade succeeded");
+ break;
+ case TRACKDB_OPEN_FOR_UPGRADE:
+ break;
+ default:
+ abort();
+ }
+ }
+ if(oldversion == config->dbversion && (flags & TRACKDB_OPEN_FOR_UPGRADE)) {
+ /* This doesn't make any sense */
+ fatal(0, "database is already at current version");
+ }
+ newdb = 0;
} else {
+ if(flags & TRACKDB_OPEN_FOR_UPGRADE) {
+ /* Cannot upgrade a new database */
+ fatal(0, "cannot upgrade a database that does not exist");
+ }
/* This is a brand new database */
newdb = 1;
}
trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_CREATE, 0666);
trackdb_noticeddb = open_db("noticed.db",
DB_DUPSORT, DB_BTREE, DB_CREATE, 0666);
- /* Stash the database version */
- if(newdb)
- trackdb_set_global("_dbversion", DBVERSION, 0);
+ if(newdb) {
+ /* Stash the database version */
+ char buf[32];
+
+ assert(!(flags & TRACKDB_OPEN_FOR_UPGRADE));
+ snprintf(buf, sizeof buf, "%ld", config->dbversion);
+ trackdb_set_global("_dbversion", buf, 0);
+ }
D(("opened databases"));
}
return !strncmp(name, prefix, (sizeof prefix) - 1);
}
+/** @brief Word_Break property tailor that treats underscores as spaces */
+static int tailor_underscore_Word_Break_Other(uint32_t c) {
+ switch(c) {
+ default:
+ return -1;
+ case 0x005F: /* LOW LINE (SPACING UNDERSCORE) */
+ return unicode_Word_Break_Other;
+ }
+}
+
+/** @brief Remove all combining characters in-place
+ * @param s Pointer to start of string
+ * @param ns Length of string
+ * @return New, possiblby reduced, length
+ */
+static size_t remove_combining_chars(uint32_t *s, size_t ns) {
+ uint32_t *start = s, *t = s, *end = s + ns;
+
+ while(s < end) {
+ const uint32_t c = *s++;
+ if(!utf32_combining_class(c))
+ *t++ = c;
+ }
+ return t - start;
+}
+
+/** @brief Normalize and split a string using a given tailoring */
+static void word_split(struct vector *v,
+ const char *s,
+ unicode_property_tailor *pt) {
+ size_t nw, nt32, i;
+ uint32_t *t32, **w32;
+
+ /* Convert to UTF-32 */
+ if(!(t32 = utf8_to_utf32(s, strlen(s), &nt32)))
+ return;
+ /* Erase case distinctions */
+ if(!(t32 = utf32_casefold_compat(t32, nt32, &nt32)))
+ return;
+ /* Drop combining characters */
+ nt32 = remove_combining_chars(t32, nt32);
+ /* Split into words, treating _ as a space */
+ w32 = utf32_word_split(t32, nt32, &nw, pt);
+ /* Convert words back to UTF-8 and append to result */
+ for(i = 0; i < nw; ++i)
+ vector_append(v, utf32_to_utf8(w32[i], utf32_len(w32[i]), 0));
+}
+
/* compute the words of a track name */
static char **track_to_words(const char *track,
const struct kvp *p) {
struct vector v;
- char **w;
- int nw;
const char *rootless = track_rootless(track);
if(!rootless)
rootless = track; /* bodge */
vector_init(&v);
- if((w = words(casefold(strip_extension(rootless)), &nw)))
- vector_append_many(&v, w, nw);
-
+ rootless = strip_extension(rootless);
+ word_split(&v, strip_extension(rootless), tailor_underscore_Word_Break_Other);
for(; p; p = p->next)
if(is_display_pref(p->name))
- if((w = words(casefold(p->value), &nw)))
- vector_append_many(&v, w, nw);
+ word_split(&v, p->value, 0);
vector_terminate(&v);
return dedupe(v.vec, v.nvec);
}
const char *dbname;
*ntracks = 0; /* for early returns */
- /* casefold all the words */
+ /* normalize all the words */
w = xmalloc(nwordlist * sizeof (char *));
for(n = 0; n < nwordlist; ++n) {
- w[n] = casefold(wordlist[n]);
+ uint32_t *w32;
+ size_t nw32;
+
+ w[n] = utf8_casefold_compat(wordlist[n], strlen(wordlist[n]), 0);
if(checktag(w[n])) ++ntags; /* count up tags */
+ /* Strip out combining characters (AFTER checking whether it's a tag) */
+ if(!(w32 = utf8_to_utf32(w[n], strlen(w[n]), &nw32)))
+ return 0;
+ nw32 = remove_combining_chars(w32, nw32);
+ if(!(w[n] = utf32_to_utf8(w32, nw32, 0)))
+ return 0;
}
/* find the longest non-stopword */
for(n = 0; n < nwordlist; ++n)
void attribute((unused)) *u) {
if(pid == rescan_pid) rescan_pid = -1;
if(status)
- error(0, "disorderd-rescan: %s", wstat(status));
+ error(0, RESCAN": %s", wstat(status));
else
- D(("disorderd-rescan terminate: %s", wstat(status)));
+ D((RESCAN" terminated: %s", wstat(status)));
/* Our cache of file lookups is out of date now */
cache_clean(&cache_files_type);
eventlog("rescanned", (char *)0);
}
void trackdb_rescan(ev_source *ev) {
+ int w;
+
if(rescan_pid != -1) {
error(0, "rescan already underway");
return;
}
rescan_pid = subprogram(ev, RESCAN, -1);
- ev_child(ev, rescan_pid, 0, reap_rescan, 0);
- D(("started rescanner"));
-
+ if(ev) {
+ ev_child(ev, rescan_pid, 0, reap_rescan, 0);
+ D(("started rescanner"));
+ } else {
+ /* This is the first rescan, we block until it is complete */
+ while(waitpid(rescan_pid, &w, 0) < 0 && errno == EINTR)
+ ;
+ reap_rescan(0, rescan_pid, w, 0, 0);
+ }
}
int trackdb_rescan_cancel(void) {