#include <errno.h>
#include "dbindex.h"
+#include "ez-array.h"
+#include "ez-set.h"
#include "ez-blob.h"
#include "ez-blob-basic.h"
/*
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]
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
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;
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 :
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);
res |= mdb_dbi_open(tx, "file#list", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &db->file_by_list);
mdb_set_dupsort(tx, db->file_by_list, cmp_uint);
res |= mdb_dbi_open(tx, "list#file", MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &db->list_by_file);
- //mdb_set_dupsort(tx, db->list_by_file, cmp_uint);
-
- // to be replaced with file#list perhaps?
- res |= mdb_dbi_open(tx, "shuffle", MDB_CREATE | MDB_INTEGERKEY, &db->shuffle);
- res |= mdb_dbi_open(tx, "shuffle#file", MDB_CREATE | MDB_INTEGERKEY, &db->shuffle_by_file);
// experimental substring search
//res |= mdb_dbi_open(tx, "file#suffix", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERDUP , &db->file_by_suffix);
//mdb_drop(tx, db->file_by_suffix, 1);
res |= mdb_dbi_open(tx, "file#suffix", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERDUP , &db->file_by_suffix);
-
db->diskid = find_next_id(tx, db->disk);
db->listid = find_next_id(tx, db->list);
db->fileid = find_next_id(tx, db->file);
+ // setup system playlists
+ {
+ static const char *names[] = { "all", "all#shuffle", "jukebox", "jukebox#shuffle", "playnow", "playnow#shuffle", "shit" };
+ static const char *descs[] = { "All files ordered by path", "All files shuffled", "All enqueued songs in order", "All endqueued songs shuffled", "All play-now files in order", "All play-now files shuffled", "The shitlist" };
+ dblist list = { 0 };
+
+ for (int i=0;i<sizeof(names)/sizeof(names[0]);i++) {
+ int tmp;
+
+ list.name = (char *)names[i];
+ list.desc = (char *)descs[i];
+ if (tmp = dblist_add(tx, db, &list)) {
+ if (tmp == MDB_KEYEXIST)
+ db->listid = find_next_id(tx, db->list);
+ else {
+ res |= tmp;
+ break;
+ }
+ }
+ }
+ }
+
+
if (0) {
MDB_cursor *cursor;
MDB_val key = { 0 }, data = { 0 };
return NULL;
}
+int dbindex_result(dbindex *db) {
+ return db->res;
+}
+
void dbindex_close(dbindex *db) {
if (db) {
mdb_env_close(db->env);
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.
*
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;
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;
}
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;
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;
// 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;
}
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);
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);
// - 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;
}
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;
}
if (res = mdb_del(tx, db->file, &key, NULL))
goto fail;
- // check lists
+ // check lists, FIXME: cleanup
{
MDB_cursor *cursor;
- size_t alloc = 256;
- size_t size = 0;
- struct dblistfile *list = malloc(sizeof(*list) * alloc);
+ ez_array array = { 0 };
- // FIXME: goto fail leaks list
+ // get all lists this file is in
if ((res = mdb_cursor_open(tx, db->list_by_file, &cursor)))
- goto fail;
+ goto fail1;
- res = mdb_cursor_get(cursor, &key, &data, MDB_SET);
- printf("set list by file: %d\n", res);
+ res = mdb_cursor_get(cursor, &key, &dat, MDB_SET);
while (res == 0) {
- printf(" list: %d @ %d\n", ((struct dblistfile *)data.mv_data)->listid, ((struct dblistfile *)data.mv_data)->seq);
- if (size >= alloc) {
- alloc *= 2;
- list = realloc(list, sizeof(*list) * alloc);
- }
- list[size++] = *(struct dblistfile *)data.mv_data;
-
- res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
+ if (ez_array_add(&array, dat.mv_data, dat.mv_size))
+ res = mdb_cursor_get(cursor, &key, &dat, MDB_NEXT_DUP);
+ else
+ res = ENOMEM;
}
+ printf("get list contents: res=%d\n", res);
mdb_cursor_close(cursor);
+ if (res != MDB_NOTFOUND)
+ goto fail1;
+ // delete the entry we just read
if (res = mdb_del(tx, db->list_by_file, &key, NULL))
- goto fail;
+ goto fail1;
- printf("list entries: %zd\n", size);
+ struct dblistfile *files = array.ea_data;
+ int count = array.ea_size / sizeof(*files);
- for (int i=0;i<size;i++) {
+ // delete all entries in the lists
+ printf("list entries: %d\n", count);
+ for (int i=0;i<count;i++) {
struct dbfilelist fdata = {
- .seq = list[i].seq,
+ .seq = files[i].seq,
.fileid = f->id
};
- printf("delete file %d from list %d @ %d\n", fdata.fileid, list[i].listid, fdata.seq);
- key.mv_data = &list[i].listid;
- key.mv_size = sizeof(list[i].listid);
- data.mv_data = &fdata;
- data.mv_size = sizeof(fdata);
- if (res = mdb_del(tx, db->file_by_list, &key, &data))
- goto fail;
+ printf("delete file %d from list %d @ %d\n", fdata.fileid, files[i].listid, fdata.seq);
+ key.mv_data = &files[i].listid;
+ key.mv_size = sizeof(files[i].listid);
+ dat.mv_data = &fdata;
+ dat.mv_size = sizeof(fdata);
+ if (res = mdb_del(tx, db->file_by_list, &key, &dat))
+ goto fail1;
}
- free(list);
- }
- mdb_txn_commit(tx);
- return res;
+ // update all the list counts
+ ez_set counts = EZ_INIT_SET(counts, hist_hash, hist_equals, free);
+ for (int i=0;i<count;i++) {
+ struct hist_node hk = { .key = files[i].listid };
+ struct hist_node *hn;
+
+ if (hn = ez_set_get(&counts, &hk))
+ hn->count += 1;
+ else if (hn = malloc(sizeof(*hn))) {
+ hn->key = hk.key;
+ hn->count = 1;
+ ez_set_put(&counts, hn);
+ } else {
+ res = ENOMEM;
+ goto fail2;
+ }
+ }
+ ez_set_scan scan;
+ for (struct hist_node *hn = ez_set_scan_init(&counts, &scan); res == 0 && hn; hn = ez_set_scan_next(&scan)) {
+ dblist *list = dblist_get(tx, db, hn->key);
+
+ if (list) {
+ printf("update '%s' %d -> %d\n", list->name, list->size, list->size - hn->count);
+ list->size -= hn->count;
+ res = dblist_put(tx, db, list, 0);
+ } else {
+ res = db->res;
+ }
+ dblist_free(list);
+ }
- fail:
- printf("del failed: %s\n", mdb_strerror(res));
- mdb_txn_abort(tx);
+ ez_set_clear(&counts);
+ fail2:
+
+ fail1:
+ ez_array_clear(&array);
+ }
+fail:
return res;
}
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);
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
goto fail;
}
- return mdb_txn_commit(tx);
-
fail:
- mdb_txn_abort(tx);
return res;
}
+/*
+ Player support functions
+*/
-// TODO: this can be made generic for other indices, see later on
-#if 0
-struct dbscan {
- dbindex *db;
- MDB_cursor *cursor;
- MDB_val key, data;
- int keyval;
- int index;
- int count;
-};
-
-dbscan *dbfile_scan_disk(dbtxn *tx, dbindex *db, int diskid) {
- dbscan *scan = malloc(sizeof(*scan));
- int res;
+// A way to iterate through a lit of files, based on an index or something else
- scan->db = db;
- scan->cursor = NULL;
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
- scan->keyval = diskid;
- scan->key.mv_data = &scan->keyval;
- scan->key.mv_size = sizeof(scan->keyval);
+/**
+ * Check if the disk is mounted.
+ *
+ * This is not generally absolutely reliable but is in the context of
+ * disk-monitor managing the mounts.
+ *
+ * It can be used for quickly discarding files that can't be mounted.
+ *
+ * This is super-slow, don't bother using it, performing a stat on the file
+ * will suffice.
+ */
+int dbdisk_mounted(dbdisk *disk) {
+#if 0
+ // Check the device of the entries
+ char parent[strlen(disk->mount)+1];
+ char *slash = strrchr(parent, '/');
- if ((res = mdb_cursor_open(tx, db->file_by_disk, &scan->cursor)))
- goto fail;
+ if (slash) {
+ struct stat pst, mst;
- if (diskid != -1)
- res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_SET);
- else
- res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_FIRST);
+ *slash = 0;
- if (res) {
- if (res == MDB_NOTFOUND) {
- scan->count = 0;
- scan->index = 0;
- return scan;
- }
- goto fail;
+ // Check if it's already mounted
+ return (stat(disk->mount, &mst) == 0
+ && stat(parent, &pst) == 0
+ && mst.st_dev != pst.st_rdev);
}
- if ((res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_MULTIPLE)))
- goto fail;
-
- scan->count = scan->data.mv_size / sizeof(int);
- scan->index = 0;
-
- return scan;
-
- fail:
- fprintf(stderr, "db scan open fail: %s\n", mdb_strerror(res));
- dbfile_scan_close(scan);
- return NULL;
-}
-
-uint32_t dbfile_scan_next(dbscan *scan) {
- int res = 0;
+ return 0;
+#else
+ // See if the directory is empty
+ // yikes, this is slow as fuck
+ DIR *d = opendir(disk->mount);
+ int entries = 0;
- while (scan->count > 0) {
- if (scan->index < scan->count)
- return ((int *)scan->data.mv_data)[scan->index++];
+ if (d) {
+ struct dirent *de;
- if (res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_NEXT_MULTIPLE)) {
- if (res == MDB_NOTFOUND && scan->keyval == -1) {
- res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_NEXT);
- if (res == 0)
- res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_MULTIPLE);
- }
- if (res)
- goto fail;
+ while (entries == 0 && (de = readdir(d))) {
+ if (strcmp(de->d_name, ".") == 0
+ || strcmp(de->d_name, "..") == 0)
+ continue;
+ entries++;
}
-
- scan->count = scan->data.mv_size / sizeof(int);
- scan->index = 0;
+ closedir(d);
}
- return ~0;
- fail:
- if (res != MDB_NOTFOUND)
- fprintf(stderr, "db scan fail: %s\n", mdb_strerror(res));
- return ~0;
-}
-
-void dbfile_scan_close(dbscan *scan) {
- if (scan->cursor)
- mdb_cursor_close(scan->cursor);
- free(scan);
-}
-#endif
-
-/**
- * Create a newly shuffled playlist.
- */
-void dbshuffle_init(dbindex *db) {
- dbtxn *tx;
- dbscan *scan;
- uint32_t fid;
- int fids_size = 4096;
- int count = 0;
- int res;
- uint32_t *fids = malloc(sizeof(*fids) * fids_size);
-
- // find all current fids
- mdb_txn_begin(db->env, NULL, 0, &tx);
- scan = dbfile_scan_disk(tx, db, -1);
- while ((fid = dbfile_scan_next(scan)) != ~0) {
- if (count >= fids_size) {
- fids_size *= 2;
- fids = realloc(fids, sizeof(*fids) * fids_size);
- }
- fids[count++] = fid;
- }
- printf("total %d\n", count);
- dbfile_scan_close(scan);
-
- // now write them randomly
- mdb_drop(tx, db->shuffle, 0);
- mdb_drop(tx, db->shuffle_by_file, 0);
-
- for (int i=0;i<count;i++) {
- int j = random() % (count-i);
- uint32_t seq = i + 1;
- MDB_val key, data;
-
- fid = fids[i+j];
- fids[i+j] = fids[i];
-
- key.mv_size = sizeof(int);
- key.mv_data = &seq;
- data.mv_size = sizeof(int);
- data.mv_data = &fid;
-
- res = mdb_put(tx, db->shuffle, &key, &data, MDB_NOOVERWRITE);
- res = mdb_put(tx, db->shuffle_by_file, &data, &key, MDB_NOOVERWRITE);
- }
- free(fids);
-
- dbindex_commit(tx);
-}
-
-// create shuffled playlist
-// TODO: start from an existing playlist?
-void dbshuffle_init2(dbindex *db) {
- dbtxn *tx;
- dbscan *scan;
- uint32_t fid;
- int fids_size = 4096;
- int count = 0;
- int res;
- uint32_t *fids = malloc(sizeof(*fids) * fids_size);
-
- // TODO: count? just get it from the thing and scan aagain?
- // find all current fids
- mdb_txn_begin(db->env, NULL, 0, &tx);
-
- scan = dbfile_scan_disk(tx, db, -1);
- while ((fid = dbfile_scan_next(scan)) != ~0) {
- if (count >= fids_size) {
- fids_size *= 2;
- fids = realloc(fids, sizeof(*fids) * fids_size);
- }
- fids[count++] = fid;
- }
- printf("total %d\n", count);
- dbfile_scan_close(scan);
-
- struct dblist list = {
- .size = count,
- .name = "shuffle", // maybe i do want them unique after-all?
- .comment = ""
- };
- struct dbfilelist fvalue;
- struct dblistfile rvalue;
- MDB_val fkey, fdata;
- MDB_val rkey, rdata;
-
- if ((res = dblist_add(tx, db, &list))) {
- printf("add list\n");
- goto fail;
- }
-
- printf("list add ok id=%d\n", list.id);
-
- fkey.mv_data = &list.id;
- fkey.mv_size = sizeof(uint32_t);
- fdata.mv_data = &fvalue;
- fdata.mv_size = sizeof(fvalue);
-
- rkey.mv_size = sizeof(uint32_t);
- rdata.mv_data = &rvalue;
- rdata.mv_size = sizeof(rvalue);
- rvalue.listid = list.id;
-
- // TODO: can shuffle have repeats??
-
- // Playlist instead:
- // [list] [seq][file]
- // ... only iterate by seq?
- // ... how to find playlist by file?
- // ... or just jump by seq?
-
- for (int i=0;i<count;i++) {
- int j = random() % (count-i);
- uint32_t seq = i + 1;
-
- fid = fids[i+j];
- fids[i+j] = fids[i];
-
- fvalue.seq = seq;
- fvalue.fileid = fid;
-
- rvalue.seq = seq;
- rkey.mv_data = &fid;
-
- printf(" %d->%d\n", seq, fid);
-
- if ((res = mdb_put(tx, db->file_by_list, &fkey, &fdata, MDB_NODUPDATA)))
- goto fail;
-
- if ((res = mdb_put(tx, db->list_by_file, &rkey, &rdata, MDB_NODUPDATA)))
- goto fail;
- }
- free(fids);
-
- dbindex_commit(tx);
- return;
-fail:
- printf("reason: %s\n", mdb_strerror(res));
- free(fids);
- mdb_txn_abort(tx);
- return;
-}
-
-/*
- Player support functions
-*/
-
-// A way to iterate through a lit of files, based on an index or something else
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-
-/**
- * Check if the disk is mounted.
- *
- * This is not generally absolutely reliable but is in the context of
- * disk-monitor managing the mounts.
- *
- * It can be used for quickly discarding files that can't be mounted.
- *
- * This is super-slow, don't bother using it, performing a stat on the file
- * will suffice.
- */
-int dbdisk_mounted(dbdisk *disk) {
-#if 0
- // Check the device of the entries
- char parent[strlen(disk->mount)+1];
- char *slash = strrchr(parent, '/');
-
- if (slash) {
- struct stat pst, mst;
-
- *slash = 0;
-
- // Check if it's already mounted
- return (stat(disk->mount, &mst) == 0
- && stat(parent, &pst) == 0
- && mst.st_dev != pst.st_rdev);
- }
-
- return 0;
-#else
- // See if the directory is empty
- // yikes, this is slow as fuck
- DIR *d = opendir(disk->mount);
- int entries = 0;
-
- if (d) {
- struct dirent *de;
-
- while (entries == 0 && (de = readdir(d))) {
- if (strcmp(de->d_name, ".") == 0
- || strcmp(de->d_name, "..") == 0)
- continue;
- entries++;
- }
- closedir(d);
- }
-
- return entries > 0;
-#endif
+ return entries > 0;
+#endif
}
return dbfile_iterate(db, f, fpath, MDB_LAST, MDB_PREV);
}
-// this is a bit different to the by-path shuffle, since the path is used again as output rather
-// than the iteration key
-static int dbfile_iterate_shuffle(dbindex *db, dbfile **fp, char **pathp, int first, int next) {
- MDB_txn *tx;
- MDB_val key, data;
- MDB_cursor *cursor;
- dbfile *file = NULL;
- int res;
+/*
+ Scan based on secondary index.
- mdb_txn_begin(db->env, NULL, MDB_RDONLY, &tx);
+ key.mv_data = &f->diskid;
+ key.mv_size = sizeof(f->diskid);
- if ((res = mdb_cursor_open(tx, db->shuffle, &cursor)))
- //if ((res = mdb_cursor_open(tx, db->shuffle_by_file, &cursor)))
- goto fail;
-#if 0
- printf("scan shuffle\n");
- res = mdb_cursor_get(cursor, &key, &data, first);
- while (res == 0) {
- printf(" seq %d val %d\n", *((int *)key.mv_data), *((int *)data.mv_data));
- res = mdb_cursor_get(cursor, &key, &data, next);
- }
+ data.mv_data = &f->id;
+ data.mv_size = sizeof(f->id);
- return 0;
-#endif
+ //res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_BOTH);
- /*
- Scan based on shuffle order
- */
- int keyval = *fp ? ((*fp)->id) : -1;
- dbdisk *disk = *fp ? dbdisk_get(tx, db, (*fp)->diskid) : NULL;
- //int mounted = *fp ? dbdisk_mounted(disk) : 0;
+*/
+/* playlist management */
- //printf("shuffle next, fid=%d\n", keyval);
+// internal put command - flags might include MDB_NOOVERWRITE
+static int dblist_put(dbtxn *tx, dbindex *db, dblist *list, unsigned int flags) {
+ MDB_val key = { .mv_data = &list->id, .mv_size = sizeof(list->id) };
+ MDB_val dat = { .mv_data = NULL, .mv_size = ez_basic_size(DBLIST_DESC, list) };
+ int res;
- dbfile_free(*fp);
- free(*pathp);
- *fp = NULL;
- *pathp = NULL;
+ if (res = mdb_put(tx, db->list, &key, &dat, flags | MDB_RESERVE))
+ return res;
- if (keyval != -1) {
- data.mv_data = &keyval;
- data.mv_size = sizeof(keyval);
+ ez_basic_encode_raw(DBLIST_DESC, list, (ez_blob *)&dat);
- res = mdb_get(tx, db->shuffle_by_file, &data, &key);
- //printf("get by file = %d, id=%d\n", res, *((int *)key.mv_data));
- if (res == MDB_NOTFOUND) {
- //printf("Not found\n");
- return -1;
- }
+ return 0;
+}
- res = mdb_cursor_get(cursor, &key, &data, MDB_SET);
- //printf(" got shuffle id=%d fid=%d\n", *((int *)key.mv_data), *(int *)data.mv_data);
+dblist *dblist_get(dbtxn *tx, dbindex *db, int id) {
+ MDB_val key = { .mv_data = &id, .mv_size = sizeof(id) };
- res = mdb_cursor_get(cursor, &key, &data, next);
- //printf(" next shuffle id=%d fid=%d\n", *((int *)key.mv_data), *(int *)data.mv_data);
- } else {
- res = mdb_cursor_get(cursor, &key, &data, first);
- }
+ return primary_get_decode(tx, db, DBLIST_DESC, &key, db->list);
+}
- while (file == NULL && res == 0) {
- file = primary_get_decode(tx, db, DBFILE_DESC, &data, db->file);
- if (file) {
- int keep;
+dblist *dblist_get_name(dbtxn *tx, dbindex *db, const char *name) {
+ MDB_val key = {
+ .mv_data = (void *)name,
+ .mv_size = strlen(name)
+ };
- //printf("loaded: %d[%zd] %d?\n", *(int *)data.mv_data, data.mv_size, file->id);
+ return secondary_get_decode(tx, db, DBLIST_DESC, &key, db->list, db->list_by_name);
+}
- if (disk == NULL || file->diskid != disk->id) {
- dbdisk_free(disk);
- disk = dbdisk_get(tx, db, file->diskid);
- //mounted = dbdisk_mounted(disk);
- }
- //keep = mounted;
- //keep = keep && file->duration > 0;
- keep = file->duration > 0;
- if (keep) {
- char path[strlen(disk->mount) + strlen(file->path) + 1];
- struct stat st;
+dbid_t dblistid_get_name(dbtxn *tx, dbindex *db, const char *name) {
+ MDB_val key = {
+ .mv_data = (void *)name,
+ .mv_size = strlen(name)
+ };
+ MDB_val dat;
- sprintf(path, "%s%s", disk->mount, file->path);
+ db->res = mdb_get(tx, db->list_by_name, &key, &dat);
- keep = lstat(path, &st) == 0 && S_ISREG(st.st_mode);
- if (keep) {
- *pathp = strdup(path);
- *fp = file;
- }
- }
+ return db->res == 0 ? *(dbid_t *)dat.mv_data : 0;
+}
- if (!keep) {
- dbfile_free(file);
- file = NULL;
- }
- }
- if (file == NULL)
- res = mdb_cursor_get(cursor, &key, &data, next);
- }
+void dblist_free(dblist *f) {
+ ez_blob_free(DBLIST_DESC, f);
+}
- //free(keyval);
- dbdisk_free(disk);
+// put ? add ? d->id == 0 -> then add, otherwise put?
+int dblist_add(MDB_txn *txn, dbindex *db, dblist *list) {
+ MDB_txn *tx;
+ MDB_val key, dat;
+ int res;
- mdb_cursor_close(cursor);
- mdb_txn_commit(tx);
+ mdb_txn_begin(db->env, txn, 0, &tx);
- //printf("laoded fid=%d\n", file->id);
+ // Store record
+ list->id = list_next_id(db);
+ if (res = dblist_put(tx, db, list, MDB_NOOVERWRITE))
+ goto fail;
- return res;
+ // secondary keys
+ dat.mv_data = &list->id;
+ dat.mv_size = sizeof(list->id);
+
+ // - by name
+ key.mv_data = list->name;
+ key.mv_size = strlen(list->name);
+ if (res = mdb_put(tx, db->list_by_name, &key, &dat, MDB_NOOVERWRITE))
+ goto fail;
+
+ return mdb_txn_commit(tx);
fail:
mdb_txn_abort(tx);
-
return res;
}
-int dbfile_next_shuffle(dbindex *db, dbfile **f, char **fpath) {
- return dbfile_iterate_shuffle(db, f, fpath, MDB_FIRST, MDB_NEXT);
-}
-
-int dbfile_prev_shuffle(dbindex *db, dbfile **f, char **fpath) {
- return dbfile_iterate_shuffle(db, f, fpath, MDB_LAST, MDB_PREV);
-}
-
-/*
- Scan based on secondary index.
-
- key.mv_data = &f->diskid;
- key.mv_size = sizeof(f->diskid);
-
- data.mv_data = &f->id;
- data.mv_size = sizeof(f->id);
-
- //res = mdb_cursor_get(scan->cursor, &scan->key, &scan->data, MDB_GET_BOTH);
-
-*/
+int dblist_clear(dbtxn *tx, dbindex *db, int listid) {
+ MDB_val key, data;
+ int res;
-/* playlist management */
-dblist *dblist_get(dbtxn *tx, dbindex *db, int id) {
- MDB_val key = { .mv_data = &id, .mv_size = sizeof(id) };
+ ez_array array = { 0 };
+ struct dbfilelist *list;
+ size_t count;
- return primary_get_decode(tx, db, DBLIST_DESC, &key, db->list);
-}
+ key.mv_data = &listid;
+ key.mv_size = sizeof(listid);
-void dblist_free(dblist *f) {
- ez_blob_free(DBLIST_DESC, f);
-}
+ res = secondary_list_key(tx, db->file_by_list, key, &array);
+ list = array.ea_data;
+ count = array.ea_size / sizeof(*list);
+ if (count) {
+ printf("found %zd entries\n", count);
+ // delete list values
+ key.mv_data = &listid;
+ key.mv_size = sizeof(listid);
+ if (res = mdb_del(tx, db->file_by_list, &key, NULL)) {
+ printf(" delete list fail\n");
+ goto fail;
+ }
-int dblist_reset(dbtxn *tx, dbindex *db) {
- mdb_drop(tx, db->list, 0);
- mdb_drop(tx, db->file_by_list, 0);
- return mdb_drop(tx, db->list_by_file, 0);
-}
+ // delete reverse table entries
+ struct dblistfile entry = { .listid = listid };
-// put ? add ? d->id == 0 -> then add, otherwise put?
-int dblist_add(MDB_txn *txn, dbindex *db, dblist *d) {
- MDB_txn *tx;
- MDB_val key, data;
- int res;
+ data.mv_data = &entry;
+ data.mv_size = sizeof(entry);
- mdb_txn_begin(db->env, txn, 0, &tx);
+ for (int i=0;i<count;i++) {
+ key.mv_data = &list[i].fileid;
+ key.mv_size = sizeof(list[i].fileid);
- // Store record
- d->id = list_next_id(db);
- key.mv_data = &d->id;
- key.mv_size = sizeof(d->id);
+ entry.seq = list[i].seq;
- data.mv_size = ez_basic_size(DBLIST_DESC, d);
- data.mv_data = NULL;
- res = mdb_put(tx, db->list, &key, &data, MDB_NOOVERWRITE | MDB_RESERVE);
- if (res == 0) {
- ez_basic_encode_raw(DBLIST_DESC, d, (ez_blob *)&data);
- mdb_txn_commit(tx);
- } else {
- printf("db put list fail: %s\n", mdb_strerror(res));
- mdb_txn_abort(tx);
+ if (res = mdb_del(tx, db->list_by_file, &key, &data)) {
+ printf(" delete fail fid=%d -> list=%d seq=%d\n", list[i].fileid, entry.listid, entry.seq);
+ if (res == MDB_NOTFOUND) {
+ printf(" db inconsistent, continuing\n");
+ } else
+ goto fail;
+ }
+ }
}
+fail:
+ free(list);
+ printf("db clear list: %s\n", mdb_strerror(res));
return res;
}
return -1;
}
-int dblist_add_file(MDB_txn *txn, dbindex *db, dblist *d, int fileid) {
- MDB_txn *tx;
- MDB_val key, data;
+// info is in/out, in=listid, fileid, out=listid, seq, fileid
+int dblist_add_file(MDB_txn *tx, dbindex *db, dblistcursor *info) {
+ MDB_val key, dat;
int res;
- struct dbfilelist fvalue = { .seq = d->size + 1, .fileid = fileid };
- struct dblistfile rvalue = { .listid = d->id, .seq = d->size + 1 };
+ dblist *list = dblist_get(tx, db, info->listid);
- mdb_txn_begin(db->env, txn, 0, &tx);
+ if (!list)
+ return db->res;
// Check file exists
- key.mv_data = &fileid;
- key.mv_size = sizeof(fileid);
- if (mdb_get(tx, db->file, &key, &data) != 0) {
+ if (!primary_exists(tx, db, info->fileid, db->file)) {
printf("FOREIGN: file doesn't exist\n");
- goto fail;
+ dblist_free(list);
+ return db->res;
}
- // TODO: foriegn constraint on listid ... or just take list name and look it up
+ struct dbfilelist fvalue = { .seq = list->size + 1, .fileid = info->fileid };
+ struct dblistfile rvalue = { .listid = list->id, .seq = list->size + 1 };
- key.mv_data = &d->id;
- key.mv_size = sizeof(d->id);
- data.mv_data = &fvalue;
- data.mv_size = sizeof(fvalue);
+ key.mv_data = &list->id;
+ key.mv_size = sizeof(list->id);
+ dat.mv_data = &fvalue;
+ dat.mv_size = sizeof(fvalue);
- printf("put file by list: { listid = %d } <- { seq = %d fileid = %d }\n", d->id, fvalue.seq, fvalue.fileid);
+ printf("put file by list: { listid = %d } <- { seq = %d fileid = %d }\n", list->id, fvalue.seq, fvalue.fileid);
- if ((res = mdb_put(tx, db->file_by_list, &key, &data, MDB_NODUPDATA)))
+ if ((res = mdb_put(tx, db->file_by_list, &key, &dat, MDB_NODUPDATA)))
goto fail;
- key.mv_data = &fileid;
- key.mv_size = sizeof(fileid);
- data.mv_data = &rvalue;
- data.mv_size = sizeof(rvalue);
+ key.mv_data = &info->fileid;
+ key.mv_size = sizeof(info->fileid);
+ dat.mv_data = &rvalue;
+ dat.mv_size = sizeof(rvalue);
- printf("put list by file: fileid = %d { listid = %d .seq = %d }\n", fileid, rvalue.listid, rvalue.seq);
+ printf("put list by file: fileid = %d { listid = %d .seq = %d }\n", info->fileid, rvalue.listid, rvalue.seq);
- if ((res = mdb_put(tx, db->list_by_file, &key, &data, MDB_NODUPDATA)))
+ if ((res = mdb_put(tx, db->list_by_file, &key, &dat, MDB_NODUPDATA)))
goto fail;
// update list record with changed size
// TODO: can i just poke in the size value?
- d->size += 1;
-
- key.mv_data = &d->id;
- key.mv_size = sizeof(d->id);
-
- data.mv_size = ez_basic_size(DBLIST_DESC, d);
- data.mv_data = NULL;
- res = mdb_put(tx, db->list, &key, &data, MDB_RESERVE);
- if (res == 0)
- ez_basic_encode_raw(DBLIST_DESC, d, (ez_blob *)&data);
- printf("update seq %d\n", d->size);
-
+ list->size += 1;
+ printf("update seq %d\n", list->size);
+ res = dblist_put(tx, db, list, 0);
if (res == 0)
- return mdb_txn_commit(tx);
+ info->seq = fvalue.seq;
fail:
- printf("fail: %s\n", mdb_strerror(res));
- mdb_txn_abort(tx);
+ dblist_free(list);
return res;
}
-void dblist_dump(dbtxn *tx, dbindex *db) {
- //MDB_txn *tx;
- MDB_val key, data;
- MDB_cursor *cursor;
- int res;
-
- //if (res = mdb_txn_begin(db->env, txn, MDB_RDONLY, &tx)) {
- // printf("failed: %s\n", mdb_strerror(res));
- // return;
- //}
-
+// if the list exists clear it, otherwise create it
+dblist *dblist_init(dbtxn *tx, dbindex *db, const char *name) {
+ dblist *list = dblist_get_name(tx, db, name);
- printf("dump all lists\n");
- printf("list_by_file =\n");
- res = mdb_cursor_open(tx, db->list_by_file, &cursor);
- res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
- while (res == 0) {
- struct dblistfile *value = data.mv_data;
- uint32_t fid = *(uint32_t *)key.mv_data;
-
- printf(" file %d list %d seq %d\n", fid, value->listid, value->seq);
-
- res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
- if (res == MDB_NOTFOUND)
- res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+ if (list) {
+ if ((db->res = dblist_clear(tx, db, list->id)) ==0)
+ return list;
+ } else {
+ if ((list = calloc(1, sizeof(*list)))
+ && (list->name = strdup(name))
+ && (db->res = dblist_add(tx, db, list)) == 0)
+ return list;
}
- mdb_cursor_close(cursor);
-
- printf("file_by_list =\n");
- res = mdb_cursor_open(tx, db->file_by_list, &cursor);
- res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
- while (res == 0) {
- struct dbfilelist *value = data.mv_data;
- uint32_t lid = *(uint32_t *)key.mv_data;
+ dblist_free(list);
+ return NULL;
+}
- printf(" list %d file %d seq %d\n", lid, value->fileid, value->seq);
+static void array_shuffle(dbid_t *ids, size_t count) {
+ for (size_t i=0;i<count;i++) {
+ size_t j = random() % (count-i);
+ int idi = ids[i];
+ int idj = ids[i+j];
- res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
- if (res == MDB_NOTFOUND)
- res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+ ids[i] = idj;
+ ids[i+j] = idi;
}
- mdb_cursor_close(cursor);
-
-
- //mdb_txn_commit(tx);
}
-// only list + seq is required
-int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) {
- MDB_txn *tx;
- MDB_val key, data;
- MDB_cursor *cursor;
- int res;
- struct dbfilelist fvalue = { .seq = list->seq, .fileid = list->fileid };
- struct dblistfile rvalue = { .listid = list->listid, .seq = list->seq };
-
- printf("list_del_file: lid=%4d seq=%4d fid=%4d\n", list->listid, list->seq, list->fileid);
-
- if (res = mdb_txn_begin(db->env, txn, 0, &tx))
- goto fail0;
+// Set a playlist to a specific sequence
+int dblist_update(dbtxn *tx, dbindex *db, const char *name, dbid_t *fids, size_t count) {
+ dblist *list;
+ int res = 0;
- int delcursor = 0;
- int delfile = 0;
- int dellist = 0;
+ list = dblist_init(tx, db, name);
+ if (!list)
+ return db->res;
- if (list->seq == 0) {
- // No sequence, lookup (first) fileid for the list
- if (res = mdb_cursor_open(tx, db->list_by_file, &cursor))
- goto fail;
+ struct dbfilelist listvalue;
+ MDB_val listdata = { .mv_data= &listvalue, .mv_size = sizeof(listvalue) };
+ MDB_val listid = { .mv_data = &list->id, .mv_size = sizeof(dbid_t) };
- key.mv_data = &list->fileid;
- key.mv_size = sizeof(list->fileid);
- data.mv_data = &rvalue;
- data.mv_size = sizeof(rvalue);
+ struct dblistfile filevalue = { .listid = list->id };
+ MDB_val filedata = { .mv_data= &filevalue, .mv_size = sizeof(filevalue) };
+ MDB_val fileid = { .mv_size = sizeof(dbid_t) };
- if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE))
+ for (size_t i=0;i<count;i++) {
+ listvalue.seq = i + 1;
+ listvalue.fileid = fids[i];
+ if ((res = mdb_put(tx, db->file_by_list, &listid, &listdata, MDB_NODUPDATA)))
goto fail;
- fvalue.seq = list->seq = ((struct dblistfile *)data.mv_data)->seq;
-
- printf("list_del_file: found seq=%4d\n", list->seq);
-
- delcursor = 1;
- delfile = 1;
- } else if (list->fileid == 0) {
- // Lookup fileid for list[seq]
- if (res = mdb_cursor_open(tx, db->file_by_list, &cursor))
+ fileid.mv_data = &fids[i];
+ filevalue.seq = i + 1;
+ if ((res = mdb_put(tx, db->list_by_file, &fileid, &filedata, MDB_NODUPDATA)))
goto fail;
+ }
- key.mv_data = &list->listid;
- key.mv_size = sizeof(list->listid);
- data.mv_data = &fvalue;
- data.mv_size = sizeof(fvalue);
+ // update/fix list record
+ list->size = count;
+ res = dblist_put(tx, db, list, 0);
- if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH))
- goto fail;
-
- list->fileid = ((struct dbfilelist *)data.mv_data)->fileid;
+fail:
+ printf("db list update: %s\n", mdb_strerror(res));
+ dblist_free(list);
+ return res;
+}
- printf("list_del_file: found fid=%4d\n", list->fileid);
+// Shuffle a specific list
+// load whole list
+// shuffle it
+// save shuffled version?
+int dblist_shuffle(dbtxn *tx, dbindex *db, const char *name) {
+ return -1;
+}
- delcursor = 1;
- dellist = 1;
- } else {
- // use supplied values
- delfile = 1;
- dellist = 1;
+// init sysstem lists, doesn't destroy any existing lists
+int dblist_init_system(dbtxn *tx, dbindex *db) {
+ int res = 0;
+ static const char *names[] = { "all", "all#shuffle", "jukebox", "jukebox#shuffle", "playnow", "playnow#shuffle", "shit" };
+ static const char *descs[] = { "All files ordered by path", "All files shuffled", "Enqueued songs in order", "Endqueued songs shuffled", "Record of play-now files in play order", "Record of play-now files shuffled", "The shitlist" };
+ dblist list = { 0 };
+
+ for (int i=0;res == 0 && i<sizeof(names)/sizeof(names[0]);i++) {
+ int tmp;
+
+ list.name = (char *)names[i];
+ list.desc = (char *)descs[i];
+ if (tmp = dblist_add(tx, db, &list)) {
+ if (tmp == MDB_KEYEXIST)
+ db->listid = find_next_id(tx, db->list);
+ else
+ res = tmp;
+ }
}
+ return res;
+}
- if (delcursor && (res = mdb_cursor_del(cursor, 0)))
- goto fail;
-
- if (delfile) {
- key.mv_data = &list->listid;
- key.mv_size = sizeof(list->listid);
- data.mv_data = &fvalue;
- data.mv_size = sizeof(fvalue);
+// remove all lists and recreate the all list
+int dblist_reset_all(dbtxn *tx, dbindex *db) {
+ int res;
- if (res = mdb_del(tx, db->file_by_list, &key, &data))
- goto fail;
+ // empty all
+ if ((res = mdb_drop(tx, db->list, 0)) == 0
+ && (res = mdb_drop(tx, db->list_by_name, 0)) == 0
+ && (res = mdb_drop(tx, db->file_by_list, 0)) == 0
+ && (res = mdb_drop(tx, db->list_by_file, 0)) == 0) {
+ db->listid = 1;
+ if ((res = dblist_init_system(tx, db)) == 0)
+ res = dblist_update_all(tx, db);
}
+ return res;
+}
- if (dellist) {
- key.mv_data = &list->fileid;
- key.mv_size = sizeof(list->fileid);
- data.mv_data = &rvalue;
- data.mv_size = sizeof(rvalue);
+// refresh the all list
+// list ordered by path
+int dblist_update_all(dbtxn *tx, dbindex *db) {
+ int res;
+ size_t count = 0;
+ dbid_t *fids = NULL;
+ ez_array array = { 0 };
- if (res = mdb_del(tx, db->list_by_file, &key, &data))
- goto fail;
- }
+ if (res = secondary_list_all(tx, db->file_by_path, &array))
+ goto fail;
- if (delcursor)
- mdb_cursor_close(cursor);
+ fids = array.ea_data;
+ count = array.ea_size / sizeof(*fids);
- mdb_txn_commit(tx);
- return 0;
+ // Create the all list
+ if (res = dblist_update(tx, db, "all", fids, count))
+ goto fail;
-fail:
- printf("fail: %s\n", mdb_strerror(res));
- if (delcursor)
- mdb_cursor_close(cursor);
+ // Create the all shuffled list
+ array_shuffle(fids, count);
+ res = dblist_update(tx, db, "all#shuffle", fids, count);
- mdb_txn_abort(tx);
-fail0:
+fail:
+ ez_array_clear(&array);
return res;
}
-#if 0
-int dblist_iterate(dbtxn *tx, dbindex *db, struct dblistcursor *pos) {
+// debug
+
+void dblist_dump(dbtxn *tx, dbindex *db) {
//MDB_txn *tx;
MDB_val key, data;
MDB_cursor *cursor;
int res;
- ez_blob_free_raw(DBFILE_DESC, &pos->file);
- memset(&pos->file, 0, sizeof(pos->file));
-
- //printf("iterate list %d\n", pos->listid);
-
- //if (res = mdb_txn_begin(db->env, txn, 0, &tx))
- // goto fail1;
-
- //dblist_dump(tx, db);
-
- if (pos->listid == 0) {
- // just go by fileid order
- if ((res = mdb_cursor_open(tx, db->file, &cursor)))
- goto fail0;
-
- key.mv_data = &pos->fileid;
- key.mv_size = sizeof(pos->fileid);
-
- if (res = mdb_cursor_get(cursor, &key, &data, MDB_SET_RANGE))
- goto fail;
- if (res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT))
- goto fail;
-
- dbfile_decode_raw((ez_blob *)&data, &pos->file);
- pos->fileid = pos->file.id = *(uint32_t*)key.mv_data;
- } else {
- // go by current info
- if ((res = mdb_cursor_open(tx, db->file_by_list, &cursor)))
- goto fail0;
+ //if (res = mdb_txn_begin(db->env, txn, MDB_RDONLY, &tx)) {
+ // printf("failed: %s\n", mdb_strerror(res));
+ // return;
+ //}
- struct dbfilelist thing = { .seq = pos->seq + 1, .fileid = 0 };
- //printf(" from seq %d file %d\n", thing.seq, thing.fileid);
+ printf("dump all lists\n");
+ printf("list_by_file =\n");
+ res = mdb_cursor_open(tx, db->list_by_file, &cursor);
+ res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+ while (res == 0) {
+ struct dblistfile *value = data.mv_data;
+ uint32_t fid = *(uint32_t *)key.mv_data;
- key.mv_data = &pos->listid;
- key.mv_size = sizeof(pos->listid);
- data.mv_data = &thing;
- data.mv_size = sizeof(thing);
+ printf(" file %d list %d seq %d\n", fid, value->listid, value->seq);
- if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE))
- goto fail;
+ res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
+ if (res == MDB_NOTFOUND)
+ res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+ }
+ mdb_cursor_close(cursor);
+ printf("file_by_list =\n");
+ res = mdb_cursor_open(tx, db->file_by_list, &cursor);
+ res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+ while (res == 0) {
struct dbfilelist *value = data.mv_data;
+ uint32_t lid = *(uint32_t *)key.mv_data;
- pos->seq = value->seq;
- pos->fileid = value->fileid;
-
- key.mv_data = &pos->fileid;
- key.mv_size = sizeof(pos->fileid);
-
- if (res = mdb_get(tx, db->file, &key, &data))
- goto fail;
+ printf(" list %d file %d seq %d\n", lid, value->fileid, value->seq);
- dbfile_decode_raw((ez_blob *)&data, &pos->file);
- pos->file.id = pos->fileid;
+ res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
+ if (res == MDB_NOTFOUND)
+ res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
}
-
mdb_cursor_close(cursor);
- //mdb_txn_commit(tx);
-
- return 0;
-fail:
- mdb_cursor_close(cursor);
-fail0:
- //mdb_txn_abort(tx);
-//fail1:
- printf("fail: %s\n", mdb_strerror(res));
- return res;
-}
-#endif
-#if 0
-static int dbfile_iterate_list(dbindex *db, int listid, dbfile **fp, int dir) {
- MDB_txn *tx;
- MDB_val key, data;
- MDB_cursor *cursor;
- dbfile *file = NULL;
- int res;
-
- mdb_txn_begin(db->env, NULL, MDB_RDONLY, &tx);
-
- if ((res = mdb_cursor_open(tx, db->file_by_list, &cursor)))
- goto fail;
-
- int keyval = *fp ? ((*fp)->id) : -1;
- dbdisk *disk = *fp ? dbdisk_get(tx, db, (*fp)->diskid) : NULL;
- int mounted = *fp ? dbdisk_mounted(disk) : 0;
- //printf("\nlist iterate: fid=%d\n", keyval);
- dbfile_free(*fp);
- *fp = NULL;
+ //mdb_txn_commit(tx);
+}
- /*
- This mess is to handle various cases:
- - first call
- - next from file
- */
+// only list + seq is required
+int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) {
+ MDB_txn *tx;
+ MDB_val key, data;
+ MDB_cursor *cursor;
+ int res;
+ struct dbfilelist fvalue = { .seq = list->seq, .fileid = list->fileid };
+ struct dblistfile rvalue = { .listid = list->listid, .seq = list->seq };
+
+ printf("list_del_file: lid=%4d seq=%4d fid=%4d\n", list->listid, list->seq, list->fileid);
- if (keyval != -1) {
- MDB_cursor *rcursor;
+ if (res = mdb_txn_begin(db->env, txn, 0, &tx))
+ goto fail0;
- // find current sequence number via GET_BOTH lookup
- res = mdb_cursor_open(tx, db->list_by_file, &rcursor);
+ int delcursor = 0;
+ int delfile = 0;
+ int dellist = 0;
- struct dblistfile rval = { .listid = listid };
+ if (list->seq == 0) {
+ // No sequence, lookup (first) fileid for the list
+ if (res = mdb_cursor_open(tx, db->list_by_file, &cursor))
+ goto fail;
- key.mv_data = &keyval;
- key.mv_size = sizeof(keyval);
+ key.mv_data = &list->fileid;
+ key.mv_size = sizeof(list->fileid);
+ data.mv_data = &rvalue;
+ data.mv_size = sizeof(rvalue);
- data.mv_data = &rval;
- data.mv_size = sizeof(rval);
+ if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE))
+ goto fail;
- //rval.seq = keyval - 1;
+ fvalue.seq = list->seq = ((struct dblistfile *)data.mv_data)->seq;
- //printf("seek : fileid=%d { listid=%d seq=%d }\n", keyval, rval.listid, rval.seq);
+ printf("list_del_file: found seq=%4d\n", list->seq);
- res = mdb_cursor_get(rcursor, &key, &data, MDB_GET_BOTH_RANGE);
- if (res != 0) {
- //printf("seek failed = %s\n", mdb_strerror(res));
- mdb_cursor_close(rcursor);
+ delcursor = 1;
+ delfile = 1;
+ } else if (list->fileid == 0) {
+ // Lookup fileid for list[seq]
+ if (res = mdb_cursor_open(tx, db->file_by_list, &cursor))
goto fail;
- }
- //printf("position: fileid=%d { listid=%d seq=%d }\n", keyval, rval.listid, rval.seq);
- struct dblistfile *dval = data.mv_data;
- struct dbfilelist fval = { .seq = dval->seq };
+ key.mv_data = &list->listid;
+ key.mv_size = sizeof(list->listid);
+ data.mv_data = &fvalue;
+ data.mv_size = sizeof(fvalue);
- key.mv_data = &listid;
- key.mv_size = sizeof(listid);
+ if (res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH))
+ goto fail;
- data.mv_data = &fval;
- data.mv_size = sizeof(fval);
+ list->fileid = ((struct dbfilelist *)data.mv_data)->fileid;
- //printf("seek : listid=%d { seq = %d fileid=%d }\n", listid, fval.seq, fval.fileid);
- res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH);
- //printf("seek: %s\n", mdb_strerror(res));
- //printf("position: listid=%d { seq = %d fileid=%d }\n", listid, fval.seq, fval.fileid);
- res = mdb_cursor_get(cursor, &key, &data, dir == 0 ? MDB_NEXT_DUP : MDB_PREV_DUP);
- //printf("next: %s\n", mdb_strerror(res));
- //printf("position: listid=%d { seq = %d fileid=%d }\n", listid, fval.seq, fval.fileid);
+ printf("list_del_file: found fid=%4d\n", list->fileid);
- mdb_cursor_close(rcursor);
+ delcursor = 1;
+ dellist = 1;
} else {
- key.mv_data = &listid;
- key.mv_size = sizeof(listid);
- res = mdb_cursor_get(cursor, &key, &data, MDB_SET);
- if (res == 0 && dir == 1)
- res = mdb_cursor_get(cursor, &key, &data, MDB_LAST_DUP);
-
- //struct dbfilelist *fval = data.mv_data;
- //printf("first : listid=%d { seq = %d fileid=%d }\n", listid, fval->seq, fval->fileid);
+ // use supplied values
+ delfile = 1;
+ dellist = 1;
}
- while (file == NULL && res == 0) {
- struct dbfilelist *dval = data.mv_data;
- MDB_val pkey = { sizeof(int), &dval->fileid };
-
- file = primary_get_decode(tx, db, DBFILE_DESC, &pkey, db->file);
- if (file) {
- int keep;
-
- //printf("loaded: %d[%d]\n", dval->fileid, dval->seq);
-
- if (disk == NULL || file->diskid != disk->id) {
- dbdisk_free(disk);
- disk = dbdisk_get(tx, db, file->diskid);
- mounted = dbdisk_mounted(disk);
- }
- keep = mounted;
- keep = keep && file->duration > 0;
- if (keep) {
- char path[strlen(disk->mount) + strlen(file->path) + 1];
- struct stat st;
+ if (delcursor && (res = mdb_cursor_del(cursor, 0)))
+ goto fail;
- sprintf(path, "%s%s", disk->mount, file->path);
+ if (delfile) {
+ key.mv_data = &list->listid;
+ key.mv_size = sizeof(list->listid);
+ data.mv_data = &fvalue;
+ data.mv_size = sizeof(fvalue);
- //printf("check %s\n", path);
+ if (res = mdb_del(tx, db->file_by_list, &key, &data))
+ goto fail;
+ }
- keep = lstat(path, &st) == 0 && S_ISREG(st.st_mode);
- if (keep) {
- file->full_path = strdup(path);
- }
- }
+ if (dellist) {
+ key.mv_data = &list->fileid;
+ key.mv_size = sizeof(list->fileid);
+ data.mv_data = &rvalue;
+ data.mv_size = sizeof(rvalue);
- if (!keep) {
- dbfile_free(file);
- file = NULL;
- }
- }
- if (file == NULL)
- res = mdb_cursor_get(cursor, &key, &data, dir == 0 ? MDB_NEXT_DUP : MDB_PREV_DUP);
+ if (res = mdb_del(tx, db->list_by_file, &key, &data))
+ goto fail;
}
- //free(keyval);
- dbdisk_free(disk);
+ if (delcursor)
+ mdb_cursor_close(cursor);
- mdb_cursor_close(cursor);
mdb_txn_commit(tx);
+ return 0;
- *fp = file;
+fail:
+ printf("fail: %s\n", mdb_strerror(res));
+ if (delcursor)
+ mdb_cursor_close(cursor);
- return res;
- fail:
- // close cursor?
mdb_txn_abort(tx);
-
+fail0:
return res;
}
-int dbfile_next_list(dbindex *db, int listid, dbfile **fp) {
- return dbfile_iterate_list(db, listid, fp, 0);
-}
-
-int dbfile_prev_list(dbindex *db, int listid, dbfile **fp) {
- return dbfile_iterate_list(db, listid, fp, 1);
-}
-#endif
-
#include <regex.h>
// TODO: should run over title index instead?
/* 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;
return NULL;
}
+// TBD
static uint32_t dbscan_secondary_next(dbscan *scan) {
int res = 0;
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);
}
* 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);
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 */
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);
#include <poll.h>
#include <pthread.h>
+#include "dbindex.h"
#include "notify.h"
#include "ez-list.h"
#include "ez-port.h"
-#include "dbindex.h"
struct audio_player;
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
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,
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)),
}
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) {
return 0;
}
+static void dump_cursor(const char *what, dblistcursor *info) {
+ printf("%10s: listid=%4d seq=%4d fileid=%4d\n", what, info->listid, info->seq, info->fileid);
+}
+
// Next in play queue
-static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan *scan)) {
+/*
+ Playlist logic.
+
+ The current playing mode is in list_mode.
+ Tracks are found by priority:
+ - PLAYNOW
+ - USER
+ - JUKEBOX
+ - DEFAULT
+
+ Once a file finishes the search begins at USER always?
+ */
+static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan *scan, dblistcursor *info)) {
int res;
int empty = ap->playing == NULL;
dbscan scan;
dbfile *file;
dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
int retry = 0;
+ int trynext = 1;
dbfile_free(ap->playing);
ap->playing = NULL;
+ // Playnow only runs once
+ if (ap->playing_state.list_mode == LIST_PLAYNOW)
+ ap->playing_state.list_mode = LIST_USER;
+
do {
+ dblistcursor *active;
+
+ switch (ap->playing_state.list_mode) {
+ case LIST_DEFAULT:
+ default:
+ printf("trying default playlist\n");
+ active = &ap->playing_state.list_default;
+ break;
+ case LIST_JUKEBOX:
+ printf("trying jukebox playlist\n");
+ active = &ap->playing_state.list_jukebox;
+ break;
+ case LIST_USER:
+ printf("trying user playlist\n");
+ active = &ap->playing_state.list_user;
+ break;
+ }
+
+ dump_cursor("active?", active);
+
printf("loop: %d\n", retry);
- file = dbscan_list_entry(tx, &scan, ap->index, ap->playing_state.listid, ap->playing_state.seq, ap->playing_state.fileid);
+ file = dbscan_list_entry(tx, &scan, ap->index, active);
if (file) {
- printf("entry: %s\n", file->path);
- dbfile_free(file);
- file = advance(&scan);
- printf("next: %s\n", file ? file->path : "<nil>");
+ //printf("entry: %s\n", file->path);
+ if (trynext) {
+ dbfile_free(file);
+ file = advance(&scan, active);
+ }
+ //printf("next: %s\n", file ? file->path : "<nil>");
+ } else {
+ // repeat? reset?
+ printf("no entry found. scan.res =%d db.res=%d\n", dbscan_result(&scan), dbindex_result(ap->index));
}
while (file) {
+ dump_cursor("playing?", active);
+
res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file));
if (res == (-30798) && !empty) // && >loop?
res = 1;
- if (res == 0) {
+ if (res == 0) {
+ ap->playing_state.current = *active;
ap->playing = file;
- ap->playing_state.listid = dbscan_list_entry_listid(&scan);
- ap->playing_state.seq = dbscan_list_entry_seq(&scan);
- ap->playing_state.fileid = file->id;
audio_checkpoint_state(ap);
break;
}
dbfile_free(file);
- file = advance(&scan);
+ file = advance(&scan, active);
}
+
// repeat at end of list?
- if (!file && dbscan_res(&scan) == -30798) {
- ap->playing_state.seq = 0;
- ap->playing_state.fileid = 0;
+ // not sure on the trynext logic here
+ if (!file && dbscan_result(&scan) == -30798) {
+ switch (ap->playing_state.list_mode) {
+ case LIST_USER:
+ // if loop ... just loop
+ ap->playing_state.list_mode = LIST_JUKEBOX;
+ trynext = 1;
+ break;
+ case LIST_JUKEBOX:
+ ap->playing_state.list_mode = LIST_DEFAULT;
+ trynext = 0;
+ break;
+ case LIST_DEFAULT:
+ // loop the default list always
+ ap->playing_state.list_default.seq = 0;
+ ap->playing_state.list_default.fileid = 0;
+ trynext = 0;
+ break;
+ }
+ } else {
+ printf("worked?\n");
}
dbscan_free(&scan);
- } while (!file && retry++ == 0);
+ } while (!file && retry++ < 5);
dbindex_abort(tx);
return res;
-#if 0
- audio_close_media(ap);
- do {
- //res = dbfile_next(ap->index, &ap->playing, &ap->playing_path);
- printf("a ap playing = %d\n", ap->playing ? ap->playing->id : -1);
- res = dbfile_next_shuffle(ap->index, &ap->playing, &ap->playing_path);
- printf("b ap playing = %d\n", ap->playing ? ap->playing->id : -1);
+}
+
+int audio_next_file(struct audio_player *ap) {
+ return audio_advance_file(ap, dbscan_list_entry_next);
+}
- if (res == 0)
- res = audio_init_media(ap, ap->playing_path);
- if (res == (-30798) && !empty) // && >loop?
- res = 1;
- } while (res != 0 && res !=(-30798)); // MDB_NOTFOUND
+int audio_prev_file(struct audio_player *ap) {
+ return audio_advance_file(ap, dbscan_list_entry_prev);
+}
- audio_checkpoint_state(ap);
+// jump to a file (in a playlist)
+// TODO: if the playlist is the same should it jump to the next occurance of the file in the list?
+int audio_goto_file(struct audio_player *ap, dblistcursor *info) {
+ int res = -1; // MDB_NOTFOUND
+ dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
+ dbfile *file = NULL;
+ dbscan scan;
- //if (res != 0) {
- // audio_stop_pcm(ap);
+ //if (info->listid != ap->current->listid) {
+ // now what? or do we just jump to that (user?) list instead?
+ // yes I think so!
//}
- return res;
-#endif
-}
+ printf("curret listid=%4d seq=%4d fileid=%4d\n", ap->playing_state.current.listid, ap->playing_state.current.seq, ap->playing_state.current.fileid);
+ printf("goto listid=%4d seq=%4d fileid=%4d\n", info->listid, info->seq, info->fileid);
-int audio_next_file(struct audio_player *ap) {
- return audio_advance_file(ap, dbscan_list_entry_next);
-#if 0
- int res;
- int empty = ap->playing == NULL;
+ // FIXME: file leaks if playing not set.
+ file = dbscan_list_entry(tx, &scan, ap->index, info);
- audio_close_media(ap);
+ if (!file || file->id != info->fileid) {
+ res = MDB_NOTFOUND;
+ goto fail;
+ }
- do {
- //res = dbfile_next(ap->index, &ap->playing, &ap->playing_path);
- printf("a ap playing = %d\n", ap->playing ? ap->playing->id : -1);
- res = dbfile_next_shuffle(ap->index, &ap->playing, &ap->playing_path);
- printf("b ap playing = %d\n", ap->playing ? ap->playing->id : -1);
+ if (res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file)))
+ goto fail;
- if (res == 0)
- res = audio_init_media(ap, ap->playing_path);
- if (res == (-30798) && !empty) // && >loop?
- res = 1;
- } while (res != 0 && res !=(-30798)); // MDB_NOTFOUND
+ ap->playing = file;
+ file = NULL;
+ ap->playing_state.current = *info;
+
+ // Work out the playlist mode now
+ if (info->listid == ap->playing_state.list_jukebox.listid) {
+ printf("jumping into jukebox list\n");
+ ap->playing_state.list_jukebox = *info;
+ ap->playing_state.list_mode = LIST_JUKEBOX;
+ } else if (info->listid == ap->playing_state.list_default.listid) {
+ printf("jumping into default list\n");
+ ap->playing_state.list_default = *info;
+ ap->playing_state.list_mode = LIST_DEFAULT;
+ } else {
+ printf("jumping to user list\n");
+ ap->playing_state.list_user = *info;
+ ap->playing_state.list_mode = LIST_USER;
+ }
audio_checkpoint_state(ap);
-
- //if (res != 0) {
- // audio_stop_pcm(ap);
- //}
+ printf("-> now listid=%4d seq=%4d fileid=%4d\n", info->listid, info->seq, info->fileid);
+fail:
+ dbscan_free(&scan);
+ dbindex_abort(tx);
return res;
-#endif
}
-int audio_prev_file(struct audio_player *ap) {
- return audio_advance_file(ap, dbscan_list_entry_prev);
-#if 0
- int res;
- int empty = ap->playing == NULL;
+// play a specific file right now out of band
+int audio_play_now(struct audio_player *ap, int fileid) {
+ int res = -1; // MDB_NOTFOUND
+ dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
+ dbfile *file = dbfile_get(tx, ap->index, fileid);
- audio_close_media(ap);
- do {
- //res = dbfile_prev(ap->index, &ap->playing, &ap->playing_path);
- res = dbfile_prev_shuffle(ap->index, &ap->playing, &ap->playing_path);
- if (res == 0)
- res = audio_init_media(ap, ap->playing_path);
- if (res == (-30798) && !empty) // && >loop?
- res = 1;
- } while (res != 0 && res !=(-30798)); // MDB_NOTFOUND
+ printf("player goto: %d diskid=%d\n", fileid, file->diskid);
- audio_checkpoint_state(ap);
+ if (file) {
+ // add to end of the playnow playlist
+ ap->playing_state.list_playnow.seq = 0;
+ ap->playing_state.list_playnow.fileid = fileid;
+ dblist_add_file(tx, ap->index, &ap->playing_state.list_playnow); // ignore result?
- //if (res != 0) {
- // audio_stop_pcm(ap);
- //}
+ res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file));
+ if (res == 0) {
+ ap->playing = file;
+
+ ap->playing_state.list_mode = LIST_PLAYNOW;
+ ap->playing_state.current = ap->playing_state.list_playnow;
+ printf("playing %s\n", file->path);
+ } else {
+ dbfile_free(file);
+ }
+ }
+
+ if (res == 0) {
+ dbindex_commit(tx);
+ // separate write transaction
+ // FIXME: put in same transaction?
+ audio_checkpoint_state(ap);
+ } else
+ dbindex_abort(tx);
return res;
-#endif
}
-int audio_goto_file(struct audio_player *ap, int fileid) {
+// jukebox
+// this adds the song to the jukebox playlist but doesn't jump immediately
+// however ... "next song" will jump over it, so ... sigh
+int audio_play_enqueue(struct audio_player *ap, int fileid) {
int res = -1; // MDB_NOTFOUND
- dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
+ dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
dbfile *file = dbfile_get(tx, ap->index, fileid);
- printf("player goto: %d diskid=%d\n", fileid, file->diskid);
+ printf("player enqueue: %d diskid=%d\n", fileid, file->diskid);
if (file) {
- dbfile_full_path(tx, ap->index, file);
-
- ap->playing = file;
-
- res = audio_init_media(ap, file->full_path);
- if (res == 0) {
- audio_checkpoint_state(ap);
+ dblistcursor info;
+
+ // add to end of the jukebox playlist
+ info.listid = ap->playing_state.list_jukebox.listid;
+ info.seq = 0;
+ info.fileid = fileid;
+ if ((res = dblist_add_file(tx, ap->index, &info)) == 0) {
+ printf("enqueued listid=%4d seq=%4d fileid=%4d\n",
+ info.listid,
+ info.seq,
+ info.fileid);
+
+ // If we're not already in jukebox mode, set the jukebox state to point
+ // to the previous file so that 'next file' will find the right one
+ // FIXME: what about 'prev' file?
+ // FIXME: find the actual seq in the db
+ // FIXME: this will drop the last song on restore, perhaps it should have a next_list_mode instead
+ // this would also fix the same problem for play-now
+
+ if (ap->playing_state.list_mode != LIST_JUKEBOX) {
+ printf(" set play state to jukebox\n");
+ ap->playing_state.list_jukebox.seq = info.seq -1;
+ ap->playing_state.list_jukebox.fileid = 0;
+ ap->playing_state.list_mode = LIST_JUKEBOX;
+ } else {
+ printf(" play state already jukebox\n");
+ }
}
}
- dbindex_abort(tx);
+
+ if (res == 0)
+ dbindex_commit(tx);
+ else
+ dbindex_abort(tx);
return res;
}
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) {
// 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;
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);