From 1a19e07809ddda5d6867cdd57c9f19136699606e Mon Sep 17 00:00:00 2001 From: Not Zed Date: Mon, 14 Jun 2021 15:13:24 +0930 Subject: [PATCH] Big rework of the playlist system. --- TODO | 9 + audio-cmd.c | 36 +- blobs.c | 8 +- dbindex.c | 1763 ++++++++++++++++++----------------------------- dbindex.h | 81 ++- disk-indexer.c | 14 +- http-monitor.c | 42 +- index-util.c | 87 ++- indexer-cmd.c | 2 +- input-monitor.c | 1 + music-player.c | 358 +++++++--- notify.c | 14 +- notify.h | 9 +- 13 files changed, 1095 insertions(+), 1329 deletions(-) diff --git a/TODO b/TODO index 79abcf7..23e767f 100644 --- 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 diff --git a/audio-cmd.c b/audio-cmd.c index 7896273..6a2b4de 100644 --- a/audio-cmd.c +++ b/audio-cmd.c @@ -20,21 +20,11 @@ #include #include #include +#include +#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 --- 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), }; diff --git a/dbindex.c b/dbindex.c index 024cf48..7cc872e 100644 --- a/dbindex.c +++ b/dbindex.c @@ -36,6 +36,8 @@ #include #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;ilistid = 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;iid }; - 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;icount += 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 +#include +#include +#include - 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;ishuffle, &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%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 -#include -#include -#include - -/** - * 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;iid = 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;iseq, .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;ifile_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 && ilistid = 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 // 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); diff --git a/dbindex.h b/dbindex.h index 34f6200..f668ff5 100644 --- 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; // 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 diff --git a/disk-indexer.c b/disk-indexer.c index 8092c08..354916d 100644 --- a/disk-indexer.c +++ b/disk-indexer.c @@ -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; diff --git a/http-monitor.c b/http-monitor.c index e14cf62..db6c49c 100644 --- a/http-monitor.c +++ b/http-monitor.c @@ -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"); diff --git a/index-util.c b/index-util.c index 48f4406..0fbdb92 100644 --- a/index-util.c +++ b/index-util.c @@ -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;iid, 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; } diff --git a/indexer-cmd.c b/indexer-cmd.c index 99b524b..921780a 100644 --- a/indexer-cmd.c +++ b/indexer-cmd.c @@ -3,8 +3,8 @@ #include #include -#include "notify.h" #include "dbindex.h" +#include "notify.h" int main(int argc, char **argv) { if (argc > 1) { diff --git a/input-monitor.c b/input-monitor.c index 745e42e..461bb4d 100644 --- a/input-monitor.c +++ b/input-monitor.c @@ -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 { diff --git a/music-player.c b/music-player.c index 0d7fc78..0c4188d 100644 --- a/music-player.c +++ b/music-player.c @@ -27,10 +27,10 @@ #include #include +#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 : ""); + //printf("entry: %s\n", file->path); + if (trynext) { + dbfile_free(file); + file = advance(&scan, active); + } + //printf("next: %s\n", file ? file->path : ""); + } 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); diff --git a/notify.c b/notify.c index a1daf81..c3cac2b 100644 --- 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 diff --git a/notify.h b/notify.h index 5501218..73ccf5f 100644 --- 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); -- 2.39.2