From: Not Zed Date: Thu, 11 Jan 2024 05:39:52 +0000 (+1030) Subject: Cleanup dbindex.c a bit. X-Git-Url: https://code.zedzone.au/cvs?a=commitdiff_plain;h=4608bc40c2695df98b82d969fcc791f86e6db635;p=playerz Cleanup dbindex.c a bit. Added list validation code. Added seq to list table. Added preliminary tests. --- diff --git a/Makefile b/Makefile index bc15db5..c7aae83 100644 --- a/Makefile +++ b/Makefile @@ -50,10 +50,10 @@ dump: dump.o dbindex.o 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 diff --git a/blobs.c b/blobs.c index bb8f819..4374afe 100644 --- a/blobs.c +++ b/blobs.c @@ -46,10 +46,11 @@ ez_blob_desc DBFILE_DESC[] = { }; 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[] = { diff --git a/dbindex.c b/dbindex.c index a390127..9aeb6a2 100644 --- a/dbindex.c +++ b/dbindex.c @@ -40,6 +40,7 @@ #include "ez-set.h" #include "ez-blob.h" #include "ez-blob-basic.h" +#include "dbmarshal.h" // prototype void dblist_dump(dbtxn *txn, dbindex *db); @@ -399,7 +400,7 @@ static void *secondary_get_decode(MDB_txn *tx, dbindex *db, ez_blob_desc *desc, 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); @@ -471,8 +472,6 @@ static int secondary_list_all(dbtxn *tx, MDB_dbi secondary, ez_array *array) { ez_array_clear(array); } - printf("secondary list all: %s\n", mdb_strerror(res)); - mdb_cursor_close(cursor); return res; } @@ -499,8 +498,6 @@ static int secondary_list_key(dbtxn *tx, MDB_dbi secondary, MDB_val *key, ez_arr ez_array_clear(array); } - printf("secondary list key: %s\n", mdb_strerror(res)); - mdb_cursor_close(cursor); return res; } @@ -542,7 +539,7 @@ fail: 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) { @@ -553,7 +550,7 @@ int dbdisk_del_id(dbtxn *tx, dbindex *db, int diskid) { 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; @@ -667,9 +664,7 @@ void dbfile_free(dbfile *f) { } } -#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; @@ -693,7 +688,7 @@ dbfile *dbfile_get(dbtxn *tx, dbindex *db, int fileid) { #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) { @@ -811,7 +806,6 @@ int dbfile_del(dbtxn *tx, dbindex *db, dbfile *f) { else res = ENOMEM; } - printf("get list contents: res=%s\n", mdb_strerror(res)); mdb_cursor_close(cursor); if (res != MDB_NOTFOUND) goto fail2; @@ -830,7 +824,7 @@ int dbfile_del(dbtxn *tx, dbindex *db, dbfile *f) { .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; @@ -861,7 +855,7 @@ int dbfile_del(dbtxn *tx, dbindex *db, dbfile *f) { 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 { @@ -1235,8 +1229,8 @@ static int dblist_put(dbtxn *tx, dbindex *db, dblist *list, unsigned int flags) 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); } @@ -1250,6 +1244,10 @@ dblist *dblist_get_name(dbtxn *tx, dbindex *db, const char *name) { 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, @@ -1262,9 +1260,6 @@ dbid_t dblistid_get_name(dbtxn *tx, dbindex *db, const char *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) { @@ -1295,7 +1290,7 @@ 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; @@ -1347,27 +1342,29 @@ fail: 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); @@ -1395,13 +1392,31 @@ int dblist_del(dbtxn *txn, dbindex *db, int listid) { 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 @@ -1420,16 +1435,14 @@ int dblist_add_file(MDB_txn *tx, dbindex *db, dblistcursor *info) { 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; @@ -1438,15 +1451,12 @@ int dblist_add_file(MDB_txn *tx, dbindex *db, dblistcursor *info) { 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; @@ -1455,23 +1465,6 @@ fail: 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;ires; + // 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) }; @@ -1514,6 +1517,7 @@ int dblist_update(dbtxn *tx, dbindex *db, const char *name, dbid_t *fids, size_t // update/fix list record list->size = count; + list->seq = count; res = dblist_put(tx, db, list, 0); fail: @@ -1610,14 +1614,14 @@ void dblist_dump(dbtxn *tx, dbindex *db) { 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) @@ -1625,14 +1629,14 @@ void dblist_dump(dbtxn *tx, dbindex *db) { } 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) @@ -1645,15 +1649,15 @@ void dblist_dump(dbtxn *tx, dbindex *db) { } // 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; @@ -1662,41 +1666,41 @@ int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) { 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; @@ -1710,8 +1714,8 @@ int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) { 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); @@ -1720,8 +1724,8 @@ int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) { } 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); @@ -1729,6 +1733,16 @@ int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) { 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); @@ -1736,7 +1750,7 @@ int dblist_del_file(MDB_txn *txn, dbindex *db, struct dblistcursor *list) { 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); @@ -1905,26 +1919,27 @@ void dbindex_dump(dbindex *db); 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); } @@ -1933,24 +1948,25 @@ void dbindex_dump(dbindex *db) { { 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*/ @@ -2244,7 +2260,6 @@ static int cmp_fid(const void *ap, const void *bp) { return *(const int32_t *)ap - *(const int32_t *)bp; } - /* protoyping */ static int check_path(const dbfile *file, const MDB_val *key) { @@ -2314,7 +2329,7 @@ int dbindex_validate(dbindex *db) { 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); @@ -2357,3 +2372,183 @@ int dbindex_validate(dbindex *db) { 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;ikey = 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;icount != 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; +} diff --git a/dbindex.h b/dbindex.h index 39ee648..a2c3126 100644 --- a/dbindex.h +++ b/dbindex.h @@ -40,7 +40,8 @@ typedef struct dblist dblist; struct dblist { dbid_t id; - int size; + uint32_t seq; + uint32_t size; char *name; char *desc; }; @@ -130,21 +131,21 @@ int dbstate_del_id(MDB_txn *tx, dbindex *db, dbid_t listid); //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); @@ -163,17 +164,17 @@ extern ez_blob_desc DBLIST_DESC[]; 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); @@ -188,11 +189,10 @@ int dblist_update_all(dbtxn *tx, dbindex *db); // delete all lists and run update_all int dblist_reset_all(dbtxn *tx, dbindex *db); - int dbfile_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); diff --git a/http-monitor.c b/http-monitor.c index bd29cfc..1d51169 100644 --- a/http-monitor.c +++ b/http-monitor.c @@ -597,27 +597,21 @@ static int handle_list(struct ez_httprequest *req, struct ez_httpresponse *rep) 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: diff --git a/index-util.c b/index-util.c index e47b180..0c60e87 100644 --- a/index-util.c +++ b/index-util.c @@ -39,9 +39,6 @@ #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); /* ********************************************************************** */ @@ -240,7 +237,7 @@ int main(int argc, char **argv) { 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); @@ -269,11 +266,11 @@ int main(int argc, char **argv) { 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); diff --git a/music-player.c b/music-player.c index 40bc775..4a65572 100644 --- a/music-player.c +++ b/music-player.c @@ -913,11 +913,12 @@ static int audio_restore_state(struct audio_player *ap) { 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); } } diff --git a/test-index.c b/test-index.c new file mode 100644 index 0000000..e3ce931 --- /dev/null +++ b/test-index.c @@ -0,0 +1,810 @@ +/* run some tests against dbindex */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include + +#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;ifile_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;isize == 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;iid, .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;iid, .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;isize == 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;iid, .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;iid, .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 +#include + +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; +}