Added list validation code.
Added seq to list table.
Added preliminary tests.
dbmarshal.h: blobs.o $(EZE)/ez-blob-compiler
- $(EZE)/ez-blob-compiler -g header $< DBDISK_DESC DBFILE_DESC DBLIST_DESC > $@~
+ $(EZE)/ez-blob-compiler -g encode,decode,size -h $< DBDISK_DESC DBFILE_DESC DBLIST_DESC > $@~
mv $@~ $@
dbmarshal.c: blobs.o $(EZE)/ez-blob-compiler
- $(EZE)/ez-blob-compiler $< DBDISK_DESC DBFILE_DESC DBLIST_DESC > $@~
+ $(EZE)/ez-blob-compiler -g encode,decode,size $< DBDISK_DESC DBFILE_DESC DBLIST_DESC > $@~
mv $@~ $@
dbmarshal.o: dbmarshal.c dbmarshal.h
};
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, desc),
+ EZ_BLOB_START(dblist, 3, 4),
+ EZ_BLOB_INT32(dblist, 1, seq),
+ EZ_BLOB_INT32(dblist, 2, size),
+ EZ_BLOB_STRING(dblist, 3, name),
+ EZ_BLOB_STRING(dblist, 4, desc),
};
ez_blob_desc PLAY_SEEK_DESC[] = {
#include "ez-set.h"
#include "ez-blob.h"
#include "ez-blob-basic.h"
+#include "dbmarshal.h"
// prototype
void dblist_dump(dbtxn *txn, dbindex *db);
return NULL;
}
-dbdisk *dbdisk_get(dbtxn *tx, dbindex *db, int diskid) {
+dbdisk *dbdisk_get(dbtxn *tx, dbindex *db, dbid_t diskid) {
MDB_val key = { .mv_data = &diskid, .mv_size = sizeof(diskid) };
return primary_get_decode(tx, db, DBDISK_DESC, &key, db->disk);
ez_array_clear(array);
}
- printf("secondary list all: %s\n", mdb_strerror(res));
-
mdb_cursor_close(cursor);
return res;
}
ez_array_clear(array);
}
- printf("secondary list key: %s\n", mdb_strerror(res));
-
mdb_cursor_close(cursor);
return res;
}
return res;
}
-int dbdisk_del_id(dbtxn *tx, dbindex *db, int diskid) {
+int dbdisk_del_id(dbtxn *tx, dbindex *db, dbid_t diskid) {
dbdisk *d = dbdisk_get(tx, db, diskid);
if (d) {
return db->res;
}
-dbfile *dbfile_get_path(MDB_txn *tx, dbindex *db, int diskid, const char *path) {
+dbfile *dbfile_get_path(MDB_txn *tx, dbindex *db, dbid_t diskid, const char *path) {
char name[strlen(path) + 9];
MDB_val key;
}
}
-#include "dbmarshal.h"
-
-dbfile *dbfile_get(dbtxn *tx, dbindex *db, int fileid) {
+dbfile *dbfile_get(dbtxn *tx, dbindex *db, dbid_t fileid) {
MDB_val key = { .mv_data = &fileid, .mv_size = sizeof(fileid) };
#if 1
MDB_val data;
#endif
}
-int dbfile_del_id(dbtxn *tx, dbindex *db, int fileid) {
+int dbfile_del_id(dbtxn *tx, dbindex *db, dbid_t fileid) {
dbfile *f = dbfile_get(tx, db, fileid);
if (f) {
else
res = ENOMEM;
}
- printf("get list contents: res=%s\n", mdb_strerror(res));
mdb_cursor_close(cursor);
if (res != MDB_NOTFOUND)
goto fail2;
.seq = files[i].seq,
.fileid = f->id
};
- printf("delete file %d from list %d @ %d\n", fdata.fileid, files[i].listid, fdata.seq);
+ //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;
dblist *list = dblist_get(tx, db, hn->key);
if (list) {
- printf("update '%s' %d -> %d\n", list->name, list->size, list->size - hn->count);
+ //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 {
return 0;
}
-dblist *dblist_get(dbtxn *tx, dbindex *db, int id) {
- MDB_val key = { .mv_data = &id, .mv_size = sizeof(id) };
+dblist *dblist_get(dbtxn *tx, dbindex *db, dbid_t listid) {
+ MDB_val key = { .mv_data = &listid, .mv_size = sizeof(listid) };
return primary_get_decode(tx, db, DBLIST_DESC, &key, db->list);
}
return secondary_get_decode(tx, db, DBLIST_DESC, &key, db->list, db->list_by_name);
}
+void dblist_free(dblist *f) {
+ ez_blob_free(DBLIST_DESC, f);
+}
+
dbid_t dblistid_get_name(dbtxn *tx, dbindex *db, const char *name) {
MDB_val key = {
.mv_data = (void *)name,
return db->res == 0 ? *(dbid_t *)dat.mv_data : 0;
}
-void dblist_free(dblist *f) {
- ez_blob_free(DBLIST_DESC, f);
-}
// put ? add ? d->id == 0 -> then add, otherwise put?
int dblist_add(MDB_txn *txn, dbindex *db, dblist *list) {
return res;
}
-int dblist_clear(dbtxn *tx, dbindex *db, int listid) {
+int dblist_reset(dbtxn *tx, dbindex *db, dbid_t listid) {
MDB_val key, data;
int res;
return res;
}
-int dblist_del(dbtxn *txn, dbindex *db, int listid) {
+int dblist_del(dbtxn *txn, dbindex *db, dblist *list) {
+ dbid_t listid = list->id;
MDB_txn *tx;
MDB_val key, data;
MDB_cursor *cursor;
int res;
// TODO: deleting the reverse list can perform GET_BOTH_RANGE i think
+ // TODO: merge clearing with dblist_reset
mdb_txn_begin(db->env, txn, 0, &tx);
- dblist_dump(tx, db);
-
key.mv_data = &listid;
key.mv_size = sizeof(listid);
if ((res = mdb_del(tx, db->list, &key, NULL)))
goto fail;
- if ((res = mdb_del(tx, db->file_by_list, &key, NULL)))
+ res = mdb_del(tx, db->file_by_list, &key, NULL);
+ if (res != 0 && res != MDB_NOTFOUND)
goto fail;
- if ((res = dbstate_del_id(tx, db, listid)))
+ res = dbstate_del_id(tx, db, listid);
+ if (res != 0 && res != MDB_NOTFOUND)
goto fail;
res = mdb_cursor_open(tx, db->list_by_file, &cursor);
mdb_cursor_close(cursor);
- dblist_dump(tx, db);
+ // secondary keys
+
+ // -- by name
+ key.mv_data = list->name;
+ key.mv_size = strlen(list->name);
+ if ((res = mdb_del(tx, db->list_by_name, &key, NULL)) != 0)
+ goto fail;
+
return mdb_txn_commit(tx);
fail:
printf("db del list fail: %s\n", mdb_strerror(res));
mdb_txn_abort(tx);
- return -1;
+ return res;
+}
+
+int dblist_del_id(dbtxn *tx, dbindex *db, dbid_t listid) {
+ dblist *list = dblist_get(tx, db, listid);
+
+ if (list) {
+ db->res = dblist_del(tx, db, list);
+ dblist_free(list);
+ }
+
+ return db->res;
}
// info is in/out, in=listid, fileid, out=listid, seq, fileid
return db->res;
}
- struct dbfilelist fvalue = { .seq = list->size + 1, .fileid = info->fileid };
- struct dblistfile rvalue = { .listid = list->id, .seq = list->size + 1 };
+ struct dbfilelist fvalue = { .seq = list->seq + 1, .fileid = info->fileid };
+ struct dblistfile rvalue = { .listid = list->id, .seq = list->seq + 1 };
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", list->id, fvalue.seq, fvalue.fileid);
-
if ((res = mdb_put(tx, db->file_by_list, &key, &dat, MDB_NODUPDATA)))
goto fail;
dat.mv_data = &rvalue;
dat.mv_size = sizeof(rvalue);
- 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, &dat, MDB_NODUPDATA)))
goto fail;
- // update list record with changed size
- // TODO: can i just poke in the size value?
+ // update list record with changed size/sequence
list->size += 1;
- printf("update seq %d\n", list->size);
+ list->seq += 1;
res = dblist_put(tx, db, list, 0);
if (res == 0)
info->seq = fvalue.seq;
return res;
}
-// 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);
-
- 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;
- }
- dblist_free(list);
- return NULL;
-}
-
static void array_shuffle(dbid_t *ids, size_t count) {
for (size_t i=0;i<count;i++) {
size_t j = random() % (count-i);
// 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;
+ dblist *list = dblist_get_name(tx, db, name);
int res = 0;
- list = dblist_init(tx, db, name);
- if (!list)
- return db->res;
+ // if the list exists clear it, otherwise create it
+ if (list) {
+ res = dblist_reset(tx, db, list->id);
+ } else {
+ if ((list = calloc(1, sizeof(*list))) == NULL
+ || (list->name = strdup(name)) == NULL)
+ res = ENOMEM;
+ else
+ res = dblist_add(tx, db, list);
+ }
+
+ if (res != 0)
+ goto fail;
struct dbfilelist listvalue;
MDB_val listdata = { .mv_data= &listvalue, .mv_size = sizeof(listvalue) };
// update/fix list record
list->size = count;
+ list->seq = count;
res = dblist_put(tx, db, list, 0);
fail:
printf("dump all lists\n");
- printf("list_by_file =\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);
+ printf(" file=%5d list=%5d seq=%5d\n", fid, value->listid, value->seq);
res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
if (res == MDB_NOTFOUND)
}
mdb_cursor_close(cursor);
- printf("file_by_list =\n");
+ 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;
- printf(" list %d file %d seq %d\n", lid, value->fileid, value->seq);
+ printf(" list=%5d file=%5d seq=%5d\n", lid, value->fileid, value->seq);
res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_DUP);
if (res == MDB_NOTFOUND)
}
// only list + seq is required
-int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) {
+int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *curr) {
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 };
+ struct dbfilelist fvalue = { .seq = curr->seq, .fileid = curr->fileid };
+ struct dblistfile rvalue = { .listid = curr->listid, .seq = curr->seq };
- printf("list_del_file: lid=%4d seq=%4d fid=%4d\n", list->listid, list->seq, list->fileid);
+ //printf("list_del_file: lid=%4d seq=%4d fid=%4d\n", curr->listid, curr->seq, curr->fileid);
if ((res = mdb_txn_begin(db->env, txn, 0, &tx)))
goto fail0;
int delfile = 0;
int dellist = 0;
- if (list->seq == 0) {
+ if (curr->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 = &list->fileid;
- key.mv_size = sizeof(list->fileid);
+ key.mv_data = &curr->fileid;
+ key.mv_size = sizeof(curr->fileid);
data.mv_data = &rvalue;
data.mv_size = sizeof(rvalue);
if ((res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH_RANGE)))
goto fail;
- fvalue.seq = list->seq = ((struct dblistfile *)data.mv_data)->seq;
+ fvalue.seq = curr->seq = ((struct dblistfile *)data.mv_data)->seq;
- printf("list_del_file: found seq=%4d\n", list->seq);
+ //printf("list_del_file: found seq=%4d\n", curr->seq);
delcursor = 1;
delfile = 1;
- } else if (list->fileid == 0) {
+ } else if (curr->fileid == 0) {
// Lookup fileid for list[seq]
if ((res = mdb_cursor_open(tx, db->file_by_list, &cursor)))
goto fail;
- key.mv_data = &list->listid;
- key.mv_size = sizeof(list->listid);
+ key.mv_data = &curr->listid;
+ key.mv_size = sizeof(curr->listid);
data.mv_data = &fvalue;
data.mv_size = sizeof(fvalue);
if ((res = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH)))
goto fail;
- list->fileid = ((struct dbfilelist *)data.mv_data)->fileid;
+ curr->fileid = ((struct dbfilelist *)data.mv_data)->fileid;
- printf("list_del_file: found fid=%4d\n", list->fileid);
+ //printf("list_del_file: found fid=%4d\n", curr->fileid);
delcursor = 1;
dellist = 1;
goto fail;
if (delfile) {
- key.mv_data = &list->listid;
- key.mv_size = sizeof(list->listid);
+ key.mv_data = &curr->listid;
+ key.mv_size = sizeof(curr->listid);
data.mv_data = &fvalue;
data.mv_size = sizeof(fvalue);
}
if (dellist) {
- key.mv_data = &list->fileid;
- key.mv_size = sizeof(list->fileid);
+ key.mv_data = &curr->fileid;
+ key.mv_size = sizeof(curr->fileid);
data.mv_data = &rvalue;
data.mv_size = sizeof(rvalue);
goto fail;
}
+ // update list record with changed size/sequence
+ dblist *list = dblist_get(tx, db, curr->listid);
+ if (!list) {
+ res = dbindex_result(db);
+ goto fail;
+ }
+ list->size -= 1;
+ if ((res = dblist_put(tx, db, list, 0)) != 0)
+ goto fail;
+
if (delcursor)
mdb_cursor_close(cursor);
return 0;
fail:
- printf("fail: %s\n", mdb_strerror(res));
+ printf("list_del_file: %s\n", mdb_strerror(res));
if (delcursor)
mdb_cursor_close(cursor);
void dbindex_dump(dbindex *db) {
MDB_txn *tx;
+ printf("Raw Dump\n");
mdb_txn_begin(db->env, NULL, MDB_RDONLY, &tx);
// dump disks
{
MDB_cursor *cursor;
MDB_val key = { 0 }, data = { 0 };
- int r;
+ int res;
- printf("Known disks:\n");
- mdb_cursor_open(tx, db->disk, &cursor);
- r = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
- while (r == 0) {
+ res = mdb_cursor_open(tx, db->disk, &cursor);
+ res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+ if (res != 0)
+ printf(" disks: none\n");
+ else
+ printf(" disks:\n");
+ while (res == 0) {
dbdisk *p = ez_basic_decode(DBDISK_DESC, (ez_blob *)&data);
p->id = *(int *)key.mv_data;
- printf("id=%d\n", p->id);
- printf(" flags=%08x\n", p->flags);
- printf(" uuid=%s\n", p->uuid);
- printf(" label=%s\n", p->label);
- printf(" mount=%s\n", p->mount);
+ printf(" id=%4d flags=%08x uuid=%s label=%-30s mount=%s\n",
+ p->id, p->flags, p->uuid, p->label, p->mount);
ez_blob_free(DBDISK_DESC, p);
- r = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+ res= mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
}
mdb_cursor_close(cursor);
}
{
MDB_cursor *cursor;
MDB_val key = { 0 }, data = { 0 };
- int r;
+ int res;
- printf("Known filess:\n");
mdb_cursor_open(tx, db->file, &cursor);
- r = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
- while (r == 0) {
+ res = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+ if (res != 0)
+ printf(" files: none\n");
+ else
+ printf(" files:\n");
+ while (res == 0) {
dbfile *p = ez_basic_decode(DBFILE_DESC, (ez_blob *)&data);
p->id = *(int *)key.mv_data;
- printf("id=%d\n", p->id);
- printf(" diskid=%d\n", p->diskid);
- printf(" path=%s\n", p->path);
- printf(" title=%s\n", p->title);
- printf(" artist=%s\n", p->artist);
+ printf(" id=%4d diskid=%4d path=%-30s title=%-30s artist=%s\n",
+ p->id, p->diskid, p->path, p->title, p->artist);
ez_blob_free(DBFILE_DESC, p);
- r = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+ res = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
}
mdb_cursor_close(cursor);
}
+ mdb_txn_abort(tx);
}
/* new dbscan version for for loops*/
return *(const int32_t *)ap - *(const int32_t *)bp;
}
-
/* protoyping */
static int check_path(const dbfile *file, const MDB_val *key) {
check_dir
};
- for (int i=0;i<4;i++) {
+ for (int i=0;i<5;i++) {
size_t count = 0;
//printf("table %d\n", i);
mdb_cursor_open(tx, tables[i], &cursor);
return ok;
}
+
+int dbindex_validate_lists(dbindex *db);
+int dbindex_validate_lists(dbindex *db) {
+ MDB_txn *tx;
+ MDB_val key, data;
+ MDB_cursor *cursor;
+ int res;
+
+ ez_set list_counts = EZ_INIT_SET(counts, hist_hash, hist_equals, free);
+ ez_array lidsa = EZ_INIT_ARRAY(lidsa);
+ dbid_t *lids;
+ size_t lids_size;
+
+ mdb_txn_begin(db->env, NULL, MDB_RDONLY, &tx);
+
+ // All listids
+ if ((res = mdb_cursor_open(tx, db->list, &cursor)) != 0)
+ goto fail1;
+ for (int next = MDB_FIRST; (res = mdb_cursor_get(cursor, &key, &data, next)) == 0; next = MDB_NEXT)
+ ez_array_add(&lidsa, key.mv_data, key.mv_size);
+ mdb_cursor_close(cursor);
+ if (res != MDB_NOTFOUND)
+ goto fail1;
+ res = 0;
+
+ lids = lidsa.ea_data;
+ lids_size = lidsa.ea_size / sizeof(*lids);
+
+ for (int i=0;i<lids_size;i++) {
+ struct hist_node *hn = malloc(sizeof(*hn));
+
+ hn->key = lids[i];
+ hn->count = 0;
+ ez_set_put(&list_counts, hn);
+ }
+
+ MDB_cursor *fcursor;
+ MDB_cursor *flcursor;
+ MDB_cursor *lfcursor;
+
+ if ((res = mdb_cursor_open(tx, db->file, &fcursor)) != 0)
+ goto fail1;
+ if ((res = mdb_cursor_open(tx, db->file_by_list, &flcursor)) != 0)
+ goto fail2;
+ if ((res = mdb_cursor_open(tx, db->list_by_file, &lfcursor)) != 0)
+ goto fail3;
+
+ // Check file_by_list and inverse
+ for (int op = MDB_FIRST; (res = mdb_cursor_get(flcursor, &key, &data, op)) == 0; op = MDB_NEXT) {
+ struct dbfilelist fvalue;
+ dbid_t lid;
+
+ assert(key.mv_size == sizeof(dbid_t));
+ assert(data.mv_size == sizeof(fvalue));
+
+ lid = *((dbid_t *)key.mv_data);
+ memcpy(&fvalue, data.mv_data, sizeof(fvalue));
+
+ printf(" lid=%4d -> seq=%5d fid=%5d\n", lid, fvalue.seq, fvalue.fileid);
+
+ // TODO: check seq is unique (enforced by db i guess)
+
+ struct hist_node hk = { .key = lid };
+ struct hist_node *hn = ez_set_get(&list_counts, &hk);
+ if (!hn) {
+ printf("file_by_list: listid foreign key fail\n");
+ goto fail;
+ }
+ hn->count += 1;
+
+ // check file exists
+ key.mv_data = &fvalue.fileid;
+ key.mv_size = sizeof(dbid_t);
+ res = mdb_cursor_get(fcursor, &key, &data, MDB_SET_KEY);
+ if (res != 0) {
+ printf("file_by_list: fileid missing\n");
+ goto fail;
+ }
+
+ // check inverse
+ struct dblistfile lvalue = { .listid = lid, .seq = fvalue.seq };
+
+ key.mv_data = &fvalue.fileid;
+ key.mv_size = sizeof(fvalue.fileid);
+ data.mv_data = &lvalue;
+ data.mv_size = sizeof(lvalue);
+
+ res = mdb_cursor_get(lfcursor, &key, &data, MDB_GET_BOTH);
+ if (res != 0) {
+ printf("file_by_list: list_by_file mismatch\n");
+ goto fail;
+ }
+ }
+ if (res != MDB_NOTFOUND)
+ goto fail;
+ res = 0;
+
+ // Check list_by_file and inverse
+ for (int op = MDB_FIRST; (res = mdb_cursor_get(lfcursor, &key, &data, op)) == 0; op = MDB_NEXT) {
+ struct dblistfile lvalue;
+ dbid_t fid;
+
+ assert(key.mv_size == sizeof(dbid_t));
+ assert(data.mv_size == sizeof(lvalue));
+
+ fid = *((dbid_t *)key.mv_data);
+ memcpy(&lvalue, data.mv_data, sizeof(lvalue));
+
+ printf(" fid=%4d -> seq=%5d lid=%5d\n", fid, lvalue.seq, lvalue.listid);
+
+ struct hist_node hk = { .key = lvalue.listid };
+ struct hist_node *hn = ez_set_get(&list_counts, &hk);
+ if (!hn) {
+ printf("list_by_file: listid foreign key fail\n");
+ goto fail;
+ }
+ hn->count += 1;
+
+ // check file exists
+ key.mv_data = &fid;
+ key.mv_size = sizeof(dbid_t);
+ res = mdb_cursor_get(fcursor, &key, &data, MDB_SET_KEY);
+ if (res != 0) {
+ printf("file_by_list: fileid missing\n");
+ goto fail;
+ }
+
+ // check inverse
+ struct dbfilelist fvalue = { .seq = lvalue.seq /* .fileid ignored */ };
+
+ key.mv_data = &lvalue.listid;
+ key.mv_size = sizeof(lvalue.listid);
+ data.mv_data = &fvalue;
+ data.mv_size = sizeof(fvalue);
+
+ res = mdb_cursor_get(flcursor, &key, &data, MDB_GET_BOTH);
+ if (res != 0) {
+ printf("list_by_file: file_by_list mismatch, missing seq: %s\n", mdb_strerror(res));
+ goto fail;
+ }
+
+ fvalue = *((struct dbfilelist *)data.mv_data);
+ if (fid != fvalue.fileid) {
+ res = MDB_NOTFOUND;
+ printf("list_by_file: file_by_list mismatch, fileid\n");
+ goto fail;
+ }
+ }
+ if (res != MDB_NOTFOUND)
+ goto fail1;
+ res = 0;
+
+ // Check list sizes match
+ for (int i=0;i<lids_size;i++) {
+ dblist *list = dblist_get(tx, db, lids[i]);
+ struct hist_node hk = { .key = lids[i] };
+ struct hist_node *hn = ez_set_get(&list_counts, &hk);
+
+ if (hn->count != list->size * 2) {
+ printf("List size mismatch\n");
+ dblist_free(list);
+ goto fail;
+ }
+ dblist_free(list);
+ }
+
+fail:
+ mdb_cursor_close(fcursor);
+fail3:
+ mdb_cursor_close(lfcursor);
+fail2:
+ mdb_cursor_close(flcursor);
+fail1:
+ ez_set_clear(&list_counts);
+ ez_array_clear(&lidsa);
+
+ mdb_txn_abort(tx);
+
+ return res;
+}
struct dblist {
dbid_t id;
- int size;
+ uint32_t seq;
+ uint32_t size;
char *name;
char *desc;
};
//int dbstate_get_list(dbtxn *tx, dbindex *db, dbid_t listid, dblistcursor *c);
//int dbstate_put_list(dbtxn *tx, dbindex *db, dbid_t listid, dblistcursor *c);
-dbdisk *dbdisk_get(dbtxn *tx, dbindex *db, int diskid);
+dbdisk *dbdisk_get(dbtxn *tx, dbindex *db, dbid_t diskid);
dbdisk *dbdisk_get_uuid(dbtxn *tx, dbindex *db, const char *uuid);
void dbdisk_free(dbdisk *d);
-int dbdisk_add(dbtxn *txn, dbindex *db, dbdisk *d);
+int dbdisk_add(dbtxn *txn, dbindex *db, dbdisk *d);
int dbdisk_del(dbtxn *tx, dbindex *db, dbdisk *disk);
-int dbdisk_del_id(dbtxn *tx, dbindex *db, int diskid);
+int dbdisk_del_id(dbtxn *tx, dbindex *db, dbid_t diskid);
int dbdisk_mounted(dbdisk *disk);
-dbfile *dbfile_get(dbtxn *tx, dbindex *db, int fileid);
-dbfile *dbfile_get_path(dbtxn *tx, dbindex *db, int diskid, const char *path);
+dbfile *dbfile_get(dbtxn *tx, dbindex *db, dbid_t fileid);
+dbfile *dbfile_get_path(dbtxn *tx, dbindex *db, dbid_t diskid, const char *path);
void dbfile_free(dbfile *f);
char *dbfile_full_path(dbtxn *tx, dbindex *db, dbfile *file);
-int dbfile_del_id(dbtxn *tx, dbindex *db, int fileid);
+int dbfile_del_id(dbtxn *tx, dbindex *db, dbid_t fileid);
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);
int dbfile_next(dbindex *db, dbfile **f, char **fpath);
int dbfile_prev(dbindex *db, dbfile **f, char **fpath);
-dblist *dblist_get(dbtxn *tx, dbindex *db, int id);
+dblist *dblist_get(dbtxn *tx, dbindex *db, dbid_t listid);
dblist *dblist_get_name(dbtxn *tx, dbindex *db, const char *name);
void dblist_free(dblist *f);
-dblist *dblist_init(dbtxn *tx, dbindex *db, const char *name);
-
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);
+int dblist_reset(dbtxn *tx, dbindex *db, dbid_t listid);
+
+int dblist_del(dbtxn *txn, dbindex *db, dblist *list);
+int dblist_del_id(dbtxn *txn, dbindex *db, dbid_t listid);
int dblist_add_file(MDB_txn *tx, dbindex *db, dblistcursor *info);
int dblist_del_file(MDB_txn *txn, dbindex *db, dblistcursor *list);
// delete all lists and run update_all
int dblist_reset_all(dbtxn *tx, dbindex *db);
-
int dbfile_search(dbindex *db, const char *pattern, dbfile **results, int maxlen);
// prototyping
-int dbfile_put_suffix(dbtxn *tx, dbindex *db, const char *suffix, uint32_t fileid);
+int dbfile_put_suffix(dbtxn *tx, dbindex *db, const char *suffix, dbid_t fileid);
size_t dbfile_search_substring(dbtxn *tx, dbindex *db, const char *sub);
int dbfile_clear_suffix(MDB_txn *tx, dbindex *db);
int dbindex_validate(dbindex *db);
dbtxn *tx = dbindex_begin(db, NULL, 0);
if (!tx)
goto fail0;
- dblist *list = dblist_get(tx, db, lid);
- if (!list)
- goto fail1;
+
if (dblist_add_file(tx, db, &info))
goto fail1;
if (dbindex_commit(tx))
- goto fail2;
+ goto fail1;
char location[64];
- sprintf(location, "/x/list/%u/%u", list->id, list->size - 1);
+ sprintf(location, "/x/list/%u/%u", lid, info.seq);
http_addheader(&rep->http, "Location", location);
httpresponse_set_response(rep, 201, "Created");
- dblist_free(list);
return 0;
- fail2:
- dblist_free(list);
-
fail1:
dbindex_abort(tx);
fail0:
#include "dbindex.h"
void dbshuffle_init2(dbindex *db);
-int dbdisk_del_id(dbtxn *txn, dbindex *db, int diskid);
-
-int dblist_del_file(dbtxn *txn, dbindex *db, struct dblistcursor *list);
/* ********************************************************************** */
dbscan scan;
for (dblist *list = dbscan_list(tx, &scan, db, 0); list; list = dbscan_list_next(&scan)) {
- printf("lid=%4d size=%5d name=%-20s (%s)\n", list->id, list->size, list->name, list->desc);
+ printf("lid=%4d seq=%5d size=%5d name=%-20s (%s)\n", list->id, list->seq, list->size, list->name, list->desc);
dblist_free(list);
}
dbscan_free(&scan);
dblist_add(NULL, db, &list);
} else if (strcmp(cmd, "list-del") == 0) {
- dblist_del(NULL, db, info.listid);
+ dblist_del_id(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)
+ if ((res = dblist_reset(tx, db, info.listid)) == 0)
dbindex_commit(tx);
else
dbindex_abort(tx);
state->curr.fileid = 0;
state->curr.seq = 0;
// Initialise sequence to end of list for playnow and jukebox
+ // TODO: move this to dbindex to maintain?
if (i == ZPL_PLAYNOW || i == ZPL_JUKEBOX) {
dblist *list = dblist_get(tx, ap->index, state->curr.listid);
if (list) {
- state->curr.seq = list->size + 1;
+ state->curr.seq = list->seq + 1;
dblist_free(list);
}
}
--- /dev/null
+/* run some tests against dbindex */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+
+#include <libavformat/avformat.h>
+
+#include <regex.h>
+
+#define obstack_chunk_alloc malloc
+#define obstack_chunk_free free
+#include <obstack.h>
+
+#include "dbindex.c"
+
+void dbindex_dump(dbindex *db);
+void dump_lists(dbindex *db, dbtxn *txn);
+int dbindex_validate_lists(dbindex *db);
+
+static dbindex *db;
+static dbtxn *tx;
+static char dbpath[] = "/tmp/dbtestXXXXXX";
+static const char *this_test = NULL;
+
+#define check_ret(test) do { if (!(test)) { fprintf(stderr, "fail: %s: " #test "\n", this_test); return 0; } } while(0)
+#define check(test) (test) ? (1) : (fprintf(stderr, "fail: %s: " #test "\n", this_test), 0)
+
+static int check_disk_equals(const dbdisk *a, const dbdisk *b) {
+ check_ret(a != NULL && b != NULL);
+ check_ret(a->id == b->id);
+ check_ret(a->uuid != NULL && b->uuid != NULL);
+ check_ret(strcmp(a->uuid, b->uuid) == 0);
+ check_ret(a->mount != NULL && b->mount != NULL);
+ check_ret(strcmp(a->mount, b->mount) == 0);
+ return 1;
+}
+
+static int check_file_equals(const dbfile *a, const dbfile *b) {
+ check_ret(a != NULL && b != NULL);
+ check_ret(a->id == b->id);
+ check_ret(a->diskid == b->diskid);
+ check_ret(a->size == b->size);
+ check_ret(a->mtime == b->mtime);
+ check_ret(a->duration == b->duration);
+ check_ret(a->path != NULL && b->path != NULL);
+ check_ret(strcmp(a->path, b->path) == 0);
+
+ check_ret((a->title == NULL && b->title == NULL)
+ || (a->title != NULL && b->title != NULL && strcmp(a->title, b->title) == 0));
+ check_ret((a->artist == NULL && b->artist == NULL)
+ || (a->artist != NULL && b->artist != NULL && strcmp(a->artist, b->artist) == 0));
+ return 1;
+}
+
+static int check_list_equals(const dblist *a, const dblist *b) {
+ check_ret(a != NULL && b != NULL);
+ check_ret(a->id == b->id);
+ check_ret(a->name != NULL && b->name != NULL);
+ check_ret(strcmp(a->name, b->name) == 0);
+
+ check_ret((a->desc != NULL && b->desc != NULL) || (a->desc == NULL && b->desc == NULL));
+ if (a->desc)
+ check_ret(strcmp(a->desc, b->desc) == 0);
+ return 1;
+}
+
+static int lookup_string_secondary(MDB_txn *tx, MDB_dbi dbi, char *keyval, dbid_t datval) {
+ MDB_val key = { .mv_data = keyval, .mv_size = strlen(keyval) };
+ MDB_val dat = { .mv_data = &datval, .mv_size = sizeof(datval) };
+ int res;
+ MDB_cursor *cursor;
+
+ res = mdb_cursor_open(tx, dbi, &cursor);
+ res = mdb_cursor_get(cursor, &key, &dat, MDB_GET_BOTH);
+ mdb_cursor_close(cursor);
+
+ return res;
+}
+
+static int lookup_dbid_secondary(MDB_txn *tx, MDB_dbi dbi, dbid_t keyval, dbid_t datval) {
+ MDB_val key = { .mv_data = &keyval, .mv_size = sizeof(keyval) };
+ MDB_val dat = { .mv_data = &datval, .mv_size = sizeof(datval) };
+ int res;
+ MDB_cursor *cursor;
+
+ res = mdb_cursor_open(tx, dbi, &cursor);
+ res = mdb_cursor_get(cursor, &key, &dat, MDB_GET_BOTH);
+ mdb_cursor_close(cursor);
+
+ return res;
+}
+
+/* ********************************************************************** */
+
+static int test_add_disk(void) {
+ dbdisk disk = {
+ .flags = 0,
+ .uuid = "disk-uuid-0",
+ .label = "disk-label",
+ .mount = "/some/path"
+ };
+ int ok;
+ int res;
+
+ this_test = __FUNCTION__;
+
+ ok = (res = dbdisk_add(tx, db, &disk)) == 0;
+ if (ok) {
+ dbdisk *diska = dbdisk_get(tx, db, disk.id);
+ dbdisk *diskb = dbdisk_get_uuid(tx, db, disk.uuid);
+
+ ok &= check_disk_equals(diska, &disk);
+ ok &= check_disk_equals(diskb, &disk);
+
+ dbdisk_free(diska);
+ dbdisk_free(diskb);
+ }
+
+ return ok;
+}
+
+static int test_del_disk(void) {
+ dbdisk disk = {
+ .flags = 0,
+ .uuid = "disk-uuid-0",
+ .label = "disk-label",
+ .mount = "/some/path"
+ };
+ int ok;
+ int res;
+
+ this_test = __FUNCTION__;
+
+ ok = (res = dbdisk_add(tx, db, &disk)) == 0;
+ if (ok) {
+ res = dbdisk_del(tx, db, &disk);
+
+ check(res == 0);
+
+ dbdisk *diska = dbdisk_get(tx, db, disk.id);
+ dbdisk *diskb = dbdisk_get_uuid(tx, db, disk.uuid);
+
+ check(diska == NULL);
+ check(diskb == NULL);
+
+ dbdisk_free(diska);
+ dbdisk_free(diskb);
+ }
+
+ return ok;
+}
+
+/* ********************************************************************** */
+
+static int test_add_file(void) {
+ dbdisk disk = {
+ .flags = 0,
+ .uuid = "disk-uuid-0",
+ .label = "disk-label",
+ .mount = "/some/path"
+ };
+
+ dbfile file = {
+ .diskid = 200,
+ .size = 10,
+ .mtime = 12,
+ .duration = 100,
+ .path = "/files/1",
+ .title = "music 1",
+ .artist = "artist 1"
+ };
+ int ok;
+
+ this_test = __FUNCTION__;
+
+ ok = check(dbdisk_add(tx, db, &disk) == 0);
+
+ // basic, no disk id
+ ok &= check(dbfile_add(tx, db, &file) == MDB_NOTFOUND);
+
+ file.diskid = disk.id;
+ ok &= check(dbfile_add(tx, db, &file) == 0);
+
+ // check keys
+ dbfile *filea = dbfile_get(tx, db, file.id);
+ dbfile *fileb = dbfile_get_path(tx, db, disk.id, file.path);
+
+ ok &= check_file_equals(&file, filea);
+ ok &= check_file_equals(&file, fileb);
+
+ dbfile_free(filea);
+ dbfile_free(fileb);
+
+ char path[strlen(file.path) + 8 + 1];
+ sprintf(path, "%08x%s", disk.id, file.path);
+
+ *strrchr(path, '/') = 0;
+
+ ok &= check(lookup_dbid_secondary(tx, db->file_by_disk, file.diskid, file.id) == 0);
+ ok &= check(lookup_string_secondary(tx, db->file_by_dir, path, file.id) == 0);
+ ok &= check(lookup_string_secondary(tx, db->file_by_title, file.title, file.id) == 0);
+ ok &= check(lookup_string_secondary(tx, db->file_by_artist, file.artist, file.id) == 0);
+
+ return ok;
+}
+
+static int test_del_file(void) {
+ dbdisk disk = {
+ .flags = 0,
+ .uuid = "disk-uuid-0",
+ .label = "disk-label",
+ .mount = "/some/path"
+ };
+
+ dbfile file = {
+ .diskid = 200,
+ .size = 10,
+ .mtime = 12,
+ .duration = 100,
+ .path = "/files/1",
+ .title = "music 1",
+ .artist = "artist 1"
+ };
+ int ok;
+
+ this_test = __FUNCTION__;
+
+ ok = check(dbdisk_add(tx, db, &disk) == 0);
+ file.diskid = disk.id;
+ ok &= check(dbfile_add(tx, db, &file) == 0);
+
+ ok &= dbfile_del(tx, db, &file) == 0;
+
+ // check keys
+ dbfile *filea = dbfile_get(tx, db, file.id);
+ dbfile *fileb = dbfile_get_path(tx, db, disk.id, file.path);
+
+ ok &= check(filea == NULL);
+ ok &= check(fileb == NULL);
+
+ dbfile_free(filea);
+ dbfile_free(fileb);
+
+ char path[strlen(file.path) + 8 + 1];
+ sprintf(path, "%08x%s", disk.id, file.path);
+
+ *strrchr(path, '/') = 0;
+
+ ok &= check(lookup_dbid_secondary(tx, db->file_by_disk, file.diskid, file.id) == MDB_NOTFOUND);
+ ok &= check(lookup_string_secondary(tx, db->file_by_dir, path, file.id) == MDB_NOTFOUND);
+ ok &= check(lookup_string_secondary(tx, db->file_by_title, file.title, file.id) == MDB_NOTFOUND);
+ ok &= check(lookup_string_secondary(tx, db->file_by_artist, file.artist, file.id) == MDB_NOTFOUND);
+
+ // TODO: playlists
+
+ return ok;
+}
+
+/* ********************************************************************** */
+
+static int test_add_files(void) {
+ dbdisk disk0 = {
+ .flags = 0,
+ .uuid = "disk-uuid-0",
+ .label = "disk-label",
+ .mount = "/some/path"
+ };
+ dbdisk disk1 = {
+ .flags = 0,
+ .uuid = "disk-uuid-1",
+ .label = "disk-label",
+ .mount = "/other/path"
+ };
+
+ char path[256];
+ char title[256];
+ char artist[256];
+ dbfile file = {
+ .diskid = 200,
+ .size = 10,
+ .mtime = 12,
+ .duration = 100,
+ .path = path,
+ .title = title,
+ .artist = artist
+ };
+ int ok, res;
+ int ntotal = 0;
+ this_test = __FUNCTION__;
+
+ ok = check(dbdisk_add(tx, db, &disk0) == 0);
+ ok &= check(dbdisk_add(tx, db, &disk1) == 0);
+
+ file.diskid = disk0.id;
+ for (int i=0;i<100;i++) {
+ sprintf(title, "title %d", i % 10);
+ sprintf(artist, "artist %d", i / 9);
+ sprintf(path, "/dir%d/file%d", i % 7, i);
+ ok &= check((res = dbfile_add(tx, db, &file)) == 0);
+ ntotal++;
+ }
+
+ file.diskid = disk1.id;
+ for (int i=0;i<100;i++) {
+ sprintf(title, "title %d", i % 10);
+ sprintf(artist, "artist %d", i / 9);
+ sprintf(path, "/dir%d/file%d", i % 7, i);
+ ok &= check((res = dbfile_add(tx, db, &file)) == 0);
+ ntotal++;
+ }
+
+ // validate will check the secondary indices
+
+ return ok;
+}
+
+static int test_del_files(void) {
+ int ok = 1, res;
+
+ test_add_files();
+
+ // delete some of the files
+ ez_array array = EZ_INIT_ARRAY(array);
+
+ secondary_list_all(tx, db->file_by_disk, &array);
+
+ size_t count = array.ea_size / sizeof(dbid_t);
+ dbid_t *fids = array.ea_data;
+
+ for (int i=0;i<count;i+=4) {
+ ok &= (res = dbfile_del_id(tx, db, fids[i])) == 0;
+ }
+ // validate will check the secondary indices
+
+ ez_array_clear(&array);
+
+ return ok;
+}
+
+/* ********************************************************************** */
+
+static int test_add_list1(void) {
+ dblist list = {
+ .name = "list",
+ .desc = "list desc"
+ };
+ int ok;
+ int res;
+
+ this_test = __FUNCTION__;
+
+ ok = (res = dblist_add(tx, db, &list)) == 0;
+ if (ok) {
+ dblist *lista = dblist_get(tx, db, list.id);
+ dblist *listb = dblist_get_name(tx, db, list.name);
+
+ ok &= check_list_equals(lista, &list);
+ ok &= check_list_equals(listb, &list);
+
+ dblist_free(lista);
+ dblist_free(listb);
+ }
+
+ return ok;
+}
+
+static int test_add_list2(void) {
+ dblist list = {
+ .name = "list",
+ .desc = NULL
+ };
+ int ok;
+ int res;
+
+ this_test = __FUNCTION__;
+
+ ok = (res = dblist_add(tx, db, &list)) == 0;
+ if (ok) {
+ dblist *lista = dblist_get(tx, db, list.id);
+ dblist *listb = dblist_get_name(tx, db, list.name);
+
+ ok &= check_list_equals(lista, &list);
+ ok &= check_list_equals(listb, &list);
+
+ dblist_free(lista);
+ dblist_free(listb);
+ }
+
+ return ok;
+}
+
+/* ********************************************************************** */
+
+static int test_del_list1(void) {
+ dblist list = {
+ .name = "list",
+ };
+ int ok;
+ int res;
+
+ this_test = __FUNCTION__;
+
+ ok = (res = dblist_add(tx, db, &list)) == 0;
+ if (ok) {
+ check_ret(dblist_del(tx, db, &list) == 0);
+ check_ret(dblist_del_id(tx, db, list.id) == MDB_NOTFOUND);
+ check_ret(dblist_del(tx, db, &list) == MDB_NOTFOUND);
+
+ dblist *lista = dblist_get(tx, db, list.id);
+ dblist *listb = dblist_get_name(tx, db, list.name);
+
+ check(lista == NULL);
+ check(listb == NULL);
+
+ dblist_free(lista);
+ dblist_free(listb);
+ }
+
+ return ok;
+}
+
+static int test_del_list2(void) {
+ dblist list = {
+ .name = "list",
+ };
+ int ok;
+ int res;
+
+ this_test = __FUNCTION__;
+
+ ok = (res = dblist_add(tx, db, &list)) == 0;
+ if (ok) {
+ check_ret(dblist_del_id(tx, db, list.id) == 0);
+ check_ret(dblist_del_id(tx, db, list.id) == MDB_NOTFOUND);
+ check_ret(dblist_del(tx, db, &list) == MDB_NOTFOUND);
+
+ dblist *lista = dblist_get(tx, db, list.id);
+ dblist *listb = dblist_get_name(tx, db, list.name);
+
+ check(lista == NULL);
+ check(listb == NULL);
+
+ dblist_free(lista);
+ dblist_free(listb);
+ }
+
+ return ok;
+}
+
+/* ********************************************************************** */
+
+#define MIN(a, b) ((a)<(b)?(a):(b))
+
+static int test_add_lists(void) {
+ dblist list = {
+ .name = "list",
+ .desc = "list desc"
+ };
+
+ if (!test_add_files()) {
+ this_test = __FUNCTION__;
+ return 0;
+ }
+
+ this_test = __FUNCTION__;
+
+ check_ret(dblist_add(tx, db, &list) == 0);
+
+ ez_array files = EZ_INIT_ARRAY(files);
+ secondary_list_all(tx, db->file_by_disk, &files);
+
+ size_t count = files.ea_size / sizeof(dbid_t);
+ dbid_t *fids = files.ea_data;
+
+ // check adding non-existant file
+ {
+ dblistcursor curr = { .listid = list.id, .fileid = fids[count-1] + 1 };
+
+ check_ret(dblist_add_file(tx, db, &curr) == MDB_NOTFOUND);
+ }
+
+ for (int i=0,seq=1;i<count;i+=3,seq+=1) {
+ dbid_t fid = fids[i];
+ dblist *lista;
+ dblistcursor curr = { .listid = list.id, .fileid = fid };
+
+ check_ret(dblist_add_file(tx, db, &curr) == 0);
+ check_ret(curr.seq = seq);
+
+ lista = dblist_get(tx, db, list.id);
+ check_ret(lista->size == seq);
+ check_ret(lista->seq == seq);
+ dblist_free(lista);
+ }
+
+ ez_array_clear(&files);
+
+ return 1;
+}
+
+static int test_del_lists_all(void) {
+ if (!test_add_lists())
+ return 0;
+
+ this_test = __FUNCTION__;
+
+ dblist *list = dblist_get_name(tx, db, "list");
+
+ ez_array files = EZ_INIT_ARRAY(files);
+ secondary_list_all(tx, db->file_by_disk, &files);
+
+ size_t count = files.ea_size / sizeof(dbid_t);
+ dbid_t *fids = files.ea_data;
+
+ // check deleting non-existant seq
+ {
+ dblistcursor curr = { .listid = list->id, .seq = 1000 };
+
+ check_ret(dblist_del_file(tx, db, &curr) == MDB_NOTFOUND);
+ }
+
+ for (int i=0,seq=1;i<count;i+=3,seq+=1) {
+ dbid_t fid = fids[i];
+ dblist *lista;
+ dblistcursor curr = { .listid = list->id, .seq = seq };
+
+ check_ret(dblist_del_file(tx, db, &curr) == 0);
+ check_ret(curr.fileid == fid);
+
+ lista = dblist_get(tx, db, list->id);
+ check_ret(lista->size == list->size - seq);
+ dblist_free(lista);
+ }
+
+ ez_array_clear(&files);
+
+ return 1;
+}
+
+static int test_del_lists_some(void) {
+ if (!test_add_lists())
+ return 0;
+
+ this_test = __FUNCTION__;
+
+ dblist *list = dblist_get_name(tx, db, "list");
+
+ ez_array files = EZ_INIT_ARRAY(files);
+ secondary_list_all(tx, db->file_by_disk, &files);
+
+ size_t count = files.ea_size / sizeof(dbid_t);
+ dbid_t *fids = files.ea_data;
+
+ // check deleting non-existant seq
+ {
+ dblistcursor curr = { .listid = list->id, .seq = 1000 };
+
+ check_ret(dblist_del_file(tx, db, &curr) == MDB_NOTFOUND);
+ }
+
+ for (int i=0,seq=1;i<count;i+=6,seq+=2) {
+ dbid_t fid = fids[i];
+ dblist *lista;
+ dblistcursor curr = { .listid = list->id, .seq = seq };
+
+ check_ret(dblist_del_file(tx, db, &curr) == 0);
+ check_ret(curr.fileid == fid);
+
+ lista = dblist_get(tx, db, list->id);
+ check_ret(lista->size == list->size - (seq+1)/2);
+ dblist_free(lista);
+ }
+
+ ez_array_clear(&files);
+
+ return 1;
+}
+
+/* ********************************************************************** */
+
+static int test_add_lists_dup(void) {
+ dblist list = {
+ .name = "list-dup",
+ .desc = "list with duplicates"
+ };
+
+ if (!test_add_files())
+ return 0;
+
+ this_test = __FUNCTION__;
+
+ check_ret(dblist_add(tx, db, &list) == 0);
+
+ ez_array files = EZ_INIT_ARRAY(files);
+ secondary_list_all(tx, db->file_by_disk, &files);
+
+ size_t count = files.ea_size / sizeof(dbid_t);
+ dbid_t *fids = files.ea_data;
+
+ for (int i=0,seq=1;i<count/2;i+=3,seq+=1) {
+ dbid_t fid = fids[i];
+ dblist *lista;
+ dblistcursor curr = { .listid = list.id, .fileid = fid };
+
+ check_ret(dblist_add_file(tx, db, &curr) == 0);
+ check_ret(curr.seq = seq * 2 - 1);
+
+ lista = dblist_get(tx, db, list.id);
+ check_ret(lista->size == seq * 2 - 1);
+ check_ret(lista->seq == seq * 2 - 1);
+ dblist_free(lista);
+
+ check_ret(dblist_add_file(tx, db, &curr) == 0);
+ check_ret(curr.seq = seq * 2);
+
+ lista = dblist_get(tx, db, list.id);
+ check_ret(lista->size == seq * 2);
+ check_ret(lista->seq == seq * 2);
+ dblist_free(lista);
+ }
+
+ ez_array_clear(&files);
+
+ return 1;
+}
+
+static int test_del_lists_dup(void) {
+ if (!test_add_lists_dup())
+ return 0;
+
+ this_test = __FUNCTION__;
+
+ dblist *list = dblist_get_name(tx, db, "list-dup");
+
+ ez_array files = EZ_INIT_ARRAY(files);
+ secondary_list_all(tx, db->file_by_disk, &files);
+
+ size_t count = files.ea_size / sizeof(dbid_t);
+ dbid_t *fids = files.ea_data;
+
+ for (int i=0,seq=1;i<count/2;i+=3,seq+=1) {
+ dbid_t fid = fids[i];
+ dblist *lista;
+ dblistcursor curr = { .listid = list->id, .fileid = fid };
+
+ check_ret(dblist_del_file(tx, db, &curr) == 0);
+ check_ret(curr.seq == seq * 2 - 1);
+
+ lista = dblist_get(tx, db, list->id);
+ check_ret(lista->size == list->size - seq * 2 + 1);
+ dblist_free(lista);
+
+ curr.seq = 0;
+ check_ret(dblist_del_file(tx, db, &curr) == 0);
+ check_ret(curr.seq == seq * 2);
+
+ lista = dblist_get(tx, db, list->id);
+ check_ret(lista->size == list->size - seq * 2);
+ dblist_free(lista);
+
+ }
+
+ ez_array_clear(&files);
+
+ return 1;
+}
+
+
+/* ********************************************************************** */
+
+static int test_del_list_files(void) {
+ if (!test_add_lists())
+ return 0;
+
+ this_test = __FUNCTION__;
+
+ dblist *list = dblist_get_name(tx, db, "list");
+
+ ez_array files = EZ_INIT_ARRAY(files);
+ secondary_list_all(tx, db->file_by_disk, &files);
+
+ size_t count = files.ea_size / sizeof(dbid_t);
+ dbid_t *fids = files.ea_data;
+
+ for (int i=0,seq=1;i<count/2;i+=1,seq++) {
+ dbid_t fid = fids[i];
+
+ dbfile_del_id(tx, db, fid);
+
+ dblistcursor curr = { .listid = list->id, .fileid = fid };
+ dbscan scan;
+ dbfile *file = dbscan_list_entry(tx, &scan, db, &curr);
+
+ check_ret((file == NULL && scan.res == MDB_NOTFOUND));
+
+ dbscan_free(&scan);
+ }
+
+ return 1;
+}
+
+/* ********************************************************************** */
+
+// TODO: the various scan interfaces
+
+/* ********************************************************************** */
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+static int(*tests[])(void) = {
+ test_add_disk,
+ test_del_disk,
+ test_add_file,
+ test_del_file,
+
+ test_add_files,
+ test_del_files,
+
+ test_add_list1,
+ test_add_list2,
+ test_del_list1,
+ test_del_list2,
+ test_add_lists,
+
+ test_del_lists_all,
+ test_del_lists_some,
+
+ test_add_lists_dup,
+ test_del_lists_dup,
+
+ test_del_list_files,
+ NULL
+};
+
+static void setup(void) {
+ db = dbindex_open(dbpath);
+ this_test = NULL;
+}
+
+static void cleanup(void) {
+ char tmp[strlen(dbpath) + 16];
+ dbindex_close(db);
+
+ sprintf(tmp, "%s/data.mdb", dbpath);
+ unlink(tmp);
+ sprintf(tmp, "%s/lock.mdb", dbpath);
+ unlink(tmp);
+}
+
+int main(int argc, char **argv) {
+ int pass = 0;
+ int fail = 0;
+ struct obstack log;
+
+ if (!mkdtemp(dbpath))
+ return 1;
+
+ obstack_init(&log);
+ obstack_printf(&log, "\nSUMMARY\n-------\n");
+ printf("using db: %s\n", dbpath);
+ for (int i=0;tests[i];i++) {
+ int ok;
+
+ setup();
+
+ tx = dbindex_begin(db, NULL, 0);
+
+ ok = tests[i]();
+
+ if (ok)
+ dbindex_commit(tx);
+ else
+ dbindex_abort(tx);
+
+ printf("%s: %s\n", this_test, ok ? "pass" : "fail");
+ obstack_printf(&log, "%s: %s\n", ok ? "PASS" : "FAIL", this_test);
+
+ if (ok) {
+ ok &= check(dbindex_validate(db) == 1);
+ ok &= check(dbindex_validate_lists(db) == 0);
+ }
+
+ if (ok)
+ pass++;
+ else
+ fail++;
+
+ cleanup();
+ }
+
+ rmdir(dbpath);
+
+ obstack_printf(&log, "----------------\nTOTAL: pass: %d, fail: %d\n", pass, fail);
+ printf("\n");
+ fputs(obstack_finish(&log), stdout);
+ obstack_free(&log, NULL);
+ return fail;
+}