Big rework of the playlist system.
authorNot Zed <notzed@gmail.com>
Mon, 14 Jun 2021 05:43:24 +0000 (15:13 +0930)
committerNot Zed <notzed@gmail.com>
Mon, 14 Jun 2021 05:43:24 +0000 (15:13 +0930)
13 files changed:
TODO
audio-cmd.c
blobs.c
dbindex.c
dbindex.h
disk-indexer.c
http-monitor.c
index-util.c
indexer-cmd.c
input-monitor.c
music-player.c
notify.c
notify.h

diff --git a/TODO b/TODO
index 79abcf7..23e767f 100644 (file)
--- a/TODO
+++ b/TODO
@@ -32,3 +32,12 @@ o check end of file processing
 o web frontend
  - custom playlists
   + can use the shuffle code again
+
+o internals
+ - kill ez_bitset and find an alternative merge algorithm for indexer
+ - incremental indexing add/remove
+  - can simply be done on a per-directory basis
+ - incremental all-playlist and all-shuffle?
+  - add items as scanned?
+  - when you add an item to a playlist, randomly swap it's order with an existing item
+  X problem is all-playlist is ordered by path so will typically require very large update anyway
index 7896273..6a2b4de 100644 (file)
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdint.h>
 
+#include "dbindex.h"
 #include "notify.h"
 
-/*
-static const struct {
-       const char *cmd;
-       enum notify_action action;
-} commands[] = {
-       { "pause", NOTIFY_PLAYER_PAUSE },
-       { "play", NOTIFY_PLAYER_PAUSE },
-       { "seek", NOTIFY_PLAYER_SEEKPAUSE },
-       { "pause", NOTIFY_PLAYER_PAUSE },
-};
-*/
-
 int main(int argc, char **argv) {
        if (argc > 1) {
                char *cmd = argv[1];
@@ -57,6 +47,28 @@ int main(int argc, char **argv) {
                        notify_msg_send(player, NOTIFY_PLAY_NEXT, 0, NULL);
                } else if (strcmp(cmd, "prev") == 0) {
                        notify_msg_send(player, NOTIFY_PLAY_PREV, 0, NULL);
+               } else if (strcmp(cmd, "goto") == 0) {
+                       if (argc == 4) {
+                               struct notify_goto msg = {
+                                       .info.listid = atoi(argv[2]),
+                                       .info.fileid = atoi(argv[3])
+                               };
+                               notify_msg_send(player, NOTIFY_PLAY_GOTO, 0, &msg);
+                       }
+               } else if (strcmp(cmd, "playnow") == 0) {
+                       if (argc == 3) {
+                               struct notify_goto msg = {
+                                       .info.fileid = atoi(argv[2])
+                               };
+                               notify_msg_send(player, NOTIFY_PLAY_NOW, 0, &msg);
+                       }
+               } else if (strcmp(cmd, "enqueue") == 0) {
+                       if (argc == 3) {
+                               struct notify_goto msg = {
+                                       .info.fileid = atoi(argv[2])
+                               };
+                               notify_msg_send(player, NOTIFY_PLAY_ENQUEUE, 0, &msg);
+                       }
                } else if (strcmp(cmd, "vol+") == 0) {
                        notify_msg_send(player, NOTIFY_VOLUME_UP, 0, NULL);
                } else if (strcmp(cmd, "vol-") == 0) {
diff --git a/blobs.c b/blobs.c
index 0f47034..67d2058 100644 (file)
--- a/blobs.c
+++ b/blobs.c
@@ -49,7 +49,7 @@ ez_blob_desc DBLIST_DESC[] = {
        EZ_BLOB_START(dblist, 3, 3),
        EZ_BLOB_INT32(dblist, 1, size),
        EZ_BLOB_STRING(dblist, 2, name),
-       EZ_BLOB_STRING(dblist, 3, comment),
+       EZ_BLOB_STRING(dblist, 3, desc),
 };
 
 ez_blob_desc PLAY_SEEK_DESC[] = {
@@ -69,6 +69,8 @@ ez_blob_desc KEY_DESC[] = {
 };
 
 ez_blob_desc GOTO_DESC[] = {
-       EZ_BLOB_START(struct notify_goto, 4, 1),
-       EZ_BLOB_INT32(struct notify_goto, 1, fileid),
+       EZ_BLOB_START(struct notify_goto, 4, 3),
+       EZ_BLOB_INT32(struct notify_goto, 1, info.listid),
+       EZ_BLOB_INT32(struct notify_goto, 2, info.seq),
+       EZ_BLOB_INT32(struct notify_goto, 3, info.fileid),
 };
index 024cf48..7cc872e 100644 (file)
--- a/dbindex.c
+++ b/dbindex.c
@@ -36,6 +36,8 @@
 #include <errno.h>
 
 #include "dbindex.h"
+#include "ez-array.h"
+#include "ez-set.h"
 #include "ez-blob.h"
 #include "ez-blob-basic.h"
 
@@ -53,24 +55,6 @@ TODO: playlist should be linked list
 /*
   playlist storage.
 
-  require ordering, so store by index.
-  reverse index?
-
-  ** bad ** too wasteful **
-  Use 64-bit index
-
-  [playlist.id][order] -> [file.id]
-
-  query as > [playlist.id][order] < [playlisti.id+1]
-
-  Reverse:
-
-  [playlist.id][file.id] -> [seq]
-
-
-
-  Alternative:
-
   forward: file_by_list [list.id] -> [seq][file.id] with custom dupsort compare on [seq] only
   reverse: list_by_file [file.id] -> [list.id][seq]
 
@@ -101,6 +85,7 @@ struct dbindex {
        MDB_dbi disk_by_uuid;   // key is uuid                UNIQUE
 
        MDB_dbi list;           // playlist to name
+       MDB_dbi list_by_name;
 
        MDB_dbi file;
        MDB_dbi file_by_path;   // key is "diskid{hex}/path"  UNIQUE  TODO: limited to 511 bytes length
@@ -113,11 +98,6 @@ struct dbindex {
 
        MDB_dbi file_by_suffix;
 
-
-       // LEGACY - TBD ? maybe it should be a playlist ?
-       MDB_dbi shuffle;        // seq to file
-       MDB_dbi shuffle_by_file;// file to seq                FOREIGN
-
        // This only works for threads not processes
        // single writer I guess.
        volatile uint32_t diskid;
@@ -157,6 +137,8 @@ static uint32_t find_next_id(MDB_txn *tx, MDB_dbi db) {
        return id;
 }
 
+static int dblist_put(dbtxn *tx, dbindex *db, dblist *list, unsigned int flags);
+
 static int
 cmp_uint(const MDB_val *a, const MDB_val *b) {
         return (*(unsigned int *)a->mv_data < *(unsigned int *)b->mv_data) ? -1 :
@@ -204,6 +186,7 @@ dbindex *dbindex_open(const char *ipath) {
        res |= mdb_dbi_open(tx, "disk#uuid", MDB_CREATE, &db->disk_by_uuid);
 
        res |= mdb_dbi_open(tx, "list", MDB_CREATE | MDB_INTEGERKEY, &db->list);
+       res |= mdb_dbi_open(tx, "list#name", MDB_CREATE, &db->list_by_name);
 
        res |= mdb_dbi_open(tx, "file", MDB_CREATE | MDB_INTEGERKEY, &db->file);
        res |= mdb_dbi_open(tx, "file#path", MDB_CREATE, &db->file_by_path);
@@ -214,22 +197,39 @@ dbindex *dbindex_open(const char *ipath) {
        res |= mdb_dbi_open(tx, "file#list", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &db->file_by_list);
        mdb_set_dupsort(tx, db->file_by_list, cmp_uint);
        res |= mdb_dbi_open(tx, "list#file", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &db->list_by_file);
-       //mdb_set_dupsort(tx, db->list_by_file, cmp_uint);
-
-       // to be replaced with file#list perhaps?
-       res |= mdb_dbi_open(tx, "shuffle", MDB_CREATE | MDB_INTEGERKEY, &db->shuffle);
-       res |= mdb_dbi_open(tx, "shuffle#file", MDB_CREATE | MDB_INTEGERKEY, &db->shuffle_by_file);
 
        // experimental substring search
        //res |= mdb_dbi_open(tx, "file#suffix", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERDUP , &db->file_by_suffix);
        //mdb_drop(tx, db->file_by_suffix, 1);
        res |= mdb_dbi_open(tx, "file#suffix", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERDUP , &db->file_by_suffix);
 
-
        db->diskid = find_next_id(tx, db->disk);
        db->listid = find_next_id(tx, db->list);
        db->fileid = find_next_id(tx, db->file);
 
+       // setup system playlists
+       {
+               static const char *names[] = { "all", "all#shuffle", "jukebox", "jukebox#shuffle", "playnow", "playnow#shuffle", "shit" };
+               static const char *descs[] = { "All files ordered by path", "All files shuffled", "All enqueued songs in order", "All endqueued songs shuffled", "All play-now files in order", "All play-now files shuffled", "The shitlist" };
+               dblist list = { 0 };
+
+               for (int i=0;i<sizeof(names)/sizeof(names[0]);i++) {
+                       int tmp;
+
+                       list.name = (char *)names[i];
+                       list.desc = (char *)descs[i];
+                       if (tmp = dblist_add(tx, db, &list)) {
+                               if (tmp == MDB_KEYEXIST)
+                                       db->listid = find_next_id(tx, db->list);
+                               else {
+                                       res |= tmp;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+
        if (0) {
                MDB_cursor *cursor;
                MDB_val key = { 0 }, data = { 0 };
@@ -276,6 +276,10 @@ dbindex *dbindex_open(const char *ipath) {
        return NULL;
 }
 
+int dbindex_result(dbindex *db) {
+       return db->res;
+}
+
 void dbindex_close(dbindex *db) {
        if (db) {
                mdb_env_close(db->env);
@@ -361,6 +365,12 @@ static void *primary_get_decode(MDB_txn *tx, dbindex *db, ez_blob_desc *desc, MD
        return NULL;
 }
 
+static int primary_exists(MDB_txn *tx, dbindex *db, dbid_t id, MDB_dbi primary) {
+       MDB_val key = { .mv_data = &id, .mv_size = sizeof(id) }, dat;
+
+       return (db->res = mdb_get(tx, primary, &key, &dat)) == 0;
+}
+
 /**
  * Retrieve and decode data based on unique secondary key.
  *
@@ -397,13 +407,10 @@ void dbdisk_free(dbdisk *f) {
        ez_blob_free(DBDISK_DESC, f);
 }
 
-int dbdisk_add(MDB_txn *txn, dbindex *db, dbdisk *d) {
-       MDB_txn *tx;
+int dbdisk_add(MDB_txn *tx, dbindex *db, dbdisk *d) {
        MDB_val key, data;
        int res;
 
-       mdb_txn_begin(db->env, txn, 0, &tx);
-
        // Store disk
        d->id = disk_next_id(db);
        key.mv_data = &d->id;
@@ -414,8 +421,7 @@ int dbdisk_add(MDB_txn *txn, dbindex *db, dbdisk *d) {
        res = mdb_put(tx, db->disk, &key, &data, MDB_NOOVERWRITE | MDB_RESERVE);
        if (res == 0)
                ez_basic_encode_raw(DBDISK_DESC, d, (ez_blob *)&data);
-
-       if (res != 0) {
+       else {
                printf("db put disk fail: %s\n", mdb_strerror(res));
                return res;
        }
@@ -429,19 +435,67 @@ int dbdisk_add(MDB_txn *txn, dbindex *db, dbdisk *d) {
        key.mv_size = strlen(d->uuid);
 
        res = mdb_put(tx, db->disk_by_uuid, &key, &data, MDB_NOOVERWRITE);
-       if (res == 0) {
-               return mdb_txn_commit(tx);
-       } else if (res == MDB_KEYEXIST) {
-               fprintf(stderr, "UNIQUE: disk uuid exists\n");
-               mdb_txn_abort(tx);
+       return res;
+}
+
+static int secondary_list_all(dbtxn *tx, MDB_dbi secondary, ez_array *array) {
+       MDB_cursor *cursor;
+       MDB_val key, data;
+       int res;
+
+       if (res = mdb_cursor_open(tx, secondary, &cursor))
                return res;
+
+       int op = MDB_FIRST;
+       while ((res = mdb_cursor_get(cursor, &key, &data, op)) == 0) {
+               if (!ez_array_add(array, data.mv_data, data.mv_size)) {
+                       res = ENOMEM;
+                       break;
+               }
+               op = MDB_NEXT;
+       }
+       if (res == MDB_NOTFOUND) {
+               res = 0;
        } else {
-               printf("db put fail: %s\n", mdb_strerror(res));
+               ez_array_clear(array);
+       }
+
+       printf("secondary list all: %s\n", mdb_strerror(res));
+
+       mdb_cursor_close(cursor);
+       return res;
+}
+
+static int secondary_list_key(dbtxn *tx, MDB_dbi secondary, MDB_val key, ez_array *array) {
+       MDB_cursor *cursor;
+       MDB_val data;
+       int res;
+
+       if (res = mdb_cursor_open(tx, secondary, &cursor))
                return res;
+
+       int op = MDB_SET_KEY;
+       while ((res = mdb_cursor_get(cursor, &key, &data, op)) == 0) {
+               if (!ez_array_add(array, data.mv_data, data.mv_size)) {
+                       res = ENOMEM;
+                       break;
+               }
+               op = MDB_NEXT_DUP;
+       }
+       if (res == MDB_NOTFOUND) {
+               res = 0;
+       } else {
+               ez_array_clear(array);
        }
 
+       printf("secondary list all: %s\n", mdb_strerror(res));
+
+       mdb_cursor_close(cursor);
+       return res;
 }
 
+
+// merge/replace with above?
 static uint32_t *scan_all(dbscan *scan, ssize_t *sizep) {
        int fid;
        int fids_size = 4096;
@@ -472,11 +526,7 @@ fail:
        return NULL;
 }
 
-int dbdisk_del(dbtxn *txn, dbindex *db, dbdisk *disk) {
-       MDB_txn *tx;
-
-       mdb_txn_begin(db->env, txn, 0, &tx);
-
+int dbdisk_del(dbtxn *tx, dbindex *db, dbdisk *disk) {
        dbscan *scan = dbfile_scan_disk(tx, db, disk->id);
        uint32_t *fids = NULL;
        ssize_t count;
@@ -506,18 +556,10 @@ int dbdisk_del(dbtxn *txn, dbindex *db, dbdisk *disk) {
        // Remove disk
        key.mv_data = &disk->id;
        key.mv_size = sizeof(disk->id);
-       if (res = mdb_del(tx, db->disk, &key, NULL))
-               goto fail;
-
-       printf("ok\n");
-       free(fids);
-       mdb_txn_commit(tx);
-       //mdb_txn_abort(tx);
-       return 0;
+       res = mdb_del(tx, db->disk, &key, NULL);
 
 fail:
        free(fids);
-       mdb_txn_abort(tx);
        return res;
 }
 
@@ -558,6 +600,27 @@ char *dbfile_full_path(dbtxn *tx, dbindex *db, dbfile *file) {
        return file->full_path;
 }
 
+int dbfile_inlist(dbtxn *tx, dbindex *db, dbid_t fileid, dbid_t listid) {
+       struct dblistfile datval = { .listid = listid, .seq = 0 };
+       MDB_cursor *cursor;
+       MDB_val key = { .mv_data = &fileid, .mv_size = sizeof(fileid) };
+       MDB_val dat = { .mv_data = &datval, .mv_size = sizeof(datval) };
+       int is = 0;
+
+       if ((db->res = mdb_cursor_open(tx, db->list_by_file, &cursor)))
+               goto fail1;
+
+       if (db->res = mdb_cursor_get(cursor, &key, &dat, MDB_GET_BOTH_RANGE))
+               goto fail;
+
+       is = ((struct dblistfile *)dat.mv_data)->listid == listid;
+
+fail:
+       mdb_cursor_close(cursor);
+fail1:
+       return is;
+}
+
 void dbfile_free(dbfile *f) {
        if (f) {
                ez_blob_free(DBFILE_DESC, f);
@@ -603,16 +666,33 @@ int dbfile_del_id(dbtxn *tx, dbindex *db, int fileid) {
        return db->res;
 }
 
-int dbfile_del(dbtxn *txn, dbindex *db, dbfile *f) {
-       MDB_txn *tx;
-       MDB_val key, data;
-       int res;
+// histogram table
+struct hist_node {
+       struct ez_node sn;
+       dbid_t key;
+       unsigned int count;
+};
 
-       mdb_txn_begin(db->env, txn, 0, &tx);
+static unsigned int hist_hash(const void *p) {
+       const struct hist_node *n = p;
+
+       return ez_hash_int32(n->key);
+}
+
+static int hist_equals(const void *p, const void *q) {
+       const struct hist_node *a = p;
+       const struct hist_node *b = q;
+
+       return a->key == b->key;
+}
+
+int dbfile_del(dbtxn *tx, dbindex *db, dbfile *f) {
+       MDB_val key, dat;
+       int res;
 
        // Remove secondary keys / constraints
-       data.mv_data = &f->id;
-       data.mv_size = sizeof(f->id);
+       dat.mv_data = &f->id;
+       dat.mv_size = sizeof(f->id);
 
        // - by disk+path (and unique constraint)
        char *dpath = dbfile_path(f);
@@ -627,14 +707,14 @@ int dbfile_del(dbtxn *txn, dbindex *db, dbfile *f) {
        // - by diskid
        key.mv_data = &f->diskid;
        key.mv_size = sizeof(f->diskid);
-       if (res = mdb_del(tx, db->file_by_disk, &key, &data))
+       if (res = mdb_del(tx, db->file_by_disk, &key, &dat))
                goto fail;
 
        // - by title
        if (f->title) {
                key.mv_data = f->title;
                key.mv_size = strlen(f->title);
-               if (res = mdb_del(tx, db->file_by_title, &key, &data))
+               if (res = mdb_del(tx, db->file_by_title, &key, &dat))
                        goto fail;
        }
 
@@ -642,7 +722,7 @@ int dbfile_del(dbtxn *txn, dbindex *db, dbfile *f) {
        if (f->artist) {
                key.mv_data = f->artist;
                key.mv_size = strlen(f->artist);
-               if (res = mdb_del(tx, db->file_by_artist, &key, &data))
+               if (res = mdb_del(tx, db->file_by_artist, &key, &dat))
                        goto fail;
        }
 
@@ -652,58 +732,88 @@ int dbfile_del(dbtxn *txn, dbindex *db, dbfile *f) {
        if (res = mdb_del(tx, db->file, &key, NULL))
                goto fail;
 
-       // check lists
+       // check lists, FIXME: cleanup
        {
                MDB_cursor *cursor;
-               size_t alloc = 256;
-               size_t size = 0;
-               struct dblistfile *list = malloc(sizeof(*list) * alloc);
+               ez_array array = { 0 };
 
-               // FIXME: goto fail leaks list
+               // get all lists this file is in
                if ((res = mdb_cursor_open(tx, db->list_by_file, &cursor)))
-                       goto fail;
+                       goto fail1;
 
-               res = mdb_cursor_get(cursor, &key, &data, MDB_SET);
-               printf("set list by file: %d\n", res);
+               res = mdb_cursor_get(cursor, &key, &dat, MDB_SET);
                while (res == 0) {
-                       printf(" list: %d @ %d\n", ((struct dblistfile *)data.mv_data)->listid, ((struct dblistfile *)data.mv_data)->seq);
-                       if (size >= alloc) {
-                               alloc *= 2;
-                               list = realloc(list, sizeof(*list) * alloc);
-                       }
-                       list[size++] = *(struct dblistfile *)data.mv_data;
-
-                       res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
+                       if (ez_array_add(&array, dat.mv_data, dat.mv_size))
+                               res = mdb_cursor_get(cursor, &key, &dat, MDB_NEXT_DUP);
+                       else
+                               res = ENOMEM;
                }
+               printf("get list contents: res=%d\n", res);
                mdb_cursor_close(cursor);
+               if (res != MDB_NOTFOUND)
+                       goto fail1;
 
+               // delete the entry we just read
                if (res = mdb_del(tx, db->list_by_file, &key, NULL))
-                       goto fail;
+                       goto fail1;
 
-               printf("list entries: %zd\n", size);
+               struct dblistfile *files = array.ea_data;
+               int count = array.ea_size / sizeof(*files);
 
-               for (int i=0;i<size;i++) {
+               // delete all entries in the lists
+               printf("list entries: %d\n", count);
+               for (int i=0;i<count;i++) {
                        struct dbfilelist fdata = {
-                               .seq = list[i].seq,
+                               .seq = files[i].seq,
                                .fileid = f->id
                        };
-                       printf("delete file %d from list %d @ %d\n", fdata.fileid, list[i].listid, fdata.seq);
-                       key.mv_data = &list[i].listid;
-                       key.mv_size = sizeof(list[i].listid);
-                       data.mv_data = &fdata;
-                       data.mv_size = sizeof(fdata);
-                       if (res = mdb_del(tx, db->file_by_list, &key, &data))
-                               goto fail;
+                       printf("delete file %d from list %d @ %d\n", fdata.fileid, files[i].listid, fdata.seq);
+                       key.mv_data = &files[i].listid;
+                       key.mv_size = sizeof(files[i].listid);
+                       dat.mv_data = &fdata;
+                       dat.mv_size = sizeof(fdata);
+                       if (res = mdb_del(tx, db->file_by_list, &key, &dat))
+                               goto fail1;
                }
-               free(list);
-       }
 
-       mdb_txn_commit(tx);
-       return res;
+               // update all the list counts
+               ez_set counts = EZ_INIT_SET(counts, hist_hash, hist_equals, free);
+               for (int i=0;i<count;i++) {
+                       struct hist_node hk = { .key = files[i].listid };
+                       struct hist_node *hn;
+
+                       if (hn = ez_set_get(&counts, &hk))
+                               hn->count += 1;
+                       else if (hn = malloc(sizeof(*hn))) {
+                               hn->key = hk.key;
+                               hn->count = 1;
+                               ez_set_put(&counts, hn);
+                       } else {
+                               res = ENOMEM;
+                               goto fail2;
+                       }
+               }
+               ez_set_scan scan;
+               for (struct hist_node *hn = ez_set_scan_init(&counts, &scan); res == 0 && hn; hn = ez_set_scan_next(&scan)) {
+                       dblist *list = dblist_get(tx, db, hn->key);
+
+                       if (list) {
+                               printf("update '%s' %d -> %d\n", list->name, list->size, list->size - hn->count);
+                               list->size -= hn->count;
+                               res = dblist_put(tx, db, list, 0);
+                       } else {
+                               res = db->res;
+                       }
+                       dblist_free(list);
+               }
 
- fail:
-       printf("del failed: %s\n", mdb_strerror(res));
-       mdb_txn_abort(tx);
+               ez_set_clear(&counts);
+       fail2:
+
+       fail1:
+               ez_array_clear(&array);
+       }
+fail:
        return res;
 }
 
@@ -719,14 +829,12 @@ int dbstrcmp(const char *a, const char *b) {
        return strcmp(a, b);
 }
 
-// update o with any changed values
-int dbfile_update(dbtxn *txn, dbindex *db, dbfile *o, dbfile *f) {
-       MDB_txn *tx;
+// update file with any changed values
+// fixes secondary indices
+int dbfile_update(dbtxn *tx, dbindex *db, dbfile *o, dbfile *f) {
        MDB_val key, data;
        int res;
 
-       mdb_txn_begin(db->env, txn, 0, &tx);
-
        // Update secondary keys
        data.mv_data = &f->id;
        data.mv_size = sizeof(f->id);
@@ -775,29 +883,18 @@ int dbfile_update(dbtxn *txn, dbindex *db, dbfile *o, dbfile *f) {
                goto fail;
 
        ez_basic_encode_raw(DBFILE_DESC, f, (ez_blob *)&data);
-
-       return mdb_txn_commit(tx);
-
- fail:
-       mdb_txn_abort(tx);
+fail:
        return res;
 }
 
-int dbfile_add(MDB_txn *txn, dbindex *db, dbfile *f) {
-       MDB_txn *tx;
+int dbfile_add(MDB_txn *tx, dbindex *db, dbfile *f) {
        MDB_val key, data;
        int res;
 
-       mdb_txn_begin(db->env, txn, 0, &tx);
-
        // Check foreign constraints
-       key.mv_data = &f->diskid;
-       key.mv_size = sizeof(f->diskid);
-       res = mdb_get(tx, db->disk, &key, &data);
-       if (res == MDB_NOTFOUND) {
-               mdb_txn_abort(tx);
+       if (!primary_exists(tx, db, f->diskid, db->disk)) {
                fprintf(stderr, "FOREIGN KEY: file with unknown disk\n");
-               return res;
+               return db->res;
        }
 
        // Store file
@@ -853,305 +950,70 @@ int dbfile_add(MDB_txn *txn, dbindex *db, dbfile *f) {
                        goto fail;
        }
 
-       return mdb_txn_commit(tx);
-
  fail:
-       mdb_txn_abort(tx);
        return res;
 }
 
+/*
+  Player support functions
+*/
 
-// TODO: this can be made generic for other indices, see later on
-#if 0
-struct dbscan {
-       dbindex *db;
-       MDB_cursor *cursor;
-       MDB_val key, data;
-       int keyval;
-       int index;
-       int count;
-};
-
-dbscan *dbfile_scan_disk(dbtxn *tx, dbindex *db, int diskid) {
-       dbscan *scan = malloc(sizeof(*scan));
-       int res;
+// A way to iterate through a lit of files, based on an index or something else
 
-       scan->db = db;
-       scan->cursor = NULL;
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
 
-       scan->keyval = diskid;
-       scan->key.mv_data = &scan->keyval;
-       scan->key.mv_size = sizeof(scan->keyval);
+/**
+ * Check if the disk is mounted.
+ *
+ * This is not generally absolutely reliable but is in the context of
+ * disk-monitor managing the mounts.
+ *
+ * It can be used for quickly discarding files that can't be mounted.
+ *
+ * This is super-slow, don't bother using it, performing a stat on the file
+ * will suffice.
+ */
+int dbdisk_mounted(dbdisk *disk) {
+#if 0
+       // Check the device of the entries
+       char parent[strlen(disk->mount)+1];
+       char *slash = strrchr(parent, '/');
 
-       if ((res = mdb_cursor_open(tx, db->file_by_disk, &scan->cursor)))
-               goto fail;
+       if (slash) {
+               struct stat pst, mst;
 
-       if (diskid != -1)
-               res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_SET);
-       else
-               res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_FIRST);
+               *slash = 0;
 
-       if (res) {
-               if (res == MDB_NOTFOUND) {
-                       scan->count = 0;
-                       scan->index = 0;
-                       return scan;
-               }
-               goto fail;
+               // Check if it's already mounted
+               return (stat(disk->mount, &mst) == 0
+                       && stat(parent, &pst) == 0
+                       && mst.st_dev != pst.st_rdev);
        }
 
-       if ((res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_MULTIPLE)))
-               goto fail;
-
-       scan->count = scan->data.mv_size / sizeof(int);
-       scan->index = 0;
-
-       return scan;
-
- fail:
-       fprintf(stderr, "db scan open fail: %s\n", mdb_strerror(res));
-       dbfile_scan_close(scan);
-       return NULL;
-}
-
-uint32_t dbfile_scan_next(dbscan *scan) {
-       int res = 0;
+       return 0;
+#else
+       // See if the directory is empty
+       // yikes, this is slow as fuck
+       DIR *d = opendir(disk->mount);
+       int entries = 0;
 
-       while (scan->count > 0) {
-               if (scan->index < scan->count)
-                       return ((int *)scan->data.mv_data)[scan->index++];
+       if (d) {
+               struct dirent *de;
 
-               if (res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_NEXT_MULTIPLE)) {
-                       if (res == MDB_NOTFOUND && scan->keyval == -1) {
-                               res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_NEXT);
-                               if (res == 0)
-                                       res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_MULTIPLE);
-                       }
-                       if (res)
-                               goto fail;
+               while (entries == 0 && (de = readdir(d))) {
+                       if (strcmp(de->d_name, ".") == 0
+                           || strcmp(de->d_name, "..") == 0)
+                               continue;
+                       entries++;
                }
-
-               scan->count = scan->data.mv_size / sizeof(int);
-               scan->index = 0;
+               closedir(d);
        }
 
-       return ~0;
- fail:
-       if (res != MDB_NOTFOUND)
-               fprintf(stderr, "db scan fail: %s\n", mdb_strerror(res));
-       return ~0;
-}
-
-void dbfile_scan_close(dbscan *scan) {
-       if (scan->cursor)
-               mdb_cursor_close(scan->cursor);
-       free(scan);
-}
-#endif
-
-/**
- * Create a newly shuffled playlist.
- */
-void dbshuffle_init(dbindex *db) {
-       dbtxn *tx;
-       dbscan *scan;
-       uint32_t fid;
-       int fids_size = 4096;
-       int count = 0;
-       int res;
-       uint32_t *fids = malloc(sizeof(*fids) * fids_size);
-
-       // find all current fids
-       mdb_txn_begin(db->env, NULL, 0, &tx);
-       scan = dbfile_scan_disk(tx, db, -1);
-       while ((fid = dbfile_scan_next(scan)) != ~0) {
-               if (count >= fids_size) {
-                       fids_size *= 2;
-                       fids = realloc(fids, sizeof(*fids) * fids_size);
-               }
-               fids[count++] = fid;
-       }
-       printf("total %d\n", count);
-       dbfile_scan_close(scan);
-
-       // now write them randomly
-       mdb_drop(tx, db->shuffle, 0);
-       mdb_drop(tx, db->shuffle_by_file, 0);
-
-       for (int i=0;i<count;i++) {
-               int j = random() % (count-i);
-               uint32_t seq = i + 1;
-               MDB_val key, data;
-
-               fid = fids[i+j];
-               fids[i+j] = fids[i];
-
-               key.mv_size = sizeof(int);
-               key.mv_data = &seq;
-               data.mv_size = sizeof(int);
-               data.mv_data = &fid;
-
-               res = mdb_put(tx, db->shuffle, &key, &data, MDB_NOOVERWRITE);
-               res = mdb_put(tx, db->shuffle_by_file, &data, &key, MDB_NOOVERWRITE);
-       }
-       free(fids);
-
-       dbindex_commit(tx);
-}
-
-// create shuffled playlist
-// TODO: start from an existing playlist?
-void dbshuffle_init2(dbindex *db) {
-       dbtxn *tx;
-       dbscan *scan;
-       uint32_t fid;
-       int fids_size = 4096;
-       int count = 0;
-       int res;
-       uint32_t *fids = malloc(sizeof(*fids) * fids_size);
-
-       // TODO: count? just get it from the thing and scan aagain?
-       // find all current fids
-       mdb_txn_begin(db->env, NULL, 0, &tx);
-
-       scan = dbfile_scan_disk(tx, db, -1);
-       while ((fid = dbfile_scan_next(scan)) != ~0) {
-               if (count >= fids_size) {
-                       fids_size *= 2;
-                       fids = realloc(fids, sizeof(*fids) * fids_size);
-               }
-               fids[count++] = fid;
-       }
-       printf("total %d\n", count);
-       dbfile_scan_close(scan);
-
-       struct dblist list = {
-               .size = count,
-               .name = "shuffle", // maybe i do want them unique after-all?
-               .comment = ""
-       };
-       struct dbfilelist fvalue;
-       struct dblistfile rvalue;
-       MDB_val fkey, fdata;
-       MDB_val rkey, rdata;
-
-       if ((res = dblist_add(tx, db, &list))) {
-               printf("add list\n");
-               goto fail;
-       }
-
-       printf("list add ok id=%d\n", list.id);
-
-       fkey.mv_data = &list.id;
-       fkey.mv_size = sizeof(uint32_t);
-       fdata.mv_data = &fvalue;
-       fdata.mv_size = sizeof(fvalue);
-
-       rkey.mv_size = sizeof(uint32_t);
-       rdata.mv_data = &rvalue;
-       rdata.mv_size = sizeof(rvalue);
-       rvalue.listid = list.id;
-
-       // TODO: can shuffle have repeats??
-
-       // Playlist instead:
-       // [list] [seq][file]
-       // ... only iterate by seq?
-       // ... how to find playlist by file?
-       // ... or just jump by seq?
-
-       for (int i=0;i<count;i++) {
-               int j = random() % (count-i);
-               uint32_t seq = i + 1;
-
-               fid = fids[i+j];
-               fids[i+j] = fids[i];
-
-               fvalue.seq = seq;
-               fvalue.fileid = fid;
-
-               rvalue.seq = seq;
-               rkey.mv_data = &fid;
-
-               printf(" %d->%d\n", seq, fid);
-
-               if ((res = mdb_put(tx, db->file_by_list, &fkey, &fdata, MDB_NODUPDATA)))
-                       goto fail;
-
-               if ((res = mdb_put(tx, db->list_by_file, &rkey, &rdata, MDB_NODUPDATA)))
-                       goto fail;
-       }
-       free(fids);
-
-       dbindex_commit(tx);
-       return;
-fail:
-       printf("reason: %s\n", mdb_strerror(res));
-       free(fids);
-       mdb_txn_abort(tx);
-       return;
-}
-
-/*
-  Player support functions
-*/
-
-// A way to iterate through a lit of files, based on an index or something else
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-
-/**
- * Check if the disk is mounted.
- *
- * This is not generally absolutely reliable but is in the context of
- * disk-monitor managing the mounts.
- *
- * It can be used for quickly discarding files that can't be mounted.
- *
- * This is super-slow, don't bother using it, performing a stat on the file
- * will suffice.
- */
-int dbdisk_mounted(dbdisk *disk) {
-#if 0
-       // Check the device of the entries
-       char parent[strlen(disk->mount)+1];
-       char *slash = strrchr(parent, '/');
-
-       if (slash) {
-               struct stat pst, mst;
-
-               *slash = 0;
-
-               // Check if it's already mounted
-               return (stat(disk->mount, &mst) == 0
-                       && stat(parent, &pst) == 0
-                       && mst.st_dev != pst.st_rdev);
-       }
-
-       return 0;
-#else
-       // See if the directory is empty
-       // yikes, this is slow as fuck
-       DIR *d = opendir(disk->mount);
-       int entries = 0;
-
-       if (d) {
-               struct dirent *de;
-
-               while (entries == 0 && (de = readdir(d))) {
-                       if (strcmp(de->d_name, ".") == 0
-                           || strcmp(de->d_name, "..") == 0)
-                               continue;
-                       entries++;
-               }
-               closedir(d);
-       }
-
-       return entries > 0;
-#endif
+       return entries > 0;
+#endif
 }
 
 
@@ -1268,180 +1130,144 @@ int dbfile_prev(dbindex *db, dbfile **f, char **fpath) {
        return dbfile_iterate(db, f, fpath, MDB_LAST, MDB_PREV);
 }
 
-// this is a bit different to the by-path shuffle, since the path is used again as output rather
-// than the iteration key
-static int dbfile_iterate_shuffle(dbindex *db, dbfile **fp, char **pathp, int first, int next) {
-       MDB_txn *tx;
-       MDB_val key, data;
-       MDB_cursor *cursor;
-       dbfile *file = NULL;
-       int res;
+/*
+         Scan based on secondary index.
 
-       mdb_txn_begin(db->env, NULL, MDB_RDONLY, &tx);
+       key.mv_data = &f->diskid;
+       key.mv_size = sizeof(f->diskid);
 
-       if ((res = mdb_cursor_open(tx, db->shuffle, &cursor)))
-       //if ((res = mdb_cursor_open(tx, db->shuffle_by_file, &cursor)))
-               goto fail;
-#if 0
-       printf("scan shuffle\n");
-       res = mdb_cursor_get(cursor, &key, &data, first);
-       while (res == 0) {
-               printf(" seq %d val %d\n", *((int *)key.mv_data), *((int *)data.mv_data));
-               res = mdb_cursor_get(cursor, &key, &data, next);
-       }
+       data.mv_data = &f->id;
+       data.mv_size = sizeof(f->id);
 
-       return 0;
-#endif
+       //res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_BOTH);
 
-       /*
-         Scan based on shuffle order
-        */
-       int keyval =  *fp ? ((*fp)->id) : -1;
-       dbdisk *disk = *fp ? dbdisk_get(tx, db, (*fp)->diskid) : NULL;
-       //int mounted = *fp ? dbdisk_mounted(disk) : 0;
+*/
 
+/* playlist management */
 
-       //printf("shuffle next, fid=%d\n", keyval);
+// internal put command - flags might include MDB_NOOVERWRITE
+static int dblist_put(dbtxn *tx, dbindex *db, dblist *list, unsigned int flags) {
+       MDB_val key = { .mv_data = &list->id, .mv_size = sizeof(list->id) };
+       MDB_val dat = { .mv_data = NULL, .mv_size = ez_basic_size(DBLIST_DESC, list) };
+       int res;
 
-       dbfile_free(*fp);
-       free(*pathp);
-       *fp = NULL;
-       *pathp = NULL;
+       if (res = mdb_put(tx, db->list, &key, &dat, flags | MDB_RESERVE))
+               return res;
 
-       if (keyval != -1) {
-               data.mv_data = &keyval;
-               data.mv_size = sizeof(keyval);
+       ez_basic_encode_raw(DBLIST_DESC, list, (ez_blob *)&dat);
 
-               res = mdb_get(tx, db->shuffle_by_file, &data, &key);
-               //printf("get by file = %d, id=%d\n", res, *((int *)key.mv_data));
-               if (res == MDB_NOTFOUND) {
-                       //printf("Not found\n");
-                       return -1;
-               }
+       return 0;
+}
 
-               res = mdb_cursor_get(cursor, &key, &data, MDB_SET);
-               //printf(" got shuffle id=%d fid=%d\n", *((int *)key.mv_data), *(int *)data.mv_data);
+dblist *dblist_get(dbtxn *tx, dbindex *db, int id) {
+       MDB_val key = { .mv_data = &id, .mv_size = sizeof(id) };
 
-               res = mdb_cursor_get(cursor, &key, &data, next);
-               //printf(" next shuffle id=%d fid=%d\n", *((int *)key.mv_data), *(int *)data.mv_data);
-       } else {
-               res = mdb_cursor_get(cursor, &key, &data, first);
-       }
+       return primary_get_decode(tx, db, DBLIST_DESC, &key, db->list);
+}
 
-       while (file == NULL && res == 0) {
-               file = primary_get_decode(tx, db, DBFILE_DESC, &data, db->file);
-               if (file) {
-                       int keep;
+dblist *dblist_get_name(dbtxn *tx, dbindex *db, const char *name) {
+       MDB_val key  = {
+               .mv_data = (void *)name,
+               .mv_size = strlen(name)
+       };
 
-                       //printf("loaded: %d[%zd] %d?\n", *(int *)data.mv_data, data.mv_size, file->id);
+       return secondary_get_decode(tx, db, DBLIST_DESC, &key, db->list, db->list_by_name);
+}
 
-                       if (disk == NULL || file->diskid != disk->id) {
-                               dbdisk_free(disk);
-                               disk = dbdisk_get(tx, db, file->diskid);
-                               //mounted = dbdisk_mounted(disk);
-                       }
-                       //keep = mounted;
-                       //keep = keep && file->duration > 0;
-                       keep = file->duration > 0;
-                       if (keep) {
-                               char path[strlen(disk->mount) + strlen(file->path) + 1];
-                               struct stat st;
+dbid_t dblistid_get_name(dbtxn *tx, dbindex *db, const char *name) {
+       MDB_val key  = {
+               .mv_data = (void *)name,
+               .mv_size = strlen(name)
+       };
+       MDB_val dat;
 
-                               sprintf(path, "%s%s", disk->mount, file->path);
+       db->res = mdb_get(tx, db->list_by_name, &key, &dat);
 
-                               keep = lstat(path, &st) == 0 && S_ISREG(st.st_mode);
-                               if (keep) {
-                                       *pathp = strdup(path);
-                                       *fp = file;
-                               }
-                       }
+       return db->res == 0 ? *(dbid_t *)dat.mv_data : 0;
+}
 
-                       if (!keep) {
-                               dbfile_free(file);
-                               file = NULL;
-                       }
-               }
-               if (file == NULL)
-                       res = mdb_cursor_get(cursor, &key, &data, next);
-       }
+void dblist_free(dblist *f) {
+       ez_blob_free(DBLIST_DESC, f);
+}
 
-       //free(keyval);
-       dbdisk_free(disk);
+// put ?  add ?  d->id == 0 -> then add, otherwise put?
+int dblist_add(MDB_txn *txn, dbindex *db, dblist *list) {
+       MDB_txn *tx;
+       MDB_val key, dat;
+       int res;
 
-       mdb_cursor_close(cursor);
-       mdb_txn_commit(tx);
+       mdb_txn_begin(db->env, txn, 0, &tx);
 
-       //printf("laoded fid=%d\n", file->id);
+       // Store record
+       list->id = list_next_id(db);
+       if (res = dblist_put(tx, db, list, MDB_NOOVERWRITE))
+               goto fail;
 
-       return res;
+       // secondary keys
+       dat.mv_data = &list->id;
+       dat.mv_size = sizeof(list->id);
+
+       // - by name
+       key.mv_data = list->name;
+       key.mv_size = strlen(list->name);
+       if (res = mdb_put(tx, db->list_by_name, &key, &dat, MDB_NOOVERWRITE))
+               goto fail;
+
+       return mdb_txn_commit(tx);
  fail:
        mdb_txn_abort(tx);
-
        return res;
 }
 
-int dbfile_next_shuffle(dbindex *db, dbfile **f, char **fpath) {
-       return dbfile_iterate_shuffle(db, f, fpath, MDB_FIRST, MDB_NEXT);
-}
-
-int dbfile_prev_shuffle(dbindex *db, dbfile **f, char **fpath) {
-       return dbfile_iterate_shuffle(db, f, fpath, MDB_LAST, MDB_PREV);
-}
-
-/*
-         Scan based on secondary index.
-
-       key.mv_data = &f->diskid;
-       key.mv_size = sizeof(f->diskid);
-
-       data.mv_data = &f->id;
-       data.mv_size = sizeof(f->id);
-
-       //res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_BOTH);
-
-*/
+int dblist_clear(dbtxn *tx, dbindex *db, int listid) {
+       MDB_val key, data;
+       int res;
 
-/* playlist management */
-dblist *dblist_get(dbtxn *tx, dbindex *db, int id) {
-       MDB_val key = { .mv_data = &id, .mv_size = sizeof(id) };
+       ez_array array = { 0 };
+       struct dbfilelist *list;
+       size_t count;
 
-       return primary_get_decode(tx, db, DBLIST_DESC, &key, db->list);
-}
+       key.mv_data = &listid;
+       key.mv_size = sizeof(listid);
 
-void dblist_free(dblist *f) {
-       ez_blob_free(DBLIST_DESC, f);
-}
+       res = secondary_list_key(tx, db->file_by_list, key, &array);
+       list = array.ea_data;
+       count = array.ea_size / sizeof(*list);
+       if (count) {
+               printf("found %zd entries\n", count);
+               // delete list values
+               key.mv_data = &listid;
+               key.mv_size = sizeof(listid);
+               if (res = mdb_del(tx, db->file_by_list, &key, NULL)) {
+                       printf(" delete list fail\n");
+                       goto fail;
+               }
 
-int dblist_reset(dbtxn *tx, dbindex *db) {
-       mdb_drop(tx, db->list, 0);
-       mdb_drop(tx, db->file_by_list, 0);
-       return mdb_drop(tx, db->list_by_file, 0);
-}
+               // delete reverse table entries
+               struct dblistfile entry = { .listid = listid };
 
-// put ?  add ?  d->id == 0 -> then add, otherwise put?
-int dblist_add(MDB_txn *txn, dbindex *db, dblist *d) {
-       MDB_txn *tx;
-       MDB_val key, data;
-       int res;
+               data.mv_data = &entry;
+               data.mv_size = sizeof(entry);
 
-       mdb_txn_begin(db->env, txn, 0, &tx);
+               for (int i=0;i<count;i++) {
+                       key.mv_data = &list[i].fileid;
+                       key.mv_size = sizeof(list[i].fileid);
 
-       // Store record
-       d->id = list_next_id(db);
-       key.mv_data = &d->id;
-       key.mv_size = sizeof(d->id);
+                       entry.seq = list[i].seq;
 
-       data.mv_size = ez_basic_size(DBLIST_DESC, d);
-       data.mv_data = NULL;
-       res = mdb_put(tx, db->list, &key, &data, MDB_NOOVERWRITE | MDB_RESERVE);
-       if (res == 0) {
-               ez_basic_encode_raw(DBLIST_DESC, d, (ez_blob *)&data);
-               mdb_txn_commit(tx);
-       } else {
-               printf("db put list fail: %s\n", mdb_strerror(res));
-               mdb_txn_abort(tx);
+                       if (res = mdb_del(tx, db->list_by_file, &key, &data)) {
+                               printf(" delete fail fid=%d  -> list=%d seq=%d\n", list[i].fileid, entry.listid, entry.seq);
+                               if (res == MDB_NOTFOUND) {
+                                       printf(" db inconsistent, continuing\n");
+                               } else
+                                       goto fail;
+                       }
+               }
        }
 
+fail:
+       free(list);
+       printf("db clear list: %s\n", mdb_strerror(res));
        return res;
 }
 
@@ -1499,440 +1325,347 @@ fail:
        return -1;
 }
 
-int dblist_add_file(MDB_txn *txn, dbindex *db, dblist *d, int fileid) {
-       MDB_txn *tx;
-       MDB_val key, data;
+// info is in/out, in=listid, fileid, out=listid, seq, fileid
+int dblist_add_file(MDB_txn *tx, dbindex *db, dblistcursor *info) {
+       MDB_val key, dat;
        int res;
-       struct dbfilelist fvalue = { .seq = d->size + 1, .fileid = fileid };
-       struct dblistfile rvalue = { .listid = d->id, .seq = d->size + 1 };
+       dblist *list = dblist_get(tx, db, info->listid);
 
-       mdb_txn_begin(db->env, txn, 0, &tx);
+       if (!list)
+               return db->res;
 
        // Check file exists
-       key.mv_data = &fileid;
-       key.mv_size = sizeof(fileid);
-       if (mdb_get(tx, db->file, &key, &data) != 0) {
+       if (!primary_exists(tx, db, info->fileid, db->file)) {
                printf("FOREIGN: file doesn't exist\n");
-               goto fail;
+               dblist_free(list);
+               return db->res;
        }
 
-       // TODO: foriegn constraint on listid ... or just take list name and look it up
+       struct dbfilelist fvalue = { .seq = list->size + 1, .fileid = info->fileid };
+       struct dblistfile rvalue = { .listid = list->id, .seq = list->size + 1 };
 
-       key.mv_data = &d->id;
-       key.mv_size = sizeof(d->id);
-       data.mv_data = &fvalue;
-       data.mv_size = sizeof(fvalue);
+       key.mv_data = &list->id;
+       key.mv_size = sizeof(list->id);
+       dat.mv_data = &fvalue;
+       dat.mv_size = sizeof(fvalue);
 
-       printf("put file by list: { listid = %d } <- { seq = %d fileid = %d }\n", d->id, fvalue.seq, fvalue.fileid);
+       printf("put file by list: { listid = %d } <- { seq = %d fileid = %d }\n", list->id, fvalue.seq, fvalue.fileid);
 
-       if ((res = mdb_put(tx, db->file_by_list, &key, &data, MDB_NODUPDATA)))
+       if ((res = mdb_put(tx, db->file_by_list, &key, &dat, MDB_NODUPDATA)))
                goto fail;
 
-       key.mv_data = &fileid;
-       key.mv_size = sizeof(fileid);
-       data.mv_data = &rvalue;
-       data.mv_size = sizeof(rvalue);
+       key.mv_data = &info->fileid;
+       key.mv_size = sizeof(info->fileid);
+       dat.mv_data = &rvalue;
+       dat.mv_size = sizeof(rvalue);
 
-       printf("put list by file: fileid = %d { listid = %d .seq = %d }\n", fileid, rvalue.listid, rvalue.seq);
+       printf("put list by file: fileid = %d { listid = %d .seq = %d }\n", info->fileid, rvalue.listid, rvalue.seq);
 
-       if ((res = mdb_put(tx, db->list_by_file, &key, &data, MDB_NODUPDATA)))
+       if ((res = mdb_put(tx, db->list_by_file, &key, &dat, MDB_NODUPDATA)))
                goto fail;
 
        // update list record with changed size
        // TODO: can i just poke in the size value?
-       d->size += 1;
-
-       key.mv_data = &d->id;
-       key.mv_size = sizeof(d->id);
-
-       data.mv_size = ez_basic_size(DBLIST_DESC, d);
-       data.mv_data = NULL;
-       res = mdb_put(tx, db->list, &key, &data, MDB_RESERVE);
-       if (res == 0)
-               ez_basic_encode_raw(DBLIST_DESC, d, (ez_blob *)&data);
-       printf("update seq %d\n", d->size);
-
+       list->size += 1;
+       printf("update seq %d\n", list->size);
+       res = dblist_put(tx, db, list, 0);
        if (res == 0)
-               return mdb_txn_commit(tx);
+               info->seq = fvalue.seq;
 fail:
-       printf("fail: %s\n", mdb_strerror(res));
-       mdb_txn_abort(tx);
+       dblist_free(list);
        return res;
 }
 
-void dblist_dump(dbtxn *tx, dbindex *db) {
-       //MDB_txn *tx;
-       MDB_val key, data;
-       MDB_cursor *cursor;
-       int res;
-
-       //if (res = mdb_txn_begin(db->env, txn, MDB_RDONLY, &tx)) {
-       //      printf("failed: %s\n", mdb_strerror(res));
-       //      return;
-       //}
-
+// if the list exists clear it, otherwise create it
+dblist *dblist_init(dbtxn *tx, dbindex *db, const char *name) {
+       dblist *list = dblist_get_name(tx, db, name);
 
-       printf("dump all lists\n");
-       printf("list_by_file =\n");
-       res = mdb_cursor_open(tx, db->list_by_file, &cursor);
-       res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
-       while (res == 0) {
-               struct dblistfile *value = data.mv_data;
-               uint32_t fid = *(uint32_t *)key.mv_data;
-
-               printf(" file %d list %d seq %d\n", fid, value->listid, value->seq);
-
-               res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
-               if (res == MDB_NOTFOUND)
-                       res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+       if (list) {
+               if ((db->res = dblist_clear(tx, db, list->id)) ==0)
+                       return list;
+       } else {
+               if ((list = calloc(1, sizeof(*list)))
+                       && (list->name = strdup(name))
+                       && (db->res = dblist_add(tx, db, list)) == 0)
+                       return list;
        }
-       mdb_cursor_close(cursor);
-
-       printf("file_by_list =\n");
-       res = mdb_cursor_open(tx, db->file_by_list, &cursor);
-       res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
-       while (res == 0) {
-               struct dbfilelist *value = data.mv_data;
-               uint32_t lid = *(uint32_t *)key.mv_data;
+       dblist_free(list);
+       return NULL;
+}
 
-               printf(" list %d file %d seq %d\n", lid, value->fileid, value->seq);
+static void array_shuffle(dbid_t *ids, size_t count) {
+       for (size_t i=0;i<count;i++) {
+               size_t j = random() % (count-i);
+               int idi = ids[i];
+               int idj = ids[i+j];
 
-               res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
-               if (res == MDB_NOTFOUND)
-                       res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+               ids[i] = idj;
+               ids[i+j] = idi;
        }
-       mdb_cursor_close(cursor);
-
-
-       //mdb_txn_commit(tx);
 }
 
-// only list + seq is required
-int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) {
-       MDB_txn *tx;
-       MDB_val key, data;
-       MDB_cursor *cursor;
-       int res;
-       struct dbfilelist fvalue = { .seq = list->seq, .fileid = list->fileid };
-       struct dblistfile rvalue = { .listid = list->listid, .seq = list->seq };
-
-       printf("list_del_file: lid=%4d seq=%4d fid=%4d\n", list->listid, list->seq, list->fileid);
-
-       if (res = mdb_txn_begin(db->env, txn, 0, &tx))
-               goto fail0;
+// Set a playlist to a specific sequence
+int dblist_update(dbtxn *tx, dbindex *db, const char *name, dbid_t *fids, size_t count) {
+       dblist *list;
+       int res = 0;
 
-       int delcursor = 0;
-       int delfile = 0;
-       int dellist = 0;
+       list = dblist_init(tx, db, name);
+       if (!list)
+               return db->res;
 
-       if (list->seq == 0) {
-               // No sequence, lookup (first) fileid for the list
-               if (res = mdb_cursor_open(tx, db->list_by_file, &cursor))
-                       goto fail;
+       struct dbfilelist listvalue;
+       MDB_val listdata = { .mv_data= &listvalue, .mv_size = sizeof(listvalue) };
+       MDB_val listid = { .mv_data = &list->id, .mv_size = sizeof(dbid_t) };
 
-               key.mv_data = &list->fileid;
-               key.mv_size = sizeof(list->fileid);
-               data.mv_data = &rvalue;
-               data.mv_size = sizeof(rvalue);
+       struct dblistfile filevalue = { .listid = list->id };
+       MDB_val filedata = { .mv_data= &filevalue, .mv_size = sizeof(filevalue) };
+       MDB_val fileid = { .mv_size = sizeof(dbid_t) };
 
-               if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE))
+       for (size_t i=0;i<count;i++) {
+               listvalue.seq = i + 1;
+               listvalue.fileid = fids[i];
+               if ((res = mdb_put(tx, db->file_by_list, &listid, &listdata, MDB_NODUPDATA)))
                        goto fail;
 
-               fvalue.seq = list->seq = ((struct dblistfile *)data.mv_data)->seq;
-
-               printf("list_del_file: found seq=%4d\n", list->seq);
-
-               delcursor = 1;
-               delfile = 1;
-       } else if (list->fileid == 0) {
-               // Lookup fileid for list[seq]
-               if (res = mdb_cursor_open(tx, db->file_by_list, &cursor))
+               fileid.mv_data = &fids[i];
+               filevalue.seq = i + 1;
+               if ((res = mdb_put(tx, db->list_by_file, &fileid, &filedata, MDB_NODUPDATA)))
                        goto fail;
+       }
 
-               key.mv_data = &list->listid;
-               key.mv_size = sizeof(list->listid);
-               data.mv_data = &fvalue;
-               data.mv_size = sizeof(fvalue);
+       // update/fix list record
+       list->size = count;
+       res = dblist_put(tx, db, list, 0);
 
-               if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH))
-                       goto fail;
-
-               list->fileid = ((struct dbfilelist *)data.mv_data)->fileid;
+fail:
+       printf("db list update: %s\n", mdb_strerror(res));
+       dblist_free(list);
+       return res;
+}
 
-               printf("list_del_file: found fid=%4d\n", list->fileid);
+// Shuffle a specific list
+// load whole list
+// shuffle it
+// save shuffled version?
+int dblist_shuffle(dbtxn *tx, dbindex *db, const char *name) {
+       return -1;
+}
 
-               delcursor = 1;
-               dellist = 1;
-       } else {
-               // use supplied values
-               delfile = 1;
-               dellist = 1;
+// init sysstem lists, doesn't destroy any existing lists
+int dblist_init_system(dbtxn *tx, dbindex *db) {
+       int res = 0;
+       static const char *names[] = { "all", "all#shuffle", "jukebox", "jukebox#shuffle", "playnow", "playnow#shuffle", "shit" };
+       static const char *descs[] = { "All files ordered by path", "All files shuffled", "Enqueued songs in order", "Endqueued songs shuffled", "Record of play-now files in play order", "Record of play-now files shuffled", "The shitlist" };
+       dblist list = { 0 };
+
+       for (int i=0;res == 0 && i<sizeof(names)/sizeof(names[0]);i++) {
+               int tmp;
+
+               list.name = (char *)names[i];
+               list.desc = (char *)descs[i];
+               if (tmp = dblist_add(tx, db, &list)) {
+                       if (tmp == MDB_KEYEXIST)
+                               db->listid = find_next_id(tx, db->list);
+                       else
+                               res = tmp;
+               }
        }
+       return res;
+}
 
-       if (delcursor && (res = mdb_cursor_del(cursor, 0)))
-               goto fail;
-
-       if (delfile) {
-               key.mv_data = &list->listid;
-               key.mv_size = sizeof(list->listid);
-               data.mv_data = &fvalue;
-               data.mv_size = sizeof(fvalue);
+// remove all lists and recreate the all list
+int dblist_reset_all(dbtxn *tx, dbindex *db) {
+       int res;
 
-               if (res = mdb_del(tx, db->file_by_list, &key, &data))
-                       goto fail;
+       // empty all
+       if ((res = mdb_drop(tx, db->list, 0)) == 0
+               && (res = mdb_drop(tx, db->list_by_name, 0)) == 0
+               && (res = mdb_drop(tx, db->file_by_list, 0)) == 0
+               && (res = mdb_drop(tx, db->list_by_file, 0)) == 0) {
+               db->listid = 1;
+               if ((res = dblist_init_system(tx, db)) == 0)
+                       res = dblist_update_all(tx, db);
        }
+       return res;
+}
 
-       if (dellist) {
-               key.mv_data = &list->fileid;
-               key.mv_size = sizeof(list->fileid);
-               data.mv_data = &rvalue;
-               data.mv_size = sizeof(rvalue);
+// refresh the all list
+// list ordered by path
+int dblist_update_all(dbtxn *tx, dbindex *db) {
+       int res;
+       size_t count = 0;
+       dbid_t *fids = NULL;
+       ez_array array = { 0 };
 
-               if (res = mdb_del(tx, db->list_by_file, &key, &data))
-                       goto fail;
-       }
+       if (res = secondary_list_all(tx, db->file_by_path, &array))
+               goto fail;
 
-       if (delcursor)
-               mdb_cursor_close(cursor);
+       fids = array.ea_data;
+       count = array.ea_size / sizeof(*fids);
 
-       mdb_txn_commit(tx);
-       return 0;
+       // Create the all list
+       if (res = dblist_update(tx, db, "all", fids, count))
+               goto fail;
 
-fail:
-       printf("fail: %s\n", mdb_strerror(res));
-       if (delcursor)
-               mdb_cursor_close(cursor);
+       // Create the all shuffled list
+       array_shuffle(fids, count);
+       res = dblist_update(tx, db, "all#shuffle", fids, count);
 
-       mdb_txn_abort(tx);
-fail0:
+fail:
+       ez_array_clear(&array);
        return res;
 }
 
-#if 0
-int dblist_iterate(dbtxn *tx, dbindex *db, struct dblistcursor *pos) {
+// debug
+
+void dblist_dump(dbtxn *tx, dbindex *db) {
        //MDB_txn *tx;
        MDB_val key, data;
        MDB_cursor *cursor;
        int res;
 
-       ez_blob_free_raw(DBFILE_DESC, &pos->file);
-       memset(&pos->file, 0, sizeof(pos->file));
-
-       //printf("iterate list %d\n", pos->listid);
-
-       //if (res = mdb_txn_begin(db->env, txn, 0, &tx))
-       //      goto fail1;
-
-       //dblist_dump(tx, db);
-
-       if (pos->listid == 0) {
-               // just go by fileid order
-               if ((res = mdb_cursor_open(tx, db->file, &cursor)))
-                       goto fail0;
-
-               key.mv_data = &pos->fileid;
-               key.mv_size = sizeof(pos->fileid);
-
-               if (res = mdb_cursor_get(cursor, &key, &data, MDB_SET_RANGE))
-                       goto fail;
-               if (res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT))
-                       goto fail;
-
-               dbfile_decode_raw((ez_blob *)&data, &pos->file);
-               pos->fileid = pos->file.id = *(uint32_t*)key.mv_data;
-       } else {
-               // go by current info
-               if ((res = mdb_cursor_open(tx, db->file_by_list, &cursor)))
-                       goto fail0;
+       //if (res = mdb_txn_begin(db->env, txn, MDB_RDONLY, &tx)) {
+       //      printf("failed: %s\n", mdb_strerror(res));
+       //      return;
+       //}
 
-               struct dbfilelist thing = { .seq = pos->seq + 1, .fileid = 0 };
 
-               //printf(" from seq %d file %d\n", thing.seq, thing.fileid);
+       printf("dump all lists\n");
+       printf("list_by_file =\n");
+       res = mdb_cursor_open(tx, db->list_by_file, &cursor);
+       res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+       while (res == 0) {
+               struct dblistfile *value = data.mv_data;
+               uint32_t fid = *(uint32_t *)key.mv_data;
 
-               key.mv_data = &pos->listid;
-               key.mv_size = sizeof(pos->listid);
-               data.mv_data = &thing;
-               data.mv_size = sizeof(thing);
+               printf(" file %d list %d seq %d\n", fid, value->listid, value->seq);
 
-               if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE))
-                       goto fail;
+               res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
+               if (res == MDB_NOTFOUND)
+                       res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+       }
+       mdb_cursor_close(cursor);
 
+       printf("file_by_list =\n");
+       res = mdb_cursor_open(tx, db->file_by_list, &cursor);
+       res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+       while (res == 0) {
                struct dbfilelist *value = data.mv_data;
+               uint32_t lid = *(uint32_t *)key.mv_data;
 
-               pos->seq = value->seq;
-               pos->fileid = value->fileid;
-
-               key.mv_data = &pos->fileid;
-               key.mv_size = sizeof(pos->fileid);
-
-               if (res = mdb_get(tx, db->file, &key, &data))
-                       goto fail;
+               printf(" list %d file %d seq %d\n", lid, value->fileid, value->seq);
 
-               dbfile_decode_raw((ez_blob *)&data, &pos->file);
-               pos->file.id = pos->fileid;
+               res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
+               if (res == MDB_NOTFOUND)
+                       res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
        }
-
        mdb_cursor_close(cursor);
-       //mdb_txn_commit(tx);
-
-       return 0;
-fail:
-       mdb_cursor_close(cursor);
-fail0:
-       //mdb_txn_abort(tx);
-//fail1:
-       printf("fail: %s\n", mdb_strerror(res));
-       return res;
-}
-#endif
-#if 0
-static int dbfile_iterate_list(dbindex *db, int listid, dbfile **fp, int dir) {
-       MDB_txn *tx;
-       MDB_val key, data;
-       MDB_cursor *cursor;
-       dbfile *file = NULL;
-       int res;
-
-       mdb_txn_begin(db->env, NULL, MDB_RDONLY, &tx);
-
-       if ((res = mdb_cursor_open(tx, db->file_by_list, &cursor)))
-               goto fail;
-
-       int keyval =  *fp ? ((*fp)->id) : -1;
-       dbdisk *disk = *fp ? dbdisk_get(tx, db, (*fp)->diskid) : NULL;
-       int mounted = *fp ? dbdisk_mounted(disk) : 0;
 
-       //printf("\nlist iterate: fid=%d\n", keyval);
 
-       dbfile_free(*fp);
-       *fp = NULL;
+       //mdb_txn_commit(tx);
+}
 
-       /*
-         This mess is to handle various cases:
-         - first call
-         - next from file
-       */
+// only list + seq is required
+int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) {
+       MDB_txn *tx;
+       MDB_val key, data;
+       MDB_cursor *cursor;
+       int res;
+       struct dbfilelist fvalue = { .seq = list->seq, .fileid = list->fileid };
+       struct dblistfile rvalue = { .listid = list->listid, .seq = list->seq };
+
+       printf("list_del_file: lid=%4d seq=%4d fid=%4d\n", list->listid, list->seq, list->fileid);
 
-       if (keyval != -1) {
-               MDB_cursor *rcursor;
+       if (res = mdb_txn_begin(db->env, txn, 0, &tx))
+               goto fail0;
 
-               // find current sequence number via GET_BOTH lookup
-               res = mdb_cursor_open(tx, db->list_by_file, &rcursor);
+       int delcursor = 0;
+       int delfile = 0;
+       int dellist = 0;
 
-               struct dblistfile rval = { .listid = listid };
+       if (list->seq == 0) {
+               // No sequence, lookup (first) fileid for the list
+               if (res = mdb_cursor_open(tx, db->list_by_file, &cursor))
+                       goto fail;
 
-               key.mv_data = &keyval;
-               key.mv_size = sizeof(keyval);
+               key.mv_data = &list->fileid;
+               key.mv_size = sizeof(list->fileid);
+               data.mv_data = &rvalue;
+               data.mv_size = sizeof(rvalue);
 
-               data.mv_data = &rval;
-               data.mv_size = sizeof(rval);
+               if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE))
+                       goto fail;
 
-               //rval.seq = keyval - 1;
+               fvalue.seq = list->seq = ((struct dblistfile *)data.mv_data)->seq;
 
-               //printf("seek    : fileid=%d { listid=%d seq=%d }\n", keyval,  rval.listid, rval.seq);
+               printf("list_del_file: found seq=%4d\n", list->seq);
 
-               res = mdb_cursor_get(rcursor, &key, &data, MDB_GET_BOTH_RANGE);
-               if (res != 0) {
-                       //printf("seek failed = %s\n", mdb_strerror(res));
-                       mdb_cursor_close(rcursor);
+               delcursor = 1;
+               delfile = 1;
+       } else if (list->fileid == 0) {
+               // Lookup fileid for list[seq]
+               if (res = mdb_cursor_open(tx, db->file_by_list, &cursor))
                        goto fail;
-               }
-               //printf("position: fileid=%d { listid=%d seq=%d }\n", keyval,  rval.listid, rval.seq);
 
-               struct dblistfile *dval = data.mv_data;
-               struct dbfilelist fval = { .seq = dval->seq };
+               key.mv_data = &list->listid;
+               key.mv_size = sizeof(list->listid);
+               data.mv_data = &fvalue;
+               data.mv_size = sizeof(fvalue);
 
-               key.mv_data = &listid;
-               key.mv_size = sizeof(listid);
+               if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH))
+                       goto fail;
 
-               data.mv_data = &fval;
-               data.mv_size = sizeof(fval);
+               list->fileid = ((struct dbfilelist *)data.mv_data)->fileid;
 
-               //printf("seek    : listid=%d { seq = %d fileid=%d }\n", listid, fval.seq, fval.fileid);
-               res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH);
-               //printf("seek: %s\n", mdb_strerror(res));
-               //printf("position: listid=%d { seq = %d fileid=%d }\n", listid, fval.seq, fval.fileid);
-               res = mdb_cursor_get(cursor, &key, &data, dir == 0 ? MDB_NEXT_DUP : MDB_PREV_DUP);
-               //printf("next: %s\n", mdb_strerror(res));
-               //printf("position: listid=%d { seq = %d fileid=%d }\n", listid, fval.seq, fval.fileid);
+               printf("list_del_file: found fid=%4d\n", list->fileid);
 
-               mdb_cursor_close(rcursor);
+               delcursor = 1;
+               dellist = 1;
        } else {
-               key.mv_data = &listid;
-               key.mv_size = sizeof(listid);
-               res = mdb_cursor_get(cursor, &key, &data, MDB_SET);
-               if (res == 0 && dir == 1)
-                       res = mdb_cursor_get(cursor, &key, &data, MDB_LAST_DUP);
-
-               //struct dbfilelist *fval = data.mv_data;
-               //printf("first   : listid=%d { seq = %d fileid=%d }\n", listid, fval->seq, fval->fileid);
+               // use supplied values
+               delfile = 1;
+               dellist = 1;
        }
 
-       while (file == NULL && res == 0) {
-               struct dbfilelist *dval = data.mv_data;
-               MDB_val pkey = { sizeof(int), &dval->fileid };
-
-               file = primary_get_decode(tx, db, DBFILE_DESC, &pkey, db->file);
-               if (file) {
-                       int keep;
-
-                       //printf("loaded: %d[%d]\n", dval->fileid, dval->seq);
-
-                       if (disk == NULL || file->diskid != disk->id) {
-                               dbdisk_free(disk);
-                               disk = dbdisk_get(tx, db, file->diskid);
-                               mounted = dbdisk_mounted(disk);
-                       }
-                       keep = mounted;
-                       keep = keep && file->duration > 0;
-                       if (keep) {
-                               char path[strlen(disk->mount) + strlen(file->path) + 1];
-                               struct stat st;
+       if (delcursor && (res = mdb_cursor_del(cursor, 0)))
+               goto fail;
 
-                               sprintf(path, "%s%s", disk->mount, file->path);
+       if (delfile) {
+               key.mv_data = &list->listid;
+               key.mv_size = sizeof(list->listid);
+               data.mv_data = &fvalue;
+               data.mv_size = sizeof(fvalue);
 
-                               //printf("check %s\n", path);
+               if (res = mdb_del(tx, db->file_by_list, &key, &data))
+                       goto fail;
+       }
 
-                               keep = lstat(path, &st) == 0 && S_ISREG(st.st_mode);
-                               if (keep) {
-                                       file->full_path = strdup(path);
-                               }
-                       }
+       if (dellist) {
+               key.mv_data = &list->fileid;
+               key.mv_size = sizeof(list->fileid);
+               data.mv_data = &rvalue;
+               data.mv_size = sizeof(rvalue);
 
-                       if (!keep) {
-                               dbfile_free(file);
-                               file = NULL;
-                       }
-               }
-               if (file == NULL)
-                       res = mdb_cursor_get(cursor, &key, &data, dir == 0 ? MDB_NEXT_DUP : MDB_PREV_DUP);
+               if (res = mdb_del(tx, db->list_by_file, &key, &data))
+                       goto fail;
        }
 
-       //free(keyval);
-       dbdisk_free(disk);
+       if (delcursor)
+               mdb_cursor_close(cursor);
 
-       mdb_cursor_close(cursor);
        mdb_txn_commit(tx);
+       return 0;
 
-       *fp = file;
+fail:
+       printf("fail: %s\n", mdb_strerror(res));
+       if (delcursor)
+               mdb_cursor_close(cursor);
 
-       return res;
- fail:
-       // close cursor?
        mdb_txn_abort(tx);
-
+fail0:
        return res;
 }
 
-int dbfile_next_list(dbindex *db, int listid, dbfile **fp) {
-       return dbfile_iterate_list(db, listid, fp, 0);
-}
-
-int dbfile_prev_list(dbindex *db, int listid, dbfile **fp) {
-       return dbfile_iterate_list(db, listid, fp, 1);
-}
-#endif
-
 #include <regex.h>
 
 // TODO: should run over title index instead?
@@ -2140,6 +1873,7 @@ void dbindex_dump(dbindex *db) {
 
 
 /* scan tables with int keys */
+// TBD
 static dbscan *dbscan_secondary(dbtxn *tx, dbindex *db, MDB_dbi table, int diskid) {
        dbscan *scan = calloc(1, sizeof(*scan));
        int res;
@@ -2184,6 +1918,7 @@ static dbscan *dbscan_secondary(dbtxn *tx, dbindex *db, MDB_dbi table, int diski
        return NULL;
 }
 
+// TBD
 static uint32_t dbscan_secondary_next(dbscan *scan) {
        int res = 0;
 
@@ -2342,25 +2077,6 @@ static void *dbscan_primary2_next(dbscan *scan) {
        return NULL;
 }
 
-/*
-int dbscan_init_primary(dbtxn *tx, dbscan *scan, dbindex *db, MDB_dbi primary, dbid_t keyid) {
-       scan->db = db;
-       scan->primary = primary;
-       scan->tx = tx;
-       scan->keyid = keyid;
-
-       if (scan->res = mdb_cursor_open(tx, primary, &scan->cursor))
-               goto fail;
-
-       scan->key.mv_data = &scan->keyid;
-       scan->key.mv_size = sizeof(scan->keyid);
-
-       return 0;
-fail:
-       return scan->res;
-       }*/
-
-
 dbdisk *dbscan_disk(dbtxn *tx, dbscan *scan, dbindex *db, dbid_t diskid) {
        return dbscan_primary2(tx, scan, db, db->disk, DBDISK_DESC, dbdisk_decode_raw, diskid, diskid == 0 ? MDB_FIRST : MDB_SET_RANGE);
 }
@@ -2392,70 +2108,32 @@ dblist *dbscan_list_next(dbscan *scan) {
  * If listid != 0 then the scan order is based on the playlist.  The seq should be supplied.  The fileid is optional and if supplied will be used to find next occurance (>= seq) of the file.
  *
  */
-dbfile *dbscan_list_entry(dbtxn *tx, dbscan *scan, dbindex *db, dbid_t listid, int seq, dbid_t fileid) {
-       scan->list_entry.listid = listid;
-       scan->list_entry.seq = seq;
-       scan->list_entry.fileid = fileid;
-
-       if (listid == 0) {
-               if (dbscan_init(tx, scan, db, db->file_by_path, DBFILE_DESC, dbfile_decode_raw) == 0) {
-                       if (fileid != 0) {
-                               // If starting on a given file, first look it up to find the path start
-                               MDB_cursor *cursor;
-
-                               dbscan_init_key(scan, fileid);
-
-                               // Get file or next file
-                               scan->res = mdb_cursor_open(tx, db->file, &cursor);
-                               scan->res = mdb_cursor_get(cursor, &scan->key, &scan->data, MDB_SET_RANGE);
-                               mdb_cursor_close(cursor);
-                               if (scan->res == 0) {
-                                       dbfile *file = dbscan_decode(scan);
-
-                                       if (file) {
-                                               // position by-path cursor for scanning
-                                               char path[strlen(file->path) + 10];
-                                               MDB_val key;
+dbfile *dbscan_list_entry(dbtxn *tx, dbscan *scan, dbindex *db, dblistcursor *info) {
+       dbid_t listid = info->listid;
+       uint32_t seq = info->seq;
+       dbid_t fileid = info->fileid;
 
-                                               sprintf(path, "%08x%s", file->diskid, file->path);
+       scan->list_entry = *info;
 
-                                               printf("scan path from path %s\n", path);
-
-                                               key.mv_data = path;
-                                               key.mv_size = strlen(path);
-                                               scan->res = mdb_cursor_get(scan->cursor, &key, &scan->key, MDB_SET);
-
-                                               return file;
-                                       }
-                               }
-                       } else {
-                               // Just start at the start
-                               MDB_val key;
+       if (dbscan_init(tx, scan, db, db->file_by_list, DBFILE_DESC, dbfile_decode_raw) == 0) {
+               // TODO: perhaps if seq is non-zero and fileid is non-zero, lookup next entry of file
+               if (seq == 0 && fileid != 0) {
+                       struct dblistfile thing = { .listid = listid, .seq = seq };
+                       MDB_cursor *cursor;
 
-                               printf("scan path from start\n");
+                       dbscan_init_key(scan, fileid);
+                       scan->data.mv_data = &thing;
+                       scan->data.mv_size = sizeof(thing);
 
-                               if ((scan->res = mdb_cursor_get(scan->cursor, &key, &scan->key, MDB_FIRST)) == 0
-                                       && (scan->res = mdb_get(scan->tx, scan->db->file, &scan->key, &scan->data)) == 0)
-                                       return dbscan_decode(scan);
-                       }
+                       scan->res = mdb_cursor_open(tx, db->list_by_file, &cursor);
+                       if ((scan->res = mdb_cursor_get(cursor, &scan->key, &scan->data, MDB_GET_BOTH_RANGE)) == 0)
+                               seq = ((struct dblistfile *)scan->data.mv_data)->seq;
+                       else
+                               seq = -1;
+                       mdb_cursor_close(cursor);
                }
-       } else {
-               if (dbscan_init(tx, scan, db, db->file_by_list, DBFILE_DESC, dbfile_decode_raw) == 0) {
-                       // lookup seq of next entry of this file?
-                       if (fileid != 0) {
-                               struct dblistfile thing = { .listid = listid, .seq = seq };
-                               MDB_cursor *cursor;
-
-                               dbscan_init_key(scan, fileid);
-                               scan->data.mv_data = &thing;
-                               scan->data.mv_size = sizeof(thing);
-
-                               scan->res = mdb_cursor_open(tx, db->list_by_file, &cursor);
-                               if ((scan->res = mdb_cursor_get(cursor, &scan->key, &scan->data, MDB_GET_BOTH_RANGE)) == 0)
-                                       seq = ((struct dblistfile *)scan->data.mv_data)->seq;
-                               mdb_cursor_close(cursor);
-                       }
 
+               if (seq != -1) {
                        struct dbfilelist thing = { .seq = seq };
 
                        dbscan_init_key(scan, listid);
@@ -2472,71 +2150,53 @@ dbfile *dbscan_list_entry(dbtxn *tx, dbscan *scan, dbindex *db, dbid_t listid, i
                                scan->key.mv_data = &scan->list_entry.fileid;
                                scan->key.mv_size = sizeof(scan->list_entry.fileid);
 
-                               if ((scan->res = mdb_get(tx, db->file, &scan->key, &scan->data)) == 0)
-                                       return dbscan_decode(scan);
+                               if ((scan->res = mdb_get(tx, db->file, &scan->key, &scan->data)) == 0) {
+                                       dbfile *file = dbscan_decode(scan);
+
+                                       if (file) {
+                                               info->seq = scan->list_entry.seq;
+                                               info->fileid = scan->list_entry.fileid;
+                                               return file;
+                                       }
+                               }
                        }
-                       dbscan_close2(scan);
                }
        }
        return NULL;
-
 }
 
-static dbfile *scan_list_entry_next(dbscan *scan, MDB_cursor_op next0, MDB_cursor_op next1) {
-       MDB_val key;
-
-       if (scan->list_entry.listid == 0) {
-#if 0
-               if ((scan->res = mdb_cursor_get(scan->cursor, &key, &scan->key, next0)) == 0
-                       && (scan->res = mdb_get(scan->tx, scan->db->file, &scan->key, &scan->data)) == 0)
-                       return dbscan_decode(scan);
-#else
-               if ((scan->res = mdb_cursor_get(scan->cursor, &key, &scan->key, next0)) != 0) {
-                       printf("cursor get: %s\n", mdb_strerror(scan->res));
-                       return NULL;
-               }
-               if ((scan->res = mdb_get(scan->tx, scan->db->file, &scan->key, &scan->data)) != 0) {
-                       printf("data get: %s\n", mdb_strerror(scan->res));
-                       return NULL;
-               }
-               return dbscan_decode(scan);
-#endif
-       } else {
-               MDB_val data;
+static dbfile *scan_list_entry_next(dbscan *scan, dblistcursor *info, MDB_cursor_op next0, MDB_cursor_op next1) {
+       dbfile *file;
+       MDB_val key, data;
 
-               if ((scan->res = mdb_cursor_get(scan->cursor, &key, &data, next1)) == 0) {
-                       struct dbfilelist *value = data.mv_data;
+       if ((scan->res = mdb_cursor_get(scan->cursor, &key, &data, next1)) == 0) {
+               struct dbfilelist *value = data.mv_data;
 
-                       scan->list_entry.seq = value->seq;
-                       scan->list_entry.fileid = value->fileid;
+               scan->list_entry.seq = value->seq;
+               scan->list_entry.fileid = value->fileid;
 
-                       scan->key.mv_data = &scan->list_entry.fileid;
-                       scan->key.mv_size = sizeof(scan->list_entry.fileid);
+               scan->key.mv_data = &scan->list_entry.fileid;
+               scan->key.mv_size = sizeof(scan->list_entry.fileid);
 
-                       if ((scan->res = mdb_get(scan->tx, scan->db->file, &scan->key, &scan->data)) == 0)
-                               return dbscan_decode(scan);
+               if ((scan->res = mdb_get(scan->tx, scan->db->file, &scan->key, &scan->data)) == 0
+                       && (file = dbscan_decode(scan))) {
+                       info->seq = scan->list_entry.seq;
+                       info->fileid = scan->list_entry.fileid;
+                       return file;
                }
        }
 
        return NULL;
 }
 
-dbfile *dbscan_list_entry_next(dbscan *scan) {
-       return scan_list_entry_next(scan, MDB_NEXT, MDB_NEXT_DUP);
-}
-
-dbfile *dbscan_list_entry_prev(dbscan *scan) {
-       return scan_list_entry_next(scan, MDB_PREV, MDB_PREV_DUP);
+dbfile *dbscan_list_entry_next(dbscan *scan, dblistcursor *info) {
+       return scan_list_entry_next(scan, info, MDB_NEXT, MDB_NEXT_DUP);
 }
 
-
-int dbscan_list_entry_seq(dbscan *scan) {
-       return scan->list_entry.seq;
+dbfile *dbscan_list_entry_prev(dbscan *scan, dblistcursor *info) {
+       return scan_list_entry_next(scan, info, MDB_PREV, MDB_PREV_DUP);
 }
 
-dbid_t  dbscan_list_entry_listid(dbscan *scan) {
-       return scan->list_entry.listid;
-}
 
 /* legacy */
 
@@ -2570,125 +2230,6 @@ dbdisk *dbdisk_next(dbscan *scan) {
        return disk;
 }
 
-dbscan *dblist_scan(dbtxn *tx, dbindex *db, int listid) {
-       dbscan *scan = dbscan_primary(tx, db, db->list, listid);
-
-       if (scan) {
-               scan->DESC = DBLIST_DESC;
-               scan->decode_raw = dblist_decode_raw;
-       }
-
-       return scan;
-}
-
-const dblist *dblist_next(dbscan *scan) {
-       if (dbscan_primary_next(scan)) {
-               return dbscan_decode(scan);
-       } else {
-               return NULL;
-       }
-}
-
-dbscan *dblist_file_scan(dbtxn *tx, dbindex *db, int listid, int seq, int fileid) {
-       dbscan *scan = calloc(1, sizeof(*scan));
-
-       scan->tx = tx;
-       scan->db = db;
-
-       scan->DESC = DBFILE_DESC;
-       scan->decode_raw = dbfile_decode_raw;
-
-       scan->list_entry.listid = listid;
-       scan->list_entry.fileid = fileid;
-
-       scan->key.mv_data = &scan->keyval;
-       scan->key.mv_size = sizeof(scan->keyval);
-
-       if (listid == 0) {
-               scan->table = db->file;
-
-               if (db->res = mdb_cursor_open(tx, db->file, &scan->cursor))
-                       goto fail;
-
-               scan->keyval = fileid + 1;
-
-               if (db->res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_SET_RANGE))
-                       goto fail;
-       } else {
-               scan->table = db->file_by_list;
-
-               if (db->res = mdb_cursor_open(tx, db->file_by_list, &scan->cursor))
-                       goto fail;
-
-               struct dbfilelist thing = { .seq = seq + 1, .fileid = 0 };
-
-               scan->keyval = listid;
-               scan->data.mv_data = &thing;
-               scan->data.mv_size = sizeof(thing);
-
-               if (db->res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_BOTH_RANGE))
-                       goto fail;
-
-               struct dbfilelist *value = scan->data.mv_data;
-
-               scan->list_entry.seq = value->seq;
-               scan->list_entry.fileid = value->fileid;
-
-               scan->key.mv_data = &scan->list_entry.fileid;
-               scan->key.mv_size = sizeof(scan->list_entry.fileid);
-
-               if (db->res = mdb_get(tx, db->file, &scan->key, &scan->data))
-                       goto fail;
-       }
-
-       scan->index = 0;
-       scan->count = 1;
-
-       return scan;
-fail:
-       if (scan->cursor)
-               mdb_cursor_close(scan->cursor);
-       free(scan);
-       return NULL;
-}
-
-const dbfile *dblist_file_next(dbscan *scan, int *seq) {
-       dbindex *db = scan->db;
-
-       if (scan->index == scan->count) {
-               if (scan->list_entry.listid == 0) {
-                       if (db->res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_NEXT))
-                               goto fail;
-               } else {
-                       if (db->res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_NEXT_DUP))
-                               goto fail;
-
-                       struct dbfilelist *value = scan->data.mv_data;
-
-                       scan->list_entry.seq = value->seq;
-                       scan->list_entry.fileid = value->fileid;
-
-                       scan->key.mv_data = &scan->list_entry.fileid;
-                       scan->key.mv_size = sizeof(scan->list_entry.fileid);
-
-                       if (db->res = mdb_get(scan->tx, db->file, &scan->key, &scan->data))
-                               goto fail;
-               }
-               scan->index = 0;
-               scan->count = 1;
-       }
-
-       scan->index += 1;
-
-       *seq = scan->list_entry.seq;
-
-       return dbscan_decode(scan);
-fail:
-       return NULL;
-}
-
-
-
 // prototyping
 int dbfile_clear_suffix(MDB_txn *tx, dbindex *db) {
        return mdb_drop(tx, db->file_by_suffix, 0);
index 34f6200..f668ff5 100644 (file)
--- a/dbindex.h
+++ b/dbindex.h
@@ -37,7 +37,7 @@ struct dblist {
        dbid_t id;
        int size;
        char *name;
-       char *comment;
+       char *desc;
 };
 
 typedef struct dbfile dbfile;
@@ -59,19 +59,38 @@ struct dbfile {
        char *full_path;        // <transient> full path including disk name, depends on api
 };
 
+typedef struct dblistcursor dblistcursor;
+
+struct dblistcursor {
+       dbid_t listid;
+       uint32_t seq;
+       dbid_t fileid;
+};
+
 /* player state, this is passed around as a struct */
 typedef struct dbstate dbstate;
 
-// FIXME: add playlist id
-// FIXME: add playlist seq (and file)
+// current list mode
+// or just list mode to try on mode finish (doesn't need to be stored?)
+enum list_mode_t {
+       LIST_DEFAULT,
+       LIST_JUKEBOX,
+       LIST_USER,
+       LIST_PLAYNOW
+};
 
 struct dbstate {
        uint32_t size;          // don't need to initialise
        uint32_t state;         // some info on playing
 
-       dbid_t listid;          // list, if any
-       uint32_t seq;           // list position, if any
-       dbid_t fileid;          // file being played
+       uint32_t list_mode;     // which list are we playing
+
+       dblistcursor current;   // current file for restore
+
+       dblistcursor list_playnow;// the playnow list (records history)
+       dblistcursor list_user; // user requested list/position
+       dblistcursor list_jukebox; // jukebox list position
+       dblistcursor list_default; // default list position
 
        uint64_t pos;           // last approximate position pts
        double poss;            // last approximate position in seconds
@@ -86,6 +105,7 @@ typedef struct dbscan dbscan;
 char *dbindex_home(void);
 
 dbindex *dbindex_open(const char *path);
+int dbindex_result(dbindex *db); // last result code
 void dbindex_close(dbindex *db);
 
 dbtxn *dbindex_begin(dbindex *db, dbtxn *txn, int readonly);
@@ -111,7 +131,9 @@ int dbfile_del(dbtxn *txn, dbindex *db, dbfile *f);
 int dbfile_add(dbtxn *txn, dbindex *db, dbfile *f);
 int dbfile_update(dbtxn *txn, dbindex *db, dbfile *o, dbfile *f);
 
-// TBD?
+int dbfile_inlist(dbtxn *tx, dbindex *db, dbid_t fileid, dbid_t listid);
+
+// TBD?  seems not
 dbscan *dbfile_scan_disk(dbtxn *tx, dbindex *db, int diskid);
 uint32_t dbfile_scan_next(dbscan *scan);
 void dbfile_scan_close(dbscan *scan);
@@ -124,19 +146,28 @@ extern ez_blob_desc DBLIST_DESC[];
 int dbfile_next(dbindex *db, dbfile **f, char **fpath);
 int dbfile_prev(dbindex *db, dbfile **f, char **fpath);
 
-void dbshuffle_init(dbindex *db);
+//void dbshuffle_init(dbindex *db);
 
-int dbfile_next_shuffle(dbindex *db, dbfile **f, char **fpath);
-int dbfile_prev_shuffle(dbindex *db, dbfile **f, char **fpath);
+//int dbfile_next_shuffle(dbindex *db, dbfile **f, char **fpath);
+//int dbfile_prev_shuffle(dbindex *db, dbfile **f, char **fpath);
 
 dblist *dblist_get(dbtxn *tx, dbindex *db, int id);
+dblist *dblist_get_name(dbtxn *tx, dbindex *db, const char *name);
 void dblist_free(dblist *f);
 
+dbid_t dblistid_get_name(dbtxn *tx, dbindex *db, const char *name);
+
 int dblist_add(dbtxn *txn, dbindex *db, dblist *d);
+int dblist_clear(dbtxn *tx, dbindex *db, int listid);
 int dblist_del(dbtxn *txn, dbindex *db, int listid);
 
-// should these take the list name?
-int dblist_add_file(dbtxn *txn, dbindex *db, dblist *d, int file);
+int dblist_add_file(MDB_txn *tx, dbindex *db, dblistcursor *info);
+
+// clear and fill the all and all#shuffle playlist
+int dblist_update_all(dbtxn *tx, dbindex *db);
+// delete all lists and run update_all
+int dblist_reset_all(dbtxn *tx, dbindex *db);
+
 
 //int dbfile_next_list(dbindex *db, int listid, dbfile **fp);
 //int dbfile_prev_list(dbindex *db, int listid, dbfile **fp);
@@ -155,13 +186,6 @@ void dbscan_close(dbscan *scan);
 int dbfile_put_suffix(dbtxn *tx, dbindex *db, const char *suffix, uint32_t fileid);
 size_t dbfile_search_substring(dbtxn *tx, dbindex *db, const char *sub);
 
-struct dblistcursor {
-       uint32_t listid;
-       uint32_t seq;
-       uint32_t fileid;
-       dbfile file;
-};
-
 //int dblist_iterate(dbtxn *txn, dbindex *db, struct dblistcursor *pos);
 
 // another attempt ...
@@ -190,16 +214,12 @@ struct dbscan {
        void (*decode_raw)(const ez_blob *blob, void *p);
 
        union {
-               struct {
-                       int listid;
-                       int fileid;
-                       int seq;
-               } list_entry;
+               struct dblistcursor list_entry;
        };
 };
 
 void dbscan_free(dbscan *scan);
-static __inline__ int dbscan_res(dbscan *scan) { return scan->res; }
+static __inline__ int dbscan_result(dbscan *scan) { return scan->res; }
 
 dbdisk *dbscan_disk(dbtxn *tx, dbscan *scan, dbindex *db, dbid_t diskid);
 dbdisk *dbscan_disk_next(dbscan *scan);
@@ -209,10 +229,11 @@ dblist *dbscan_list(dbtxn *tx, dbscan *scan, dbindex *db, dbid_t listid);
 dblist *dbscan_list_next(dbscan *scan);
 
 // TODO: s/entry/file/
-dbfile *dbscan_list_entry(dbtxn *tx, dbscan *scan, dbindex *db, dbid_t listid, int seq, dbid_t fileid);
-dbfile *dbscan_list_entry_next(dbscan *scan);
-dbfile *dbscan_list_entry_prev(dbscan *scan);
-int     dbscan_list_entry_seq(dbscan *scan);
-dbid_t  dbscan_list_entry_listid(dbscan *scan);
+dbfile *dbscan_list_entry(dbtxn *tx, dbscan *scan, dbindex *db, dblistcursor *cursor);
+dbfile *dbscan_list_entry_next(dbscan *scan, dblistcursor *info);
+dbfile *dbscan_list_entry_prev(dbscan *scan, dblistcursor *info);
+// these two TBD, its in the cursor now
+//int     dbscan_list_entry_seq(dbscan *scan);
+//dbid_t  dbscan_list_entry_listid(dbscan *scan);
 
 #define MAIN_INDEX NULL
index 8092c08..354916d 100644 (file)
@@ -519,11 +519,17 @@ static void indexer(const char *path) {
                case NOTIFY_DISK_REMOVE:
                        // don't really care on this, indexing should fail if it was busy
                        break;
-               case NOTIFY_SHUFFLE:
+               case NOTIFY_SHUFFLE: {
+                       dbtxn *tx = dbindex_begin(db, NULL, 0);
                        printf("Creating shuffled playlist\n");
-                       dbshuffle_init(db);
-                       printf("Shuffle complete.\n");
-                       break;
+                       if (dblist_update_all(tx, db) == 0) {
+                               printf("Shuffle complete.\n");
+                               dbindex_commit(tx);
+                       } else {
+                               printf("Shuffle failed.\n");
+                               dbindex_abort(tx);
+                       }
+                       break; }
                case NOTIFY_QUIT:
                        quit = 1;
                        break;
index e14cf62..db6c49c 100644 (file)
@@ -29,6 +29,9 @@
 
 #include "player.h"
 
+// Embeds the player.html as gzip'd ro data.
+//#define EMBED_PLAYER_HTML
+
 static dbindex *db;
 static notify_t player;
 
@@ -36,19 +39,22 @@ static struct ez_pair ct_text_html = {
        .name = "Content-Type",
        .value = "text/html;charset=utf-8"
 };
+#if 0
 static struct ez_pair ct_text_xml = {
        .name = "Content-Type",
        .value = "text/xml"
 };
+#endif
 static struct ez_pair ct_application_json = {
        .name = "Content-Type",
        .value = "application/json"
 };
+#ifdef EMBED_PLAYER_HTML
 static struct ez_pair ce_gzip = {
        .name = "Content-Encoding",
        .value = "gzip"
 };
-
+#endif
 static void obstack_sgrow(struct obstack *os, const char *value) {
        obstack_grow(os, value, strlen(value));
 }
@@ -98,6 +104,7 @@ static int param_int(struct ez_httprequest *r, const char *name, int def) {
        return def;
 }
 
+#ifdef EMBED_PLAYER_HTML
 static double param_float(struct ez_httprequest *r, const char *name, double def) {
        struct ez_pair *h = find_param(r, name);
 
@@ -106,6 +113,7 @@ static double param_float(struct ez_httprequest *r, const char *name, double def
 
        return def;
 }
+#endif
 
 static const char *param_value(struct ez_httprequest *r, const char *name, const char *def) {
        struct ez_pair *h = find_param(r, name);
@@ -143,23 +151,23 @@ static void write_state_json(dbindex *db, struct obstack *os) {
        int res = dbstate_get(tx, db, &state);
        dbfile *file = NULL;
 
-       printf("state.fileid = %d\n", state.fileid);
+       printf("state.fileid = %d\n", state.current.fileid);
        printf("state.pos = %zd\n", state.pos);
-       printf("state.listid = %d\n", state.listid);
-       printf("state.seq = %d\n", state.seq);
+       printf("state.listid = %d\n", state.current.listid);
+       printf("state.seq = %d\n", state.current.seq);
 
        if (res == 0
            && (state.state & 1)
-           && (file = dbfile_get(tx, db, state.fileid))) {
+           && (file = dbfile_get(tx, db, state.current.fileid))) {
                obstack_1grow(os, '{');
                write_file_json(os, file);
 
                obstack_printf(os, ",\"path\":\"%s\"", file->path);
                obstack_printf(os, ",\"status\":\"%s\"", (state.state & 2) ? "paused" : "playing");
 
-               obstack_printf(os, ",\"listid\":\"%u\"", state.listid);
-               obstack_printf(os, ",\"seq\":\"%u\"", state.seq);
-               obstack_printf(os, ",\"fileid\":\"%u\"", state.fileid);
+               obstack_printf(os, ",\"listid\":\"%u\"", state.current.listid);
+               obstack_printf(os, ",\"seq\":\"%u\"", state.current.seq);
+               obstack_printf(os, ",\"fileid\":\"%u\"", state.current.fileid);
 
                obstack_sgrow(os, ",\"position\":");
                duration_value(os, (uint64_t)(state.poss * 1000));
@@ -209,6 +217,7 @@ static void write_list_json(dbindex *db, struct obstack *io, int listid, int seq
        dbscan scan;
        dbfile *file;
        dbtxn *tx = dbindex_begin(db, NULL, 1);
+       dblistcursor list = { .listid = listid, .seq = seq, .fileid = fileid };
 
        // TODO: get list properly and seq
 
@@ -216,18 +225,18 @@ static void write_list_json(dbindex *db, struct obstack *io, int listid, int seq
        obstack_sgrow(io, "\"list_name\":\"shuffle\"");
        obstack_sgrow(io, ",\"items\": [");
 
-       file = dbscan_list_entry(tx, &scan, db, listid, seq, fileid);
+       file = dbscan_list_entry(tx, &scan, db, &list);
        for (int i=0;file && i<10;i++) {
                obstack_sgrow(io, i > 0 ? ",{" : "{");
                write_file_json(io, file);
 
-               obstack_printf(io, ",\"listid\":\"%u\"", listid);
-               obstack_printf(io, ",\"seq\":\"%u\"", dbscan_list_entry_seq(&scan));
+               obstack_printf(io, ",\"listid\":\"%u\"", list.listid);
+               obstack_printf(io, ",\"seq\":\"%u\"", list.seq);
 
                obstack_1grow(io, '}');
 
                dbfile_free(file);
-               file = dbscan_list_entry_next(&scan);
+               file = dbscan_list_entry_next(&scan, &list);
        }
        dbfile_free(file);
        obstack_sgrow(io, "]}");
@@ -266,7 +275,7 @@ static void write_search_json(dbindex *db, struct obstack *io, const char *query
 /* ********************************************************************** */
 
 static int handle_player(struct ez_httprequest *req, struct ez_httpresponse *rep) {
-#if 0
+#ifdef EMBED_PLAYER_HTML
        httpresponse_set_response(rep, 200, "Ok");
        ez_list_addtail(&rep->http.headers, &ct_text_html);
        ez_list_addtail(&rep->http.headers, &ce_gzip);
@@ -417,6 +426,7 @@ static int handle_list(struct ez_httprequest *req, struct ez_httpresponse *rep)
                write_list_json(db, os, lid, seq, fid);
                return set_data_json(rep);
        } else if (strcmp(req->method, "POST") == 0) {
+               struct dblistcursor info = { .listid = lid, .fileid = fid };
                // /x/list/{listid} post fid=?  ignore seq
                if (fid == 0)
                        goto fail0;
@@ -424,8 +434,10 @@ static int handle_list(struct ez_httprequest *req, struct ez_httpresponse *rep)
                dbtxn *tx = dbindex_begin(db, NULL, 0);
                if (!tx)
                        goto fail0;
+               if (dblist_add_file(tx, db, &info))
+                       goto fail1;
                dblist *list = dblist_get(tx, db, lid);
-               if (!list || dblist_add_file(tx, db, list, fid))
+               if (!list)
                        goto fail1;
 
                if (dbindex_commit(tx))
@@ -471,7 +483,7 @@ static int handle_goto(struct ez_httprequest *req, struct ez_httpresponse *rep)
        // TODO: look it up in the db?
 
        if (fid != 0) {
-               struct notify_goto gogo = { .fileid = fid };
+               struct notify_goto gogo = { .info.fileid = fid };
 
                notify_msg_send(player, NOTIFY_PLAY_GOTO, 0, &gogo);
                httpresponse_set_response(rep, 202, "Requested");
index 48f4406..0fbdb92 100644 (file)
@@ -42,7 +42,6 @@ void dbshuffle_init2(dbindex *db);
 int dbdisk_del_id(dbtxn *txn, dbindex *db, int diskid);
 void dbindex_validate(dbindex *db);
 
-int dblist_reset(dbtxn *tx, dbindex *db);
 int dblist_del_file(dbtxn *txn, dbindex *db, struct dblistcursor *list);
 
 /* ********************************************************************** */
@@ -197,8 +196,9 @@ static void check(dbindex *db) {
 
 int main(int argc, char **argv) {
        const char *dbdir = MAIN_INDEX;
-       int fileid = 0, diskid = 0, listid = 0;
-       int seq = 0;
+       dbid_t diskid = 0;
+       dblistcursor info = { 0 };
+       int rc = 0;
 
        setlocale(LC_ALL, "en_AU.UTF-8");
 
@@ -212,19 +212,20 @@ int main(int argc, char **argv) {
 
        for (int i=1;i<argc;i++) {
                const char *cmd = argv[i];
+               int res = 0;
 
                if (strcmp(cmd, "-f") == 0) {
-                       fileid = atoi(argv[++i]);
+                       info.fileid = atoi(argv[++i]);
                } else if (strcmp(cmd, "-s") == 0) {
-                       seq = atoi(argv[++i]);
+                       info.seq = atoi(argv[++i]);
                } else if (strcmp(cmd, "-d") == 0) {
                        diskid = atoi(argv[++i]);
                } else if (strcmp(cmd, "-l") == 0) {
-                       listid = atoi(argv[++i]);
+                       info.listid = atoi(argv[++i]);
                } else if (strcmp(cmd, "check") == 0) {
                        check(db);
                } else if (strcmp(cmd, "shuffle") == 0) {
-                       dbshuffle_init2(db);
+                       //dbshuffle_init2(db);
                } else if (strcmp(cmd, "file-dump") == 0) {
                        // dump file info
                        // dump lists it's in
@@ -232,7 +233,7 @@ int main(int argc, char **argv) {
                        dbtxn *tx = dbindex_begin(db, NULL, 1);
                        dbscan scan;
 
-                       for (dbfile *file = dbscan_file(tx, &scan, db, fileid); file; file = dbscan_file_next(&scan)) {
+                       for (dbfile *file = dbscan_file(tx, &scan, db, info.fileid); file; file = dbscan_file_next(&scan)) {
                                printf("fid=%4d title='%s' path='%s'\n", file->id, file->title, file->path);
                                dbfile_free(file);
                        }
@@ -241,25 +242,44 @@ int main(int argc, char **argv) {
                } else if (strcmp(cmd, "file-del") == 0) {
                        dbtxn *tx = dbindex_begin(db, NULL, 0);
 
-                       if (dbfile_del_id(tx, db, fileid) == 0)
+                       if ((res = dbfile_del_id(tx, db, info.fileid)) == 0)
                                dbindex_commit(tx);
                        else
                                dbindex_abort(tx);
+               } else if (strcmp(cmd, "file-inlist") == 0) {
+                       dbtxn *tx = dbindex_begin(db, NULL, 1);
+
+                       rc = dbfile_inlist(tx, db, info.fileid, info.listid);
+                       res = dbindex_result(db);
+                       dbindex_abort(tx);
+                       printf("fileid=%d listid=%d inlist?=%d\n", info.fileid, info.listid, rc);
                } else if (strcmp(cmd, "lists") == 0) {
                        dbtxn *tx = dbindex_begin(db, NULL, 1);
                        dbscan scan;
 
                        for (dblist *list = dbscan_list(tx, &scan, db, 0); list; list = dbscan_list_next(&scan)) {
-                               printf("lid=%4d size=%5d name=%s\n", list->id, list->size, list->name);
+                               printf("lid=%4d size=%5d name=%-20s (%s)\n", list->id, list->size, list->name, list->desc);
                                dblist_free(list);
                        }
                        dbscan_free(&scan);
                        dbindex_abort(tx);
+               } else if (strcmp(cmd, "lists-init") == 0) {
+                       dbtxn *tx = dbindex_begin(db, NULL, 0);
+
+                       if (dblist_update_all(tx, db) == 0) {
+                               printf("ok\n");
+                               dbindex_commit(tx);
+                       } else {
+                               printf("failed\n");
+                               dbindex_abort(tx);
+                       }
                } else if (strcmp(cmd, "lists-reset") == 0) {
                        dbtxn *tx = dbindex_begin(db, NULL, 0);
 
-                       dblist_reset(tx, db);
-                       dbindex_commit(tx);
+                       if ((res = dblist_reset_all(tx, db)) == 0)
+                               dbindex_commit(tx);
+                       else
+                               dbindex_abort(tx);
                } else if (strcmp(cmd, "list-add") == 0) {
                        dblist list = {
                                .name = argv[++i]
@@ -267,32 +287,25 @@ int main(int argc, char **argv) {
 
                        dblist_add(NULL, db, &list);
                } else if (strcmp(cmd, "list-del") == 0) {
-                       dblist_del(NULL, db, listid);
+                       dblist_del(NULL, db, info.listid);
+               } else if (strcmp(cmd, "list-clear") == 0) {
+                       dbtxn *tx = dbindex_begin(db, NULL, 0);
+
+                       if ((res = dblist_clear(tx, db, info.listid)) == 0)
+                               dbindex_commit(tx);
+                       else
+                               dbindex_abort(tx);
                } else if (strcmp(cmd, "list-add-file") == 0) {
-                       if (listid != 0 && fileid != 0) {
-                               dbtxn *tx = dbindex_begin(db, NULL, 0);
-                               dblist *list = dblist_get(tx, db, listid);
+                       dbtxn *tx = dbindex_begin(db, NULL, 0);
 
-                               if (list) {
-                                       if (dblist_add_file(tx, db, list, fileid) == 0)
-                                               dbindex_commit(tx);
-                                       else
-                                               dbindex_abort(tx);
-                               } else {
-                                       printf("%s: unknown list %d\n", cmd, listid);
-                               }
-                       } else {
-                               printf("%s: Must supply fileid and listid\n", cmd);
-                       }
+                       if ((res = dblist_add_file(tx, db, &info)) == 0)
+                               dbindex_commit(tx);
+                       else
+                               dbindex_abort(tx);
                } else if (strcmp(cmd, "list-del-file") == 0) {
                        dbtxn *tx = dbindex_begin(db, NULL, 0);
-                       struct dblistcursor list = {
-                               .listid = listid,
-                               .seq = seq,
-                               .fileid = fileid
-                       };
 
-                       if (dblist_del_file(tx, db, &list) == 0)
+                       if ((res = dblist_del_file(tx, db, &info)) == 0)
                                dbindex_commit(tx);
                        else
                                dbindex_abort(tx);
@@ -300,8 +313,8 @@ int main(int argc, char **argv) {
                        dbtxn *tx = dbindex_begin(db, NULL, 1);
                        dbscan scan;
 
-                       for (dbfile *file = dbscan_list_entry(tx, &scan, db, listid, seq, fileid); file; file = dbscan_list_entry_next(&scan)) {
-                               printf("fid=%4d seq=%4d title: %-60s %s\n", file->id, dbscan_list_entry_seq(&scan), file->title, file->path);
+                       for (dbfile *file = dbscan_list_entry(tx, &scan, db, &info); file; file = dbscan_list_entry_next(&scan, &info)) {
+                               printf("fid=%4d seq=%4d title: %-60s %s\n", file->id, info.seq, file->title, file->path);
                                dbfile_free(file);
                        }
                        dbscan_free(&scan);
@@ -341,9 +354,11 @@ int main(int argc, char **argv) {
                                printf("search failed\n");
                        }
                }
+               if (res)
+                       printf("error (%d): %s\n", res, mdb_strerror(res));
        }
 
        dbindex_close(db);
 
-       return 0;
+       return rc;
 }
index 99b524b..921780a 100644 (file)
@@ -3,8 +3,8 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "notify.h"
 #include "dbindex.h"
+#include "notify.h"
 
 int main(int argc, char **argv) {
        if (argc > 1) {
index 745e42e..461bb4d 100644 (file)
@@ -41,6 +41,7 @@
 #define INPUT_KB "/dev/input/by-id/usb-2.4G_Wireless_Receiver-event-kbd"
 #define INPUT_MOUSE "/dev/input/by-id/usb-2.4G_Wireless_Receiver-if01-event-mouse"
 
+#include "dbindex.h"
 #include "notify.h"
 
 struct monitor {
index 0d7fc78..0c4188d 100644 (file)
 #include <poll.h>
 #include <pthread.h>
 
+#include "dbindex.h"
 #include "notify.h"
 #include "ez-list.h"
 #include "ez-port.h"
-#include "dbindex.h"
 
 struct audio_player;
 
@@ -114,6 +114,9 @@ struct audio_player {
        dbfile *playing;
        dbstate playing_state;
 
+       // current track/playlist management
+       dblistcursor *list_active; // update in play now?
+
        int quit;
        int paused;
        int paused_tmp;         // temporary pause, e.g. talking or effects, a count
@@ -421,9 +424,11 @@ int audio_init_filter(struct audio_player *ap) {
        if (res < 0)
                goto fail;
 
-       res = avfilter_graph_create_filter(&ap->anorm_ctx, anorm, "norm", "", NULL, ap->fg);
-       if (res < 0)
-               goto fail;
+       if (0) { // normalise, make configurable
+               res = avfilter_graph_create_filter(&ap->anorm_ctx, anorm, "norm", "", NULL, ap->fg);
+               if (res < 0)
+                       goto fail;
+       }
 
        sprintf(tmp, "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%lx",
                av_get_sample_fmt_name(AV_SAMPLE_FMT_S16), ap->sample_rate,
@@ -437,21 +442,23 @@ int audio_init_filter(struct audio_player *ap) {
        if (res < 0)
                goto fail;
 
-       if ((res = avfilter_link(ap->asource_ctx, 0, ap->anorm_ctx, 0)) < 0)
-               goto fail;
-       printf("link: %d\n", res);
-       if ((res = avfilter_link(ap->anorm_ctx, 0, ap->aformat_ctx, 0)) < 0)
-               goto fail;
-       printf("link: %d\n", res);
-       if ((res = avfilter_link(ap->aformat_ctx, 0, ap->asink_ctx, 0)) < 0)
-               goto fail;
-       printf("link: %d\n", res);
+       if (0) { // normalise
+               if ((res = avfilter_link(ap->asource_ctx, 0, ap->anorm_ctx, 0)) < 0)
+                       goto fail;
+               if ((res = avfilter_link(ap->anorm_ctx, 0, ap->aformat_ctx, 0)) < 0)
+                       goto fail;
+               if ((res = avfilter_link(ap->aformat_ctx, 0, ap->asink_ctx, 0)) < 0)
+                       goto fail;
+       } else {
+               if ((res = avfilter_link(ap->asource_ctx, 0, ap->aformat_ctx, 0)) < 0)
+                       goto fail;
+               if ((res = avfilter_link(ap->aformat_ctx, 0, ap->asink_ctx, 0)) < 0)
+                       goto fail;
+       }
 
        if ((res = avfilter_graph_config(ap->fg, NULL)) < 0)
                goto fail;
 
-       printf("setup ok\n");
-
        printf("output channels: %d\n", av_buffersink_get_channels(ap->asink_ctx));
        printf("output format: %s %d\n",
               av_get_sample_fmt_name(av_buffersink_get_format(ap->asink_ctx)),
@@ -619,7 +626,7 @@ int audio_checkpoint_state(struct audio_player *ap) {
                }
                ap->playing_state.stamp = time(NULL);
 
-               printf("checkpoint state=%d file=%d pos=%zd poss=%f '%s'\n", ap->playing_state.state, ap->playing_state.fileid, ap->playing_state.pos, ap->playing_state.poss, ap->playing ? ap->playing->full_path : "");
+               //printf("checkpoint state=%d file=%d pos=%zd poss=%f '%s'\n", ap->playing_state.state, ap->playing_state.current.fileid, ap->playing_state.pos, ap->playing_state.poss, ap->playing ? ap->playing->full_path : "");
                //printf("Checkpoint state, = %d\n", ap->playing_state.state);
 
                if ((res = dbstate_put(tx, ap->index, &ap->playing_state)) == 0) {
@@ -635,153 +642,267 @@ int audio_checkpoint_state(struct audio_player *ap) {
        return 0;
 }
 
+static void dump_cursor(const char *what, dblistcursor *info) {
+       printf("%10s: listid=%4d seq=%4d fileid=%4d\n", what, info->listid, info->seq, info->fileid);
+}
+
 // Next in play queue
-static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan *scan)) {
+/*
+  Playlist logic.
+
+  The current playing mode is in list_mode.
+  Tracks are found by priority:
+    - PLAYNOW
+    - USER
+    - JUKEBOX
+    - DEFAULT
+
+  Once a file finishes the search begins at USER always?
+ */
+static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan *scan, dblistcursor *info)) {
        int res;
        int empty = ap->playing == NULL;
        dbscan scan;
        dbfile *file;
        dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
        int retry = 0;
+       int trynext = 1;
 
        dbfile_free(ap->playing);
        ap->playing = NULL;
 
+       // Playnow only runs once
+       if (ap->playing_state.list_mode == LIST_PLAYNOW)
+               ap->playing_state.list_mode = LIST_USER;
+
        do {
+               dblistcursor *active;
+
+               switch (ap->playing_state.list_mode) {
+               case LIST_DEFAULT:
+               default:
+                       printf("trying default playlist\n");
+                       active = &ap->playing_state.list_default;
+                       break;
+               case LIST_JUKEBOX:
+                       printf("trying jukebox playlist\n");
+                       active = &ap->playing_state.list_jukebox;
+                       break;
+               case LIST_USER:
+                       printf("trying user playlist\n");
+                       active = &ap->playing_state.list_user;
+                       break;
+               }
+
+               dump_cursor("active?", active);
+
                printf("loop: %d\n", retry);
-               file = dbscan_list_entry(tx, &scan, ap->index, ap->playing_state.listid, ap->playing_state.seq, ap->playing_state.fileid);
+               file = dbscan_list_entry(tx, &scan, ap->index, active);
                if (file) {
-                       printf("entry: %s\n", file->path);
-                       dbfile_free(file);
-                       file = advance(&scan);
-                       printf("next: %s\n", file ? file->path : "<nil>");
+                       //printf("entry: %s\n", file->path);
+                       if (trynext) {
+                               dbfile_free(file);
+                               file = advance(&scan, active);
+                       }
+                       //printf("next: %s\n", file ? file->path : "<nil>");
+               } else {
+                       // repeat?  reset?
+                       printf("no entry found. scan.res =%d  db.res=%d\n", dbscan_result(&scan), dbindex_result(ap->index));
                }
                while (file) {
+                       dump_cursor("playing?", active);
+
                        res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file));
 
                        if (res == (-30798) && !empty) // && >loop?
                                res = 1;
 
-                       if (res == 0) {
+                               if (res == 0) {
+                               ap->playing_state.current = *active;
                                ap->playing = file;
-                               ap->playing_state.listid = dbscan_list_entry_listid(&scan);
-                               ap->playing_state.seq = dbscan_list_entry_seq(&scan);
-                               ap->playing_state.fileid = file->id;
                                audio_checkpoint_state(ap);
                                break;
                        }
 
                        dbfile_free(file);
-                       file = advance(&scan);
+                       file = advance(&scan, active);
                }
+
                // repeat at end of list?
-               if (!file && dbscan_res(&scan) == -30798) {
-                       ap->playing_state.seq = 0;
-                       ap->playing_state.fileid = 0;
+               // not sure on the trynext logic here
+               if (!file && dbscan_result(&scan) == -30798) {
+                       switch (ap->playing_state.list_mode) {
+                       case LIST_USER:
+                               // if loop ... just loop
+                               ap->playing_state.list_mode = LIST_JUKEBOX;
+                               trynext = 1;
+                               break;
+                       case LIST_JUKEBOX:
+                               ap->playing_state.list_mode = LIST_DEFAULT;
+                               trynext = 0;
+                               break;
+                       case LIST_DEFAULT:
+                               // loop the default list always
+                               ap->playing_state.list_default.seq = 0;
+                               ap->playing_state.list_default.fileid = 0;
+                               trynext = 0;
+                               break;
+                       }
+               } else {
+                       printf("worked?\n");
                }
                dbscan_free(&scan);
-       } while (!file && retry++ == 0);
+       } while (!file && retry++ < 5);
 
        dbindex_abort(tx);
        return res;
-#if 0
-       audio_close_media(ap);
-       do {
-               //res = dbfile_next(ap->index, &ap->playing, &ap->playing_path);
-               printf("a ap playing = %d\n", ap->playing ? ap->playing->id : -1);
-               res = dbfile_next_shuffle(ap->index, &ap->playing, &ap->playing_path);
-               printf("b ap playing = %d\n", ap->playing ? ap->playing->id : -1);
+}
+
+int audio_next_file(struct audio_player *ap) {
+       return audio_advance_file(ap, dbscan_list_entry_next);
+}
 
-               if (res == 0)
-                       res = audio_init_media(ap, ap->playing_path);
-               if (res == (-30798) && !empty) // && >loop?
-                       res = 1;
-       } while (res != 0 && res !=(-30798)); // MDB_NOTFOUND
+int audio_prev_file(struct audio_player *ap) {
+       return audio_advance_file(ap, dbscan_list_entry_prev);
+}
 
-       audio_checkpoint_state(ap);
+// jump to a file (in a playlist)
+// TODO: if the playlist is the same should it jump to the next occurance of the file in the list?
+int audio_goto_file(struct audio_player *ap, dblistcursor *info) {
+       int res = -1; // MDB_NOTFOUND
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
+       dbfile *file = NULL;
+       dbscan scan;
 
-       //if (res != 0) {
-       //      audio_stop_pcm(ap);
+       //if (info->listid != ap->current->listid) {
+               // now what?  or do we just jump to that (user?) list instead?
+               // yes I think so!
        //}
 
-       return res;
-#endif
-}
+       printf("curret listid=%4d seq=%4d fileid=%4d\n", ap->playing_state.current.listid, ap->playing_state.current.seq, ap->playing_state.current.fileid);
+       printf("goto   listid=%4d seq=%4d fileid=%4d\n", info->listid, info->seq, info->fileid);
 
-int audio_next_file(struct audio_player *ap) {
-       return audio_advance_file(ap, dbscan_list_entry_next);
-#if 0
-       int res;
-       int empty = ap->playing == NULL;
+       // FIXME: file leaks if playing not set.
+       file = dbscan_list_entry(tx, &scan, ap->index, info);
 
-       audio_close_media(ap);
+       if (!file || file->id != info->fileid) {
+               res = MDB_NOTFOUND;
+               goto fail;
+       }
 
-       do {
-               //res = dbfile_next(ap->index, &ap->playing, &ap->playing_path);
-               printf("a ap playing = %d\n", ap->playing ? ap->playing->id : -1);
-               res = dbfile_next_shuffle(ap->index, &ap->playing, &ap->playing_path);
-               printf("b ap playing = %d\n", ap->playing ? ap->playing->id : -1);
+       if (res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file)))
+               goto fail;
 
-               if (res == 0)
-                       res = audio_init_media(ap, ap->playing_path);
-               if (res == (-30798) && !empty) // && >loop?
-                       res = 1;
-       } while (res != 0 && res !=(-30798)); // MDB_NOTFOUND
+       ap->playing = file;
+       file = NULL;
+       ap->playing_state.current = *info;
+
+       // Work out the playlist mode now
+       if (info->listid == ap->playing_state.list_jukebox.listid) {
+               printf("jumping into jukebox list\n");
+               ap->playing_state.list_jukebox = *info;
+               ap->playing_state.list_mode = LIST_JUKEBOX;
+       } else if (info->listid == ap->playing_state.list_default.listid) {
+               printf("jumping into default list\n");
+               ap->playing_state.list_default = *info;
+               ap->playing_state.list_mode = LIST_DEFAULT;
+       } else {
+               printf("jumping to user list\n");
+               ap->playing_state.list_user = *info;
+               ap->playing_state.list_mode = LIST_USER;
+       }
 
        audio_checkpoint_state(ap);
-
-       //if (res != 0) {
-       //      audio_stop_pcm(ap);
-       //}
+       printf("-> now listid=%4d seq=%4d fileid=%4d\n", info->listid, info->seq, info->fileid);
+fail:
+       dbscan_free(&scan);
+       dbindex_abort(tx);
 
        return res;
-#endif
 }
 
-int audio_prev_file(struct audio_player *ap) {
-       return audio_advance_file(ap, dbscan_list_entry_prev);
-#if 0
-       int res;
-       int empty = ap->playing == NULL;
+// play a specific file right now out of band
+int audio_play_now(struct audio_player *ap, int fileid) {
+       int res = -1; // MDB_NOTFOUND
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
+       dbfile *file = dbfile_get(tx, ap->index, fileid);
 
-       audio_close_media(ap);
-       do {
-               //res = dbfile_prev(ap->index, &ap->playing, &ap->playing_path);
-               res = dbfile_prev_shuffle(ap->index, &ap->playing, &ap->playing_path);
-               if (res == 0)
-                       res = audio_init_media(ap, ap->playing_path);
-               if (res == (-30798) && !empty) // && >loop?
-                       res = 1;
-       } while (res != 0 && res !=(-30798)); // MDB_NOTFOUND
+       printf("player goto: %d  diskid=%d\n", fileid, file->diskid);
 
-       audio_checkpoint_state(ap);
+       if (file) {
+               // add to end of the playnow playlist
+               ap->playing_state.list_playnow.seq = 0;
+               ap->playing_state.list_playnow.fileid = fileid;
+               dblist_add_file(tx, ap->index, &ap->playing_state.list_playnow); // ignore result?
 
-       //if (res != 0) {
-       //      audio_stop_pcm(ap);
-       //}
+               res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file));
+               if (res == 0) {
+                       ap->playing = file;
+
+                       ap->playing_state.list_mode = LIST_PLAYNOW;
+                       ap->playing_state.current = ap->playing_state.list_playnow;
+                       printf("playing %s\n", file->path);
+               } else {
+                       dbfile_free(file);
+               }
+       }
+
+       if (res == 0) {
+               dbindex_commit(tx);
+               // separate write transaction
+               // FIXME: put in same transaction?
+               audio_checkpoint_state(ap);
+       } else
+               dbindex_abort(tx);
 
        return res;
-#endif
 }
 
-int audio_goto_file(struct audio_player *ap, int fileid) {
+// jukebox
+// this adds the song to the jukebox playlist but doesn't jump immediately
+// however ... "next song" will jump over it, so ... sigh
+int audio_play_enqueue(struct audio_player *ap, int fileid) {
        int res = -1; // MDB_NOTFOUND
-       dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
        dbfile *file = dbfile_get(tx, ap->index, fileid);
 
-       printf("player goto: %d  diskid=%d\n", fileid, file->diskid);
+       printf("player enqueue: %d  diskid=%d\n", fileid, file->diskid);
 
        if (file) {
-               dbfile_full_path(tx, ap->index, file);
-
-               ap->playing = file;
-
-               res = audio_init_media(ap, file->full_path);
-               if (res == 0) {
-                       audio_checkpoint_state(ap);
+               dblistcursor info;
+
+               // add to end of the jukebox playlist
+               info.listid = ap->playing_state.list_jukebox.listid;
+               info.seq = 0;
+               info.fileid = fileid;
+               if ((res = dblist_add_file(tx, ap->index, &info)) == 0) {
+                       printf("enqueued listid=%4d seq=%4d fileid=%4d\n",
+                               info.listid,
+                               info.seq,
+                               info.fileid);
+
+                       // If we're not already in jukebox mode, set the jukebox state to point
+                       // to the previous file so that 'next file' will find the right one
+                       // FIXME: what about 'prev' file?
+                       // FIXME: find the actual seq in the db
+                       // FIXME: this will drop the last song on restore, perhaps it should have a next_list_mode instead
+                       //        this would also fix the same problem for play-now
+
+                       if (ap->playing_state.list_mode != LIST_JUKEBOX) {
+                               printf(" set play state to jukebox\n");
+                               ap->playing_state.list_jukebox.seq = info.seq -1;
+                               ap->playing_state.list_jukebox.fileid = 0;
+                               ap->playing_state.list_mode = LIST_JUKEBOX;
+                       } else {
+                               printf(" play state already jukebox\n");
+                       }
                }
        }
-       dbindex_abort(tx);
+
+       if (res == 0)
+               dbindex_commit(tx);
+       else
+               dbindex_abort(tx);
 
        return res;
 }
@@ -792,10 +913,12 @@ int audio_restore_state(struct audio_player *ap) {
        dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
        int res = -1;
 
-       // FIXME: playlist stuff?
+       // FIXME: playlist stuff
+       // FIXME: need to check current, etc
+       // Or just advance to next and let advance handle it?
 
        if ((res = dbstate_get(tx, ap->index, &ap->playing_state)) == 0 && ap->playing_state.state == 1) {
-               dbfile *file = dbfile_get(tx, ap->index, ap->playing_state.fileid);
+               dbfile *file = dbfile_get(tx, ap->index, ap->playing_state.current.fileid);
 
                printf("restoring file %s\n", file->path);
                if (file) {
@@ -812,13 +935,24 @@ int audio_restore_state(struct audio_player *ap) {
                // FIXME: check playing_state.state == 1 separate to error test above
                res = -1;
                printf("unable to restore state\n");
-               // ??
-               //ap->playing_state.listid = 1;
        }
 
-       dbindex_commit(tx);
+       // always these
+       ap->playing_state.list_default.listid = dblistid_get_name(tx, ap->index, "all");
+       ap->playing_state.list_playnow.listid = dblistid_get_name(tx, ap->index, "playnow");
+       ap->playing_state.list_jukebox.listid = dblistid_get_name(tx, ap->index, "jukebox");
+
+       {
+               dblist *list = dblist_get(tx, ap->index, ap->playing_state.current.listid);
+
+               printf("restore state=%d file=%d list='%s' '%s'\n", ap->playing_state.state, ap->playing_state.current.fileid,
+                       ap->playing ? ap->playing->full_path : NULL,
+                       list ? list->name : NULL);
+
+               dblist_free(list);
+       }
 
-       printf("restore state=%d file=%d '%s'\n", ap->playing_state.state, ap->playing_state.fileid, ap->playing ? ap->playing->full_path : NULL);
+       dbindex_commit(tx);
 
        if (res == 0)
                return res;
@@ -945,10 +1079,20 @@ void audio_player_control(struct audio_player *ap) {
                case NOTIFY_PLAY_PREV:
                        audio_prev_file(ap);
                        break;
+               case NOTIFY_PLAY_NOW: {
+                       struct notify_goto *g = msg;
+
+                       audio_play_now(ap, g->info.fileid);
+                       break; }
                case NOTIFY_PLAY_GOTO: {
                        struct notify_goto *g = msg;
 
-                       audio_goto_file(ap, g->fileid);
+                       audio_goto_file(ap, &g->info);
+                       break; }
+               case NOTIFY_PLAY_ENQUEUE: {
+                       struct notify_goto *g = msg;
+
+                       audio_play_enqueue(ap, g->info.fileid);
                        break; }
                case NOTIFY_VOLUME_UP:
                        audio_mixer_adjust(ap, +1);
index a1daf81..c3cac2b 100644 (file)
--- a/notify.c
+++ b/notify.c
@@ -51,9 +51,9 @@ static ez_blob_desc KEY_DESC[] = {
 
 static ez_blob_desc GOTO_DESC[] = {
        EZ_BLOB_START(struct notify_goto, 4, 3),
-       EZ_BLOB_INT32(struct notify_goto, 1, listid),
-       EZ_BLOB_INT32(struct notify_goto, 2, seq),
-       EZ_BLOB_INT32(struct notify_goto, 3, fileid),
+       EZ_BLOB_INT32(struct notify_goto, 1, info.listid),
+       EZ_BLOB_INT32(struct notify_goto, 2, info.seq),
+       EZ_BLOB_INT32(struct notify_goto, 3, info.fileid),
 };
 
 /**
@@ -74,6 +74,10 @@ static ez_blob_desc *action_desc[] = {
        NULL,
        NULL,
 
+       GOTO_DESC,
+       GOTO_DESC,
+       GOTO_DESC,
+
        NULL,
        NULL,
        NULL,
@@ -84,9 +88,7 @@ static ez_blob_desc *action_desc[] = {
 
        DEBUG_DESC,
 
-       NULL,
-
-       GOTO_DESC
+       NULL
 };
 
 // should be global by default
index 5501218..73ccf5f 100644 (file)
--- a/notify.h
+++ b/notify.h
@@ -38,6 +38,10 @@ enum notify_action {
        NOTIFY_PLAY_NEXT,
        NOTIFY_PLAY_PREV,
 
+       NOTIFY_PLAY_NOW,        /* play a specific file out-of-band */
+       NOTIFY_PLAY_GOTO,       /* go to list optionally at a specific file (should it be sequence?  or maybe both?) */
+       NOTIFY_PLAY_ENQUEUE,    /* enqueue a file onto the jukebox */
+
        NOTIFY_VOLUME_UP,
        NOTIFY_VOLUME_DOWN,
        NOTIFY_VOLUME_MUTE,
@@ -49,7 +53,6 @@ enum notify_action {
        NOTIFY_DEBUG,           // debug/prototyping command
 
        NOTIFY_SHUFFLE,         /* disk-manager: create shuffled playlist */
-       NOTIFY_PLAY_GOTO,       /* player - go to fileid */
 
        NOTIFY_SIZEOF
 };
@@ -70,9 +73,7 @@ struct notify_key {
 };
 
 struct notify_goto {
-       int listid;
-       int seq;
-       int fileid;
+       dblistcursor info;
 };
 
 notify_t notify_reader_new(const char *path);