Add trivial DB conversion tool
authorSteve McIntyre <steve@einval.com>
Tue, 22 Nov 2011 18:56:19 +0000 (18:56 +0000)
committerSteve McIntyre <steve@einval.com>
Tue, 22 Nov 2011 18:56:19 +0000 (18:56 +0000)
Moved the location of all the flacs on my server to add an extra level
of directories. Rather than a complete re-encode, simply open the
database and modify all the flac paths to match.

C/Makefile
C/db_convert.c [new file with mode: 0644]

index dfff0dd..e0179e0 100644 (file)
@@ -1,6 +1,6 @@
 default: all
 
-all: fuse-music
+all: fuse-music convert
 
 FUSE_CFLAGS=$(shell pkg-config fuse --cflags)
 FUSE_LIBS=$(shell pkg-config fuse --libs)
@@ -11,6 +11,8 @@ CC=gcc
 
 fuse-music: fuse-music.o fmdb.o fmcache.o misc.o sha1.o xstrtoumax.o
        $(CC) -o fuse-music fuse-music.o fmdb.o fmcache.o misc.o sha1.o xstrtoumax.o $(FUSE_LIBS) $(SQL3_LIBS)
+convert: db_convert.o misc.o xstrtoumax.o
+       $(CC) -o convert db_convert.o misc.o xstrtoumax.o $(SQL3_LIBS)
 
 fuse-music.o: fuse-music.c fmdb.h fmcache.h misc.h
        $(CC) -c $(CFLAGS) $(FUSE_CFLAGS) fuse-music.c -o fuse-music.o
@@ -24,6 +26,8 @@ sha1.o: sha1.c sha1.h
        $(CC) -c $(CFLAGS) sha1.c -o sha1.o
 xstrtoumax.o: xstrtoumax.c xstrtol.h
        $(CC) -c $(CFLAGS) xstrtoumax.c -o xstrtoumax.o
+db_convert.o: db_convert.c misc.h fmdb.h
+       $(CC) -c $(CFLAGS) db_convert.c -o db_convert.o
 
 clean:
        rm -f *~ fuse-music *.o
diff --git a/C/db_convert.c b/C/db_convert.c
new file mode 100644 (file)
index 0000000..7a7db45
--- /dev/null
@@ -0,0 +1,1074 @@
+#include <sqlite3.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <ctype.h>
+#include "fmdb.h"
+#include "misc.h"
+
+FILE *logfile = NULL;
+
+#define DB_COUNT_DEBUG
+
+#define DB_DEBUG 2
+
+#if DB_DEBUG >= 0
+#   define DBLOG0(x) LOGPRINT(x)
+#else
+#   define DBLOG0(x)
+#endif
+
+#if DB_DEBUG >= 1
+#   define DBLOG1(x) LOGPRINT(x)
+#else
+#   define DBLOG1(x)
+#endif
+
+#if DB_DEBUG >= 2
+#   define DBLOG2(x) LOGPRINT(x)
+#else
+#   define DBLOG2(x)
+#endif
+
+static pthread_mutex_t db_mutex = PTHREAD_MUTEX_INITIALIZER;
+static long long db_cache_space_used_value = 0;
+
+typedef struct
+{
+    sqlite3 *db;
+} db_state_t;
+
+enum result_type
+{
+    RES_SIZE,
+    RES_CACHE,
+    RES_COUNT
+};
+
+struct results
+{
+    struct results *next;
+    struct results *prev;
+    enum result_type type;
+    union
+    {
+        db_size_entry_t size;
+        db_cache_entry_t cache;
+        db_count_entry_t count;
+    } data;
+};
+
+struct results *res_head = NULL;
+struct results *res_current = NULL;
+struct results *res_tail = NULL;
+
+static void free_results(void)
+{
+    struct results *entry = res_head;
+    struct results *current = res_head;
+    
+    while(entry)
+    {
+        entry = entry->next;
+        free(current);
+        current = entry;
+    }
+    res_head = NULL;
+    res_current = NULL;
+    res_tail = NULL;
+}
+
+static int results_callback(void *pArg, int argc, char **argv, char **columnNames)
+{
+    struct results *entry = calloc(1, sizeof (*entry));
+    enum result_type *type = pArg;
+
+    if (res_tail)
+        res_tail->next = entry;
+    if (!res_head)
+        res_head = entry;
+
+    entry->prev = res_tail;
+    res_tail = entry;
+
+    switch (*type)
+    {
+        case RES_SIZE:
+            if (argv[0])
+                strncpy(entry->data.size.flac_path, argv[0], sizeof(entry->data.size.flac_path));
+            if (argv[1])
+                strncpy(entry->data.size.format_choice, argv[1], sizeof(entry->data.size.format_choice));
+            if (argv[2])
+            {
+                strncpy(entry->data.size.format_quality, argv[2], sizeof(entry->data.size.format_quality));
+            }
+            if (argv[3])
+            {
+                entry->data.size.size = strtoull(argv[3], NULL, 10);
+            }
+            if (argv[4])
+                entry->data.size.mtime = strtoul(argv[4], NULL, 10);
+            break;
+        case RES_CACHE:
+            if (argv[0])
+                strncpy(entry->data.cache.flac_path, argv[0], sizeof(entry->data.cache.flac_path));
+            if (argv[1])
+                strncpy(entry->data.cache.cache_path, argv[1], sizeof(entry->data.cache.cache_path));
+            if (argv[2])
+                entry->data.cache.state = strtoul(argv[2], NULL, 10);
+            if (argv[3])
+                entry->data.cache.size = strtoull(argv[3], NULL, 10);
+            if (argv[4])
+                entry->data.cache.mtime = strtoul(argv[4], NULL, 10);
+            break;
+        case RES_COUNT:
+            if (argv[0])
+                entry->data.count.count = strtol(argv[0], NULL, 10);
+            break;
+    }
+    return 0;
+}
+
+/* Needs no locking, internal function called at startup when we're
+ * still single threaded */
+static int db_create_size_table(db_state_t *dbp)
+{
+    int error = 0;
+    char *open_error;
+    char sql_command[2 * PATH_MAX];
+    
+    /* Delete the table and create new */
+    error = sqlite3_exec(dbp->db, "DROP TABLE sizes;", NULL, NULL, NULL);
+    sprintf(sql_command, "CREATE TABLE sizes ("
+            "flac_path VARCHAR(%d),"
+            "format_choice VARCHAR(10),"
+            "format_quality VARCHAR(10),"
+            "size INTEGER,"
+            "mtime INTEGER);", PATH_MAX);
+    error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: got error %d (%s) from create\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+    return 0;
+}
+
+void db_dump_size_entry(const db_size_entry_t *entry)
+{
+    DBLOG0((logfile, "  flac_path: %s\n", entry->flac_path));
+    DBLOG0((logfile, "  format_choice: %s\n", entry->format_choice));
+    DBLOG0((logfile, "  format_quality: %s\n", entry->format_quality));
+    DBLOG0((logfile, "  size: %lld\n", entry->size));
+    DBLOG0((logfile, "  mtime: %ld\n", entry->mtime));
+}
+
+static int db_dump_size_unlocked(db_state_t *state)
+{
+    int error = 0;
+    char *open_error;
+    int result_type = RES_SIZE;
+    char sql_command[2 * PATH_MAX];
+    int i = 0;
+
+    free_results();
+    sprintf(sql_command, "SELECT * FROM sizes");
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    DBLOG0((logfile, "%s: db dump\n", __func__));
+    while (res_current)
+    {
+        DBLOG0((logfile, "entry %d:\n", i++));
+        db_dump_size_entry(&res_current->data.size);
+        res_current = res_current->next;
+    }
+
+    return error;
+}
+
+int db_dump_size(FMDB *dbp)
+{
+    int error = 0;
+
+    pthread_mutex_lock(&db_mutex);
+    free_results();
+
+    error = db_dump_size_unlocked(dbp);
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+#ifdef DB_COUNT_DEBUG
+static int db_count_size(FMDB *dbp)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int result_type = RES_COUNT;
+
+    free_results();
+
+    error = sqlite3_exec(state->db, "SELECT COUNT(*) FROM sizes;", results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    if (res_current)
+    {
+        DBLOG1((logfile, "%s: %d entries in the sizes DB\n", 
+                __func__, res_current->data.count.count));
+    }
+    else
+        error = ENOENT;
+
+    return error;
+}
+#endif
+
+int db_store_size_entry(FMDB *dbp, const db_size_entry_t *in)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    char sql_command[2 * PATH_MAX];
+
+    pthread_mutex_lock(&db_mutex);
+    free_results();
+
+    /* Delete any existing entries */
+    sprintf(sql_command,
+            "DELETE FROM sizes WHERE flac_path == '%s'",
+            in->flac_path);
+    error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG2((logfile, "%s: Failed to remove old entry for %s, error %d (%s)\n",
+                __func__, in->flac_path, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+    }
+    else
+        DBLOG2((logfile, "%s: Deleted old entry for %s\n", __func__, in->flac_path));
+
+    DBLOG2((logfile, "%s: storing %s:%s:%s:%lld:%ld\n",
+            __func__, in->flac_path, in->format_choice, in->format_quality,
+            in->size, in->mtime));
+    sprintf(sql_command, "INSERT INTO sizes VALUES('%s','%s','%s',%lld,%ld);",
+            in->flac_path, in->format_choice, in->format_quality,
+            in->size, in->mtime);
+    error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to write entry, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        pthread_mutex_unlock(&db_mutex);
+        return error;
+    }
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int db_lookup_size_entry(FMDB *dbp, const db_size_entry_t *in, db_size_entry_t *out)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int result_type = RES_SIZE;
+    char sql_command[2 * PATH_MAX];
+
+    pthread_mutex_lock(&db_mutex);
+    free_results();
+    sprintf(sql_command,
+            "SELECT * FROM sizes WHERE flac_path == '%s' AND format_choice == '%s' AND format_quality == %s;",
+            in->flac_path, in->format_choice, in->format_quality);
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n", __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        pthread_mutex_unlock(&db_mutex);
+        return error;
+    }
+
+    res_current = res_head;
+    if (res_current)
+    {
+        memcpy(out, &res_current->data.size, sizeof(db_size_entry_t));
+        res_current = res_current->next;
+        DBLOG2((logfile, "%s: copying %s:%s:%s:%lld:%ld\n",
+                __func__, out->flac_path, out->format_choice, out->format_quality,
+                out->size, out->mtime));
+    }
+    else
+        error = ENOENT;
+
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int db_remove_size_entry(FMDB *dbp, const db_size_entry_t *in)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    char sql_command[2 * PATH_MAX];
+
+    pthread_mutex_lock(&db_mutex);
+    free_results();
+    sprintf(sql_command,
+            "DELETE FROM sizes WHERE flac_path == '%s' AND format_choice == '%s' AND format_quality == %s;",
+            in->flac_path, in->format_choice, in->format_quality);
+    error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to remove, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+    }
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+/* Needs no locking, internal function called at startup when we're
+ * still single threaded */
+static int db_create_cache_table(db_state_t *dbp)
+{
+    int error = 0;
+    char *open_error;
+    char sql_command[2 * PATH_MAX];
+    
+    /* Delete the table and create new */
+    error = sqlite3_exec(dbp->db, "DROP TABLE cache;", NULL, NULL, NULL);
+    sprintf(sql_command, "CREATE TABLE cache ("
+            "flac_path VARCHAR(%d),"
+            "cache_path VARCHAR(%d),"
+            "state INTEGER,"
+            "size INTEGER,"
+            "mtime INTEGER);", PATH_MAX, PATH_MAX);
+    error = sqlite3_exec(dbp->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: got error %d (%s) from create\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+    return 0;
+}
+
+#ifdef DB_DEBUG
+static char *str_state(db_cache_state_e state)
+{
+    switch (state)
+    {
+        case FMDB_CACHE_FREE:
+            return "FMDB_CACHE_FREE";
+        case FMDB_CACHE_ENCODING:
+            return "FMDB_CACHE_ENCODING";
+        case FMDB_CACHE_READING:
+            return "FMDB_CACHE_READING";
+        case FMDB_CACHE_DELETING:
+            return "FMDB_CACHE_DELETING";
+        default:
+            return "BOGUS STATE";
+    }
+}
+#endif
+
+void db_dump_cache_entry(const db_cache_entry_t *entry)
+{
+    DBLOG0((logfile, "  flac_path: %s\n", entry->flac_path));
+    DBLOG0((logfile, "  cache_path: %s\n", entry->cache_path));
+    DBLOG0((logfile, "  state: %s\n", str_state(entry->state)));
+    DBLOG0((logfile, "  size: %lld\n", entry->size));
+    DBLOG0((logfile, "  mtime: %ld\n", entry->mtime));
+}
+
+static int db_dump_cache_unlocked(db_state_t *state, long long *size)
+{
+    int error = 0;
+    char *open_error;
+    int result_type = RES_CACHE;
+    char sql_command[2 * PATH_MAX];
+    int i = 0;
+    long long total_size = 0;
+
+    free_results();
+
+    sprintf(sql_command, "SELECT * FROM cache");
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    DBLOG0((logfile, "%s: db dump\n", __func__));
+    while (res_current)
+    {
+        DBLOG0((logfile, "entry %d:\n", i++));
+        db_dump_cache_entry(&res_current->data.cache);
+        total_size += res_current->data.cache.size;
+        res_current = res_current->next;
+    }
+
+    DBLOG0((logfile, "%s: %d entries, %lld total size\n", __func__, i, total_size));
+    *size = total_size;
+    db_cache_space_used_value = total_size;
+    return error;
+}
+
+int db_dump_cache(FMDB *dbp, long long *size)
+{
+    int error = 0;
+
+    pthread_mutex_lock(&db_mutex);
+    free_results();
+
+    error = db_dump_cache_unlocked(dbp, size);
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+static int _db_cache_space_used_unlocked(FMDB *dbp, long long *size)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int result_type = RES_CACHE;
+    char sql_command[2 * PATH_MAX];
+    long long total_size = 0;
+
+    free_results();
+
+    sprintf(sql_command, "SELECT * FROM cache");
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    while (res_current)
+    {
+        total_size += res_current->data.cache.size;
+        res_current = res_current->next;
+    }
+
+    *size = total_size;
+    db_cache_space_used_value = total_size;
+    return error;
+}
+
+int db_cache_space_used(FMDB *dbp, long long *size)
+{
+    *size = db_cache_space_used_value;
+    return 0;
+}
+
+static int db_count_cache(FMDB *dbp, int *num_entries)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int result_type = RES_COUNT;
+
+    free_results();
+
+    error = sqlite3_exec(state->db, "SELECT COUNT(*) FROM cache;", results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    if (res_current)
+    {
+        DBLOG1((logfile, "%s: %d entries in the cache DB\n", 
+                __func__, res_current->data.count.count));
+        *num_entries = res_current->data.count.count;
+    }
+    else
+        error = ENOENT;
+
+    return error;
+}
+
+int db_store_cache_entry(FMDB *dbp, const db_cache_entry_t *in)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    char sql_command[2 * PATH_MAX];
+    long long junk;
+
+    pthread_mutex_lock(&db_mutex);
+    /* Delete any existing entries */
+    sprintf(sql_command,
+            "DELETE FROM cache WHERE flac_path == '%s'",
+            in->flac_path);
+    error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG1((logfile, "%s: Failed to remove old entry for %s, error %d (%s)\n",
+                __func__, in->flac_path, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+    }
+    else
+    {
+        DBLOG1((logfile, "%s: Deleted old entry for %s\n", __func__, in->flac_path));
+    }
+    /* Now write the new entry */
+    DBLOG2((logfile, "%s: storing %s:%s:%s:%lld:%ld\n",
+            __func__, in->flac_path, in->cache_path, str_state(in->state),
+            in->size, in->mtime));
+    sprintf(sql_command, "INSERT INTO cache VALUES('%s','%s',%d,%lld,%ld);",
+            in->flac_path, in->cache_path, in->state,
+            in->size, in->mtime);
+    error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to write entry, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        pthread_mutex_unlock(&db_mutex);
+        return error;
+    }
+    _db_cache_space_used_unlocked(state, &junk);
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int db_lookup_cache_entry_by_flac_path(FMDB *dbp, const db_cache_entry_t *in, db_cache_entry_t *out)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int result_type = RES_CACHE;
+#ifdef DB_COUNT_DEBUG
+    int num_entries = 0;
+#endif
+    char sql_command[2 * PATH_MAX];
+
+    pthread_mutex_lock(&db_mutex);
+#ifdef DB_COUNT_DEBUG
+    db_count_cache(dbp, &num_entries);
+#endif
+    free_results();
+
+    sprintf(sql_command,
+            "SELECT * FROM cache WHERE flac_path == '%s'",
+            in->flac_path);
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        pthread_mutex_unlock(&db_mutex);
+        return error;
+    }
+
+    res_current = res_head;
+    if (res_current)
+    {
+        memcpy(out, &res_current->data.cache, sizeof(db_cache_entry_t));
+        res_current = res_current->next;
+        DBLOG2((logfile, "%s: copying %s:%s:%d:%lld:%ld\n",
+                __func__, out->flac_path, out->cache_path, out->state,
+               out->size, out->mtime));
+    }
+    else
+        error = ENOENT;
+
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int db_lookup_cache_entry_by_cache_path(FMDB *dbp, const db_cache_entry_t *in, db_cache_entry_t *out)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int result_type = RES_CACHE;
+#ifdef DB_COUNT_DEBUG
+    int num_entries = 0;
+#endif
+    char sql_command[2 * PATH_MAX];
+
+    pthread_mutex_lock(&db_mutex);
+#ifdef DB_COUNT_DEBUG
+    db_count_cache(dbp, &num_entries);
+#endif
+    free_results();
+
+    sprintf(sql_command,
+            "SELECT * FROM cache WHERE cache_path == '%s'",
+            in->cache_path);
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        pthread_mutex_unlock(&db_mutex);
+        return error;
+    }
+
+    res_current = res_head;
+    if (res_current)
+    {
+        memcpy(out, &res_current->data.cache, sizeof(db_cache_entry_t));
+        res_current = res_current->next;
+        DBLOG2((logfile, "%s: copying %s:%s:%d:%lld:%ld\n",
+                __func__, out->flac_path, out->cache_path, out->state,
+               out->size, out->mtime));
+    }
+    else
+        error = ENOENT;
+
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int db_lookup_cache_entry_by_lru(FMDB *dbp, db_cache_entry_t *out, int *num)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    int num_entries = 0;
+    int result_type = RES_CACHE;
+    char sql_command[2 * PATH_MAX];
+    long long junk;
+
+    pthread_mutex_lock(&db_mutex);
+    db_count_cache(dbp, &num_entries);
+    free_results();
+
+    sprintf(sql_command,
+            "SELECT * FROM cache WHERE state == %d ORDER BY mtime ASC LIMIT 1", FMDB_CACHE_FREE);
+//            "SELECT * FROM cache");
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        pthread_mutex_unlock(&db_mutex);
+        return error;
+    }
+
+    res_current = res_head;
+    if (res_current)
+    {
+        memcpy(out, &res_current->data.cache, sizeof(db_cache_entry_t));
+        res_current = res_current->next;
+        *num = num_entries;
+        DBLOG2((logfile, "%s: copying %s:%s:%d:%lld:%ld\n",
+                __func__, out->flac_path, out->cache_path, out->state,
+                out->size, out->mtime));
+    }
+    else
+    {
+        DBLOG0((logfile, "%s: failed to find an LRU entry\n", __func__));
+        error = ENOENT;
+        *num = num_entries;
+        db_dump_cache_unlocked(dbp, &junk);
+    }
+    
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int db_remove_cache_entry(FMDB *dbp, const db_cache_entry_t *in)
+{
+    int error = 0;
+    db_state_t *state = dbp;
+    char *open_error;
+    char sql_command[2 * PATH_MAX];
+    long long junk;
+
+    pthread_mutex_lock(&db_mutex);
+    sprintf(sql_command,
+            "DELETE FROM cache WHERE flac_path == '%s';",
+            in->flac_path);
+    error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to remove, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+    }
+    _db_cache_space_used_unlocked(state, &junk);
+    pthread_mutex_unlock(&db_mutex);
+    return error;
+}
+
+int sync_cache_and_db(FMDB *dbp, char *cache_dir)
+{
+    int error = 0;
+    struct stat sb;
+    char *open_error;
+    int result_type = RES_CACHE;
+    char sql_command[2 * PATH_MAX];
+    db_state_t *state = dbp;
+#ifdef DB_COUNT_DEBUG
+    int num_entries = 0;
+#endif
+    int startup_db_entries = 0;
+    int removed_db_entries = 0;
+    int done = 0;
+
+    /* First, check that all the entries in the DB exist on disk; any
+     * that don't, or that look wrong: delete the DB entry */
+    DBLOG1((logfile, "%s: Checking DB entry sanity\n", __func__));
+#ifdef DB_COUNT_DEBUG
+    db_count_cache(dbp, &num_entries);
+#endif
+#ifdef DB_COUNT_DEBUG
+    db_count_size(dbp);
+#endif
+    free_results();
+
+    while (!done)
+    {
+        int entry_changed = 0;
+        
+        sprintf(sql_command, "SELECT * FROM cache;");
+        error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+        if (error)
+        {
+            DBLOG0((logfile, "%s: Failed to lookup results, error %d (%s)\n",
+                    __func__, error, open_error));
+            if (open_error)
+                sqlite3_free(open_error);
+            return error;
+        }
+
+        res_current = res_head;
+        /* Quick check - if we have no results (i.e. no cache entries,
+         * then break out */
+        if (!res_current)
+            done = 1;
+
+        while (res_current)
+        {
+            db_cache_entry_t *entry = &res_current->data.cache;
+            char cache_file_name[PATH_MAX];
+            
+            startup_db_entries++;
+            
+            /* Remove entries that don't look complete */
+            if (entry->flac_path == NULL ||
+                entry->cache_path == NULL || 
+                entry->state == FMDB_CACHE_ENCODING ||
+                entry->state == FMDB_CACHE_DELETING)
+            {
+                db_remove_cache_entry(dbp, entry);
+                removed_db_entries++;
+                entry_changed++;            }
+            else
+            {
+                
+                /* Fix up any entries that might be currently marked as in use */
+                if (entry->state == FMDB_CACHE_READING)
+                {
+                    entry->state = FMDB_CACHE_FREE;
+                    error = db_store_cache_entry(dbp, entry);
+                    if (error)
+                    {
+                        DBLOG0((logfile, "%s: can't update cache DB entry for %s; error %d\n",
+                                __func__, entry->cache_path, error));
+                        return error;
+                    }
+                    entry_changed++;
+                }
+        
+                /* Now look for entries that don't have a matching
+                 * (and correct) cache file on disk */
+                snprintf(cache_file_name, PATH_MAX, "%s/%s", cache_dir, entry->cache_path);
+                error = stat(cache_file_name, &sb);
+                if (error)
+                {
+                    DBLOG1((logfile, "%s: can't stat cache file %s, error %d. Deleting DB entry\n",
+                            __func__, cache_file_name, errno));
+                    db_remove_cache_entry(dbp, entry);
+                    removed_db_entries++;
+                    entry_changed++;
+                }
+                else
+                {
+                    if (sb.st_size != entry->size)
+                    {
+                        DBLOG1((logfile, "%s: cache file %s is wrong size (%lld bytes, DB said %lld). Deleting DB entry\n",
+                            __func__, cache_file_name, (long long)sb.st_size, entry->size));
+                        db_remove_cache_entry(dbp, entry);
+                        removed_db_entries++;
+                        entry_changed++;
+                    }
+                }
+            }
+            if (!entry_changed)
+            {
+                /* This DB entry looks OK, then. Next! */
+                res_current = res_current->next;
+                /* And if we've reached the end of the list without
+                 * changes, we're finished. */
+                if (!res_current)
+                    done = 1;
+            }
+            else
+            {
+                DBLOG0((logfile, "%s: modified a cache DB entry, restart loop\n", __func__));
+                break;
+            }
+        }
+    }
+    free_results();
+    DBLOG1((logfile, "%s: statistics:\n", __func__));
+    DBLOG1((logfile, "  found %d cache DB entries at startup\n", startup_db_entries));
+    DBLOG1((logfile, "  cleaned up %d cache DB entries\n", removed_db_entries));
+    DBLOG1((logfile, "  now have %d cache DB entries\n", startup_db_entries - removed_db_entries));
+    return 0;
+}
+
+FMDB *db_open(char *db_name)
+{
+    db_state_t *dbp = NULL;
+    int error = 0;            /* function return value */
+    long long junk;
+
+    /* Allocate state structure */
+    dbp = calloc(1, sizeof(*dbp));
+    if (dbp)
+    {
+        error = sqlite3_open(db_name, &dbp->db);
+        if (error)
+        {
+            DBLOG0((logfile, "%s: Unable to open sqlite file %s: error %d\n", __func__, db_name, error));
+            errno = error;
+            return NULL;
+        }
+        
+        /* We have a database pointer open. Do we need to init the
+         * tables? Try to grab the first row of the sizes table and
+         * see if we get an error. There has to be a better way than
+         * this! */
+        error = sqlite3_exec(dbp->db, "SELECT COUNT(*) FROM sizes;", NULL, NULL, NULL);
+        if (SQLITE_ERROR == error)
+        {
+            /* No table found, so create new */
+            /* First, the size table */
+            error = db_create_size_table(dbp);
+            if (error)
+            {
+                sqlite3_close(dbp->db);
+                errno = error;
+                return NULL;
+            }
+            /* 2. The cache table */
+            error = db_create_cache_table(dbp);
+            if (error)
+            {
+                sqlite3_close(dbp->db);
+                errno = error;
+                return NULL;
+            }
+        }
+    }
+    _db_cache_space_used_unlocked(dbp, &junk);
+    return dbp;
+}
+
+int db_close(FMDB *dbp)
+{
+    db_state_t *state = dbp;
+    /* When we're done with the database, close it. */
+    if (state->db)
+        sqlite3_close(state->db);
+    free(state);
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    int error = 0;
+    char *dbname = argv[1];
+    FMDB *p = db_open(dbname);
+    char *open_error;
+    int result_type;
+    char sql_command[2 * PATH_MAX];
+    char tmp_path[PATH_MAX];
+    int i = 0;
+    db_state_t *state = p;
+
+    logfile = stdout;
+
+    if (!p)
+    {
+        fprintf(stderr, "Failed to open db, errno %d\n", errno);
+        return errno;
+    }
+
+    free_results();
+    result_type = RES_CACHE;
+    sprintf(sql_command, "SELECT * FROM cache");
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    while (res_current)
+    {
+        db_cache_entry_t *entry = &res_current->data.cache;
+        DBLOG0((logfile, "entry %d:\n", i++));
+        sprintf(tmp_path, "%c/%s", toupper(entry->flac_path[0]), entry->flac_path);
+        printf("  old flac_path: %s\n", entry->flac_path);
+        printf("  new flac_path: %s\n", tmp_path);
+        printf("  delete old:\n");
+        /* Delete existing entry */
+        sprintf(sql_command,
+                "DELETE FROM cache WHERE flac_path == '%s'",
+                entry->flac_path);
+        error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+        if (error)
+        {
+            printf("    error!\n");
+            return 1;
+        }
+        else
+            printf("    done\n");
+
+        memcpy(entry->flac_path, tmp_path, PATH_MAX);
+        printf("  insert new:\n");
+
+        DBLOG2((logfile, "%s: storing %s:%s:%s:%lld:%ld\n",
+                __func__, entry->flac_path, entry->cache_path, str_state(entry->state),
+                entry->size, entry->mtime));
+        sprintf(sql_command, "INSERT INTO cache VALUES('%s','%s',%d,%lld,%ld);",
+                entry->flac_path, entry->cache_path, entry->state,
+                entry->size, entry->mtime);
+        error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+        if (error)
+        {
+            printf("    error!\n");
+            return 1;
+        }
+        else
+            printf("    done\n");
+        
+        res_current = res_current->next;
+    }
+
+    free_results();
+    result_type = RES_SIZE;
+    sprintf(sql_command, "SELECT * FROM sizes");
+    error = sqlite3_exec(state->db, sql_command, results_callback, &result_type, &open_error);
+    if (error)
+    {
+        DBLOG0((logfile, "%s: Failed to lookup, error %d (%s)\n",
+                __func__, error, open_error));
+        if (open_error)
+            sqlite3_free(open_error);
+        return error;
+    }
+
+    res_current = res_head;
+    while (res_current)
+    {
+        db_size_entry_t *entry = &res_current->data.size;
+        DBLOG0((logfile, "entry %d:\n", i++));
+        sprintf(tmp_path, "%c/%s", toupper(entry->flac_path[0]), entry->flac_path);
+        printf("  old flac_path: %s\n", entry->flac_path);
+        printf("  new flac_path: %s\n", tmp_path);
+        printf("  delete old:\n");
+        /* Delete existing entry */
+        sprintf(sql_command,
+                "DELETE FROM sizes WHERE flac_path == '%s'",
+                entry->flac_path);
+        error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+        if (error)
+        {
+            printf("    error!\n");
+            return 1;
+        }
+        else
+            printf("    done\n");
+
+        memcpy(entry->flac_path, tmp_path, PATH_MAX);
+        printf("  insert new:\n");
+
+        DBLOG2((logfile, "%s: storing %s:%s:%s:%lld:%ld\n",
+                __func__, entry->flac_path, entry->format_choice, entry->format_quality,
+                entry->size, entry->mtime));
+        sprintf(sql_command, "INSERT INTO sizes VALUES('%s','%s','%s',%lld,%ld);",
+                entry->flac_path, entry->format_choice, entry->format_quality,
+                entry->size, entry->mtime);
+        error = sqlite3_exec(state->db, sql_command, NULL, NULL, &open_error);
+        if (error)
+        {
+            printf("    error!\n");
+            return 1;
+        }
+        else
+            printf("    done\n");
+        
+        res_current = res_current->next;
+    }
+
+    return 0;
+}
+