chiark / gitweb /
Spot changes to database parameters and rebuild database.
authorRichard Kettlewell <rjk@greenend.org.uk>
Mon, 13 Apr 2009 16:30:26 +0000 (17:30 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Mon, 13 Apr 2009 16:30:26 +0000 (17:30 +0100)
This change stores a hash of the current database parameters, i.e.
options that affect alias construction, the search database, etc in the
database.  If it is missing or changes then aliases and the search
database are deleted and recomputed.

README.reload
server/Makefile.am
server/dbparams.c [new file with mode: 0644]
server/disorder-server.h
server/disorderd.c

index 23fad2c6f72a33a4e7ffe8efaa8042a969b4c2b8..f9fa4c7ad24a0e04089259e812fa2521f9189957 100644 (file)
@@ -30,6 +30,16 @@ The search database will have to be rebuilt from scratch.
 
 All the files will be owned by the wrong user!
 
 
 All the files will be owned by the wrong user!
 
+** Alias, search database, etc
+
+Rescan can regenerate aliases and the search and tag databases but we
+rather assume that they are either empty or good.  Therefore we need
+to store anything that can affect these values and erase them if they
+change.
+
+The solution is a global pref _dbparams which contains the hash of the
+alias, stopword and namepart data.
+
 * Options that must remain the same across reload
 
 Some things will just require a restart.  We should either enforce
 * Options that must remain the same across reload
 
 Some things will just require a restart.  We should either enforce
index 5d089d79708386e8f6a853146005d3ca87f785a1..44c1f4d3f28369777bc0325a1cef23f24a5799cd 100644 (file)
@@ -25,7 +25,8 @@ AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib
 
 disorderd_SOURCES=disorderd.c api.c api-server.c daemonize.c play.c    \
        server.c server-queue.c queue-ops.c state.c plugin.c            \
 
 disorderd_SOURCES=disorderd.c api.c api-server.c daemonize.c play.c    \
        server.c server-queue.c queue-ops.c state.c plugin.c            \
-       schedule.c exports.c ../lib/memgc.c disorder-server.h
+       schedule.c dbparams.c \
+       exports.c ../lib/memgc.c disorder-server.h
 disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
        $(LIBASOUND) $(COREAUDIO)
 disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
        $(LIBASOUND) $(COREAUDIO)
diff --git a/server/dbparams.c b/server/dbparams.c
new file mode 100644 (file)
index 0000000..60ba4a8
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2009 Richard Kettlewell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+/** @file server/dbparams.c
+ * @brief Parameters affecting the database
+ *
+ * Rescan can regenerate aliases and the search and tag databases but
+ * we rather assume that they are either empty or good.  Therefore we
+ * need to store anything that can affect these values and erase them
+ * if they change.
+ *
+ * The solution is a global pref _dbparams which contains the hash of
+ * the alias, stopword and namepart data.
+ */
+#include "disorder-server.h"
+#include <gcrypt.h>
+
+static int dbparams_cleanup(DB_TXN *tid);
+static void h_write_string(gcrypt_hash_handle h,
+                           const char *s);
+static char *compute_dbparams(void);
+
+/** @brief Check whether database parameters have changed
+ *
+ * If the database parameters have changed then deletes the search and
+ * tag database contents and all aliases.  The subsequent rescan will
+ * regenerate them.
+ */
+void dbparams_check(void) {
+  const char *newparams = compute_dbparams();
+  const char *oldparams = trackdb_get_global("_dbparams");
+
+  // If the parameters match, return straight away
+  if(oldparams && !strcmp(newparams, oldparams))
+    return;
+  // Log what we're going to do
+  for(;;) {
+    DB_TXN *tid;
+    if(oldparams)
+      info("database parameter string changed from %s to %s - removing old data",
+           oldparams, newparams);
+    else {
+      info("new database parameter string %s - removing old data",
+           newparams);
+      /* This is a slightly annoying case; the global pref wasn't present.  In
+       * practice this is almost certainly either an upgrade (with no change to
+       * any relevant parameters) or a new installation (with no tracks).
+       *
+       * The new installation case doesn't matter much; clearing an empty
+       * search database and iterating over a likewise track database won't
+       * take long.
+       *
+       * However for upgrade this will throw away a lot of data and laboriously
+       * regenerate it, which is rather a shame.
+       */
+    }
+    tid = trackdb_begin_transaction();
+    int err = dbparams_cleanup(tid);
+    if(!err)
+      err = trackdb_set_global_tid("_dbparams", newparams, tid);
+    switch(err) {
+    case 0:
+      trackdb_commit_transaction(tid);
+      info("removed old data OK, will regenerate on rescan");
+      return;
+    case DB_LOCK_DEADLOCK:
+      /* Deadlocked, try again */
+      trackdb_abort_transaction(tid);
+      break;
+    default:
+      fatal(0, "error updating database: %s", db_strerror(err));
+    }
+  }
+}
+
+/** @brief Clean up databases */
+static int dbparams_cleanup(DB_TXN *tid) {
+  int err;
+  u_int32_t count;
+
+  /* We'll regenerate search.db based on the new set of stopwords */
+  if((err = trackdb_searchdb->truncate(trackdb_searchdb, tid, &count, 0))) {
+    error(err, "truncating search.db: %s", db_strerror(err));
+    return err;
+  }
+  /* We'll regenerate aliases based on the new alias/namepart settings, so
+   * delete all the alias records currently present
+   *
+   * TODO this looks suspiciously similar to part of dump.c
+   */
+  DBC *cursor;
+  DBT k, d;
+  cursor = trackdb_opencursor(trackdb_tracksdb, tid);
+  if((err = cursor->c_get(cursor, prepare_data(&k), prepare_data(&d),
+                          DB_FIRST)) == DB_LOCK_DEADLOCK) {
+    error(0, "cursor->c_get: %s", db_strerror(err));
+    goto done;
+  }
+  while(err == 0) {
+    struct kvp *data = kvp_urldecode(d.data, d.size);
+    if(kvp_get(data, "_alias_for")) {
+      if((err = cursor->c_del(cursor, 0))) {
+        error(0, "cursor->c_del: %s", db_strerror(err));
+        goto done;
+      }
+    }
+    err = cursor->c_get(cursor, prepare_data(&k), prepare_data(&d), DB_NEXT);
+  }
+  if(err == DB_LOCK_DEADLOCK) {
+    error(0, "cursor operation: %s", db_strerror(err));
+    goto done;
+  }
+  if(err != DB_NOTFOUND) 
+    fatal(0, "cursor->c_get: %s", db_strerror(err));
+  err = 0;
+done:
+  if(trackdb_closecursor(cursor) && !err) err = DB_LOCK_DEADLOCK;
+  return err;
+}
+
+/** @brief Write a string into a gcrypt hash function
+ * @param h Hash handle
+ * @param s String to write
+ *
+ * The 0 terminator is included in the byte written.
+ */
+static void h_write_string(gcrypt_hash_handle h,
+                           const char *s) {
+  gcry_md_write(h, s, strlen(s) + 1);
+}
+
+/** @brief Compute database parameters hash
+ * @return Opaque string encapsulating database parameters
+ */
+static char *compute_dbparams(void) {
+  gcry_error_t e;
+  gcrypt_hash_handle h;
+
+  if((e = gcry_md_open(&h, GCRY_MD_SHA256, 0)))
+    fatal(0, "gcry_md_open: %s", gcry_strerror(e));
+  h_write_string(h, "alias");
+  h_write_string(h, config->alias);
+  for(int n = 0; n < config->stopword.n; ++n) {
+    h_write_string(h, "stopword");
+    h_write_string(h, config->stopword.s[n]);
+  }
+  for(int n = 0; n < config->namepart.n; ++n) {
+    h_write_string(h, "namepart");
+    h_write_string(h, config->namepart.s[n].part);
+    h_write_string(h, config->namepart.s[n].res);
+    h_write_string(h, config->namepart.s[n].replace);
+    h_write_string(h, config->namepart.s[n].context);
+    char buffer[64];
+    snprintf(buffer, sizeof buffer, "%u", config->namepart.s[n].reflags);
+    h_write_string(h, buffer);
+  }
+  char *result;
+  byte_xasprintf(&result, "dbparams-0-sha256:%s", 
+                 hex(gcry_md_read(h, GCRY_MD_SHA256), 
+                     gcry_md_get_algo_dlen(GCRY_MD_SHA256)));
+  gcry_md_close(h);
+  return result;
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 6d04c2646fc3f66a841851e4de52b6a4aa66c6e3..8209adb9ddf27085c734de7ae9c977183e9a51a2 100644 (file)
@@ -102,6 +102,8 @@ void quit(ev_source *ev) attribute((noreturn));
 int reconfigure(ev_source *ev, int reload);
 /* reconfigure.  If @reload@ is nonzero, update the configuration. */
 
 int reconfigure(ev_source *ev, int reload);
 /* reconfigure.  If @reload@ is nonzero, update the configuration. */
 
+void dbparams_check(void);
+
 extern struct queue_entry qhead;
 /* queue of things yet to be played.  the head will be played
  * soonest. */
 extern struct queue_entry qhead;
 /* queue of things yet to be played.  the head will be played
  * soonest. */
index 22037393eeeb38bb6f8380797c0939a23ea8b5e4..a4af66b1a2fa5a6cc1026e5d61b1fd1001026e40 100644 (file)
@@ -276,6 +276,8 @@ int main(int argc, char **argv) {
   trackdb_old_users();
   /* create a root login */
   trackdb_create_root();
   trackdb_old_users();
   /* create a root login */
   trackdb_create_root();
+  /* check for change to database parameters */
+  dbparams_check();
   /* re-read config if we receive a SIGHUP */
   if(ev_signal(ev, SIGHUP, handle_sighup, 0)) fatal(0, "ev_signal failed");
   /* exit on SIGINT/SIGTERM */
   /* re-read config if we receive a SIGHUP */
   if(ev_signal(ev, SIGHUP, handle_sighup, 0)) fatal(0, "ev_signal failed");
   /* exit on SIGINT/SIGTERM */