1 /* disk-util.c: utilities for managing indices.
3 Copyright (C) 2021 Michael Zucchi
5 This program is free software: you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation, either version 3 of the
8 License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see
17 <http://www.gnu.org/licenses/>.
20 #include <sys/types.h>
31 #include <libavformat/avformat.h>
37 #include "ez-bitset.h"
41 void dbshuffle_init2(dbindex *db);
43 /* ********************************************************************** */
49 // WORK IN PROGRESS, not hooked up yet
52 (naive) idea for full text sub-string search inside lmdb
53 just build full suffix tables
55 [suffix] [all members]
58 suffix >= query and suffix[0 .. query.length] == query
62 void build_suffix(dbtxn *tx, dbindex *db, uint32_t fileid, const char *words) {
64 size_t len = strlen(words);
65 char word[len+1]; // + ??
66 wchar_t lwords[len+1];
68 /* convert to wide char astring */
71 len = mbrtowcs(lwords, words, len, NULL);
72 if (len == (size_t)-1)
77 //printf("words: %s\n", words);
81 Break string into words.
84 Build suffix tables (in-memory db?)
86 we want ' included though, translate other 's into '? or remove all?
97 if (c == 0 || !iswgraph(c) || iswpunct(c)) {
102 len = wcstombs(word, &t, sizeof(word), NULL);
103 if (len < sizeof(word)) {
104 dbfile_put_suffix(tx, db, word, fileid);
106 fprintf(stderr, "overflow %s\n", words);
118 int dbfile_clear_suffix(dbtxn *tx, dbindex *db);
120 void suffix(const char *path) {
121 dbindex *db = dbindex_open(path);
122 dbtxn *tx = dbindex_begin(db, NULL, 0);
124 dbfile_clear_suffix(tx, db);
126 dbscan *scan = dbfile_scan_disk(tx, db, -1);
128 ez_list list = EZ_INIT_LIST(list);
130 while ((fid = dbfile_scan_next(scan)) != ~0) {
131 dbfile *f = dbfile_get(tx, db, fid);
132 struct string_node *w;
134 //printf("%s\n", f->title);
135 analyse_words(&list, 1, f->title);
136 while ((w = ez_list_remhead(&list))) {
137 //printf(" %s\n", w->value);
138 dbfile_put_suffix(tx, db, w->value, f->id);
145 dbfile_scan_close(scan);
150 void search_suffix(const char *path) {
151 dbindex *db = dbindex_open(path);
153 dbtxn *tx = dbindex_begin(db, NULL, 0);
155 dbfile_search_substring(tx, db, "union");
159 int len = dbfile_search(db, "cnic", matches, 50);
160 printf("matches: %d\n", len);
166 /* ********************************************************************** */
168 // TODO: This needs to be common with the player
169 const char * const list_names[4] = {
176 int main(int argc, char **argv) {
177 const char *dbdir = MAIN_INDEX;
179 dblistcursor info = { 0 };
182 setlocale(LC_ALL, "en_AU.UTF-8");
184 if (argc > 2 && strcmp(argv[1], "-d") == 0) {
190 dbindex *db = dbindex_open(dbdir);
192 for (int i=1;i<argc;i++) {
193 const char *cmd = argv[i];
196 if (strcmp(cmd, "-f") == 0) {
197 info.fileid = atoi(argv[++i]);
198 } else if (strcmp(cmd, "-s") == 0) {
199 info.seq = atoi(argv[++i]);
200 } else if (strcmp(cmd, "-d") == 0) {
201 diskid = atoi(argv[++i]);
202 } else if (strcmp(cmd, "-l") == 0) {
203 info.listid = atoi(argv[++i]);
204 } else if (strcmp(cmd, "check") == 0) {
206 } else if (strcmp(cmd, "shuffle") == 0) {
207 //dbshuffle_init2(db);
208 } else if (strcmp(cmd, "file-dump") == 0) {
210 // dump lists it's in
211 } else if (strcmp(cmd, "files") == 0) {
212 dbtxn *tx = dbindex_begin(db, NULL, 1);
215 for (dbfile *file = dbscan_file(tx, &scan, db, info.fileid); file; file = dbscan_file_next(&scan)) {
216 printf("fid=%4d title='%s' path='%s'\n", file->id, file->title, file->path);
221 } else if (strcmp(cmd, "file-del") == 0) {
222 dbtxn *tx = dbindex_begin(db, NULL, 0);
224 if ((res = dbfile_del_id(tx, db, info.fileid)) == 0)
228 } else if (strcmp(cmd, "file-inlist") == 0) {
229 dbtxn *tx = dbindex_begin(db, NULL, 1);
231 rc = dbfile_inlist(tx, db, info.fileid, info.listid);
232 res = dbindex_result(db);
234 printf("fileid=%d listid=%d inlist?=%d\n", info.fileid, info.listid, rc);
235 } else if (strcmp(cmd, "lists") == 0) {
236 dbtxn *tx = dbindex_begin(db, NULL, 1);
239 for (dblist *list = dbscan_list(tx, &scan, db, 0); list; list = dbscan_list_next(&scan)) {
240 printf("lid=%4d seq=%5d size=%5d name=%-20s (%s)\n", list->id, list->seq, list->size, list->name, list->desc);
245 } else if (strcmp(cmd, "lists-init") == 0) {
246 dbtxn *tx = dbindex_begin(db, NULL, 0);
248 if (dblist_update_all(tx, db) == 0) {
255 } else if (strcmp(cmd, "lists-reset") == 0) {
256 dbtxn *tx = dbindex_begin(db, NULL, 0);
258 if ((res = dblist_reset_all(tx, db)) == 0)
262 } else if (strcmp(cmd, "list-add") == 0) {
267 dblist_add(NULL, db, &list);
268 } else if (strcmp(cmd, "list-del") == 0) {
269 dblist_del_id(NULL, db, info.listid);
270 } else if (strcmp(cmd, "list-clear") == 0) {
271 dbtxn *tx = dbindex_begin(db, NULL, 0);
273 if ((res = dblist_reset(tx, db, info.listid)) == 0)
277 } else if (strcmp(cmd, "list-add-file") == 0) {
278 dbtxn *tx = dbindex_begin(db, NULL, 0);
280 if ((res = dblist_add_file(tx, db, &info)) == 0)
284 } else if (strcmp(cmd, "list-del-file") == 0) {
285 dbtxn *tx = dbindex_begin(db, NULL, 0);
287 if ((res = dblist_del_file(tx, db, &info)) == 0)
291 } else if (strcmp(cmd, "list-dump") == 0) {
292 dbtxn *tx = dbindex_begin(db, NULL, 1);
295 for (dbfile *file = dbscan_list_entry(tx, &scan, db, &info); file; file = dbscan_list_entry_next(&scan, &info)) {
296 printf("fid=%4d seq=%4d title: %-60s %s\n", file->id, info.seq, file->title, file->path);
301 } else if (strcmp(cmd, "disks") == 0) {
302 dbtxn *tx = dbindex_begin(db, NULL, 1);
305 for (dbdisk *disk = dbscan_disk(tx, &scan, db, 0); disk; disk = dbscan_disk_next(&scan)) {
306 printf("did=%4d flags=%08x uuid='%s' label='%s' mount='%s'\n", disk->id, disk->flags, disk->uuid, disk->label, disk->mount);
311 } else if (strcmp(cmd, "disk-del") == 0) {
313 dbtxn *tx = dbindex_begin(db, NULL, 0);
315 dbdisk_del_id(tx, db, diskid);
318 printf("%s: Must supply diskid\n", cmd);
320 } else if (strcmp(cmd, "validate") == 0) {
321 dbindex_validate(db);
322 } else if (strcmp(cmd, "search") == 0) {
323 const char *match = argv[++i];
324 dbfile *results[100];
325 int res = dbfile_search(db, match, results, 100);
328 for (int i=0;i<res;i++) {
329 printf("%4d title: %s\n", results[i]->id, results[i]->title);
330 dbfile_free(results[i]);
333 printf("search failed\n");
335 } else if (strcmp(cmd, "play-state") == 0) {
336 dbtxn *tx = dbindex_begin(db, NULL, 1);
338 for (int i=ZPL_SIZEOF-1;i>=0;i--) {
344 state.curr.listid = dblistid_get_name(tx, db, list_names[i]);
345 res = dbstate_get(tx, db, state.curr.listid, &state);
348 printf("%4d: state=%08x stamp=%016lx pos=%9.3fs lid=%4d seq=%4d fid=%5d\n",
349 i, state.state, state.stamp, state.poss,
350 state.curr.listid, state.curr.seq, state.curr.fileid);
353 } else if (strcmp(cmd, "play-queue") == 0) {
354 dbtxn *tx = dbindex_begin(db, NULL, 1);
357 int limit = argc == 3 ? atoi(argv[2]) : 10;
359 for (int i=ZPL_SIZEOF-1;i>=0 && count < limit;i--) {
368 state.curr.listid = dblistid_get_name(tx, db, list_names[i]);
369 res = dbstate_get(tx, db, state.curr.listid, &state);
373 file = dbscan_list_entry(tx, &scan, db, &state.curr);
374 while (count < limit && file) {
375 if (listcount > 0 || (state.state & ZPL_STATEF_PLAYING) == 0) {
376 printf("%4d: fid=%4d lid=%4d seq=%4d title: %-60s %s\n", count++, file->id, state.curr.listid, state.curr.seq, file->title, file->path);
380 file = dbscan_list_entry_next(&scan, &state.curr);
384 res = dbscan_result(&scan);
387 // loop on default playlist always
388 if (res == MDB_NOTFOUND && i == 0 && state.curr.seq > 0) {
390 state.curr.fileid = 0;
396 res = dbstate_get(tx, db, &state);
401 file = dbfile_get(tx, db, info.fileid);
402 printf("%4d: fid=%4d lid=%4d seq=%4d title: %-60s %s\n", count++, file->id, info.listid, info.seq, file->title, file->path);
403 if (state.list_curr != ZPL_USER
404 && state.list[state.list_curr].listid != info.listid) {
405 info = state.list[state.list_curr];
409 while (count < limit) {
410 file = dbscan_list_entry(tx, &scan, db, &info);
414 file = dbscan_list_entry_next(&scan, &info);
416 while (file && count < limit) {
417 printf("%4d: fid=%4d lid=%4d seq=%4d title: %-60s %s\n", count++, file->id, info.listid, info.seq, file->title, file->path);
420 file = dbscan_list_entry_next(&scan, &info);
426 res = dbscan_result(&scan);
428 if (res == MDB_NOTFOUND) {
429 if (state.list_curr > 0) {
430 state.list_curr -= 1;
431 info = state.list[state.list_curr];
433 } else if (info.listid == state.list[ZPL_DEFAULT].listid && info.seq == 0 && info.fileid == 0) {
436 info.listid = state.list[ZPL_DEFAULT].listid;
444 printf("state query failed\n");
450 printf("error (%d): %s\n", res, mdb_strerror(res));