Cleanup dbindex.c a bit.
[playerz] / index-util.c
1 /* disk-util.c: utilities for managing indices.
2
3    Copyright (C) 2021 Michael Zucchi
4
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.
9
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.
14
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/>.
18 */
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/time.h>
23 #include <unistd.h>
24 #include <dirent.h>
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <locale.h>
30
31 #include <libavformat/avformat.h>
32
33 #include <regex.h>
34
35 #include "ez-list.h"
36 #include "ez-set.h"
37 #include "ez-bitset.h"
38
39 #include "dbindex.h"
40
41 void dbshuffle_init2(dbindex *db);
42
43 /* ********************************************************************** */
44
45 #if 0
46
47 #include "analyse.h"
48
49 // WORK IN PROGRESS, not hooked up yet
50
51 /*
52   (naive) idea for full text sub-string search inside lmdb
53   just build full suffix tables
54
55   [suffix] [all members]
56
57   sub-string search is:
58    suffix >= query and suffix[0 .. query.length] == query
59
60  */
61 #if 0
62 void build_suffix(dbtxn *tx, dbindex *db, uint32_t fileid, const char *words) {
63         int state = 0;
64         size_t len = strlen(words);
65         char word[len+1]; // + ??
66         wchar_t lwords[len+1];
67
68         /* convert to wide char astring */
69         size_t res;
70
71         len = mbrtowcs(lwords, words, len, NULL);
72         if (len == (size_t)-1)
73                 return;
74
75
76
77         //printf("words: %s\n", words);
78
79         /*
80           Basic idea:
81           Break string into words.
82           Strip puncutation.
83           lower-scase
84           Build suffix tables (in-memory db?)
85
86           we want ' included though, translate other 's into '?  or remove all?
87          */
88
89         int high = 0;
90
91         wchar_t c;
92         wchar_t *p = lwords;
93         wchar_t *s = p;
94         do {
95                 c = *p;
96
97                 if (c == 0 || !iswgraph(c) || iswpunct(c)) {
98                         *p = 0;
99                         while (p - s >= 3) {
100                                 wchar_t *t = s++;
101
102                                 len = wcstombs(word, &t, sizeof(word), NULL);
103                                 if (len < sizeof(word)) {
104                                         dbfile_put_suffix(tx, db, word, fileid);
105                                 } else {
106                                         fprintf(stderr, "overflow %s\n", words);
107                                 }
108                         }
109                         s = ++p;
110                 } else {
111                         *p++ = towlower(c);
112                 }
113         } while (c);
114
115 }
116 #endif
117
118 int dbfile_clear_suffix(dbtxn *tx, dbindex *db);
119
120 void suffix(const char *path) {
121         dbindex *db = dbindex_open(path);
122         dbtxn *tx = dbindex_begin(db, NULL, 0);
123
124         dbfile_clear_suffix(tx, db);
125
126         dbscan *scan = dbfile_scan_disk(tx, db, -1);
127         uint32_t fid;
128         ez_list list = EZ_INIT_LIST(list);
129
130         while ((fid = dbfile_scan_next(scan)) != ~0) {
131                 dbfile *f = dbfile_get(tx, db, fid);
132                 struct string_node *w;
133
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);
139                         free(w);
140                 }
141
142                 dbfile_free(f);
143         }
144
145         dbfile_scan_close(scan);
146         dbindex_commit(tx);
147         dbindex_close(db);
148 }
149
150 void search_suffix(const char *path) {
151         dbindex *db = dbindex_open(path);
152         if (1) {
153                 dbtxn *tx = dbindex_begin(db, NULL, 0);
154
155                 dbfile_search_substring(tx, db, "union");
156                 dbindex_abort(tx);
157         } else {
158                 dbfile *matches[50];
159                 int len = dbfile_search(db, "cnic", matches, 50);
160                 printf("matches: %d\n", len);
161         }
162         dbindex_close(db);
163 }
164 #endif
165
166 /* ********************************************************************** */
167
168 // TODO: This needs to be common with the player
169 const char * const list_names[4] = {
170         "all#shuffle",
171         NULL,
172         "jukebox",
173         "playnow"
174 };
175
176 int main(int argc, char **argv) {
177         const char *dbdir = MAIN_INDEX;
178         dbid_t diskid = 0;
179         dblistcursor info = { 0 };
180         int rc = 0;
181
182         setlocale(LC_ALL, "en_AU.UTF-8");
183
184         if (argc > 2 && strcmp(argv[1], "-d") == 0) {
185                 dbdir = argv[2];
186                 argv += 2;
187                 argc -= 2;
188         }
189
190         dbindex *db = dbindex_open(dbdir);
191
192         for (int i=1;i<argc;i++) {
193                 const char *cmd = argv[i];
194                 int res = 0;
195
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) {
205                         //check(db);
206                 } else if (strcmp(cmd, "shuffle") == 0) {
207                         //dbshuffle_init2(db);
208                 } else if (strcmp(cmd, "file-dump") == 0) {
209                         // dump file info
210                         // dump lists it's in
211                 } else if (strcmp(cmd, "files") == 0) {
212                         dbtxn *tx = dbindex_begin(db, NULL, 1);
213                         dbscan scan;
214
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);
217                                 dbfile_free(file);
218                         }
219                         dbscan_free(&scan);
220                         dbindex_abort(tx);
221                 } else if (strcmp(cmd, "file-del") == 0) {
222                         dbtxn *tx = dbindex_begin(db, NULL, 0);
223
224                         if ((res = dbfile_del_id(tx, db, info.fileid)) == 0)
225                                 dbindex_commit(tx);
226                         else
227                                 dbindex_abort(tx);
228                 } else if (strcmp(cmd, "file-inlist") == 0) {
229                         dbtxn *tx = dbindex_begin(db, NULL, 1);
230
231                         rc = dbfile_inlist(tx, db, info.fileid, info.listid);
232                         res = dbindex_result(db);
233                         dbindex_abort(tx);
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);
237                         dbscan scan;
238
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);
241                                 dblist_free(list);
242                         }
243                         dbscan_free(&scan);
244                         dbindex_abort(tx);
245                 } else if (strcmp(cmd, "lists-init") == 0) {
246                         dbtxn *tx = dbindex_begin(db, NULL, 0);
247
248                         if (dblist_update_all(tx, db) == 0) {
249                                 printf("ok\n");
250                                 dbindex_commit(tx);
251                         } else {
252                                 printf("failed\n");
253                                 dbindex_abort(tx);
254                         }
255                 } else if (strcmp(cmd, "lists-reset") == 0) {
256                         dbtxn *tx = dbindex_begin(db, NULL, 0);
257
258                         if ((res = dblist_reset_all(tx, db)) == 0)
259                                 dbindex_commit(tx);
260                         else
261                                 dbindex_abort(tx);
262                 } else if (strcmp(cmd, "list-add") == 0) {
263                         dblist list = {
264                                 .name = argv[++i]
265                         };
266
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);
272
273                         if ((res = dblist_reset(tx, db, info.listid)) == 0)
274                                 dbindex_commit(tx);
275                         else
276                                 dbindex_abort(tx);
277                 } else if (strcmp(cmd, "list-add-file") == 0) {
278                         dbtxn *tx = dbindex_begin(db, NULL, 0);
279
280                         if ((res = dblist_add_file(tx, db, &info)) == 0)
281                                 dbindex_commit(tx);
282                         else
283                                 dbindex_abort(tx);
284                 } else if (strcmp(cmd, "list-del-file") == 0) {
285                         dbtxn *tx = dbindex_begin(db, NULL, 0);
286
287                         if ((res = dblist_del_file(tx, db, &info)) == 0)
288                                 dbindex_commit(tx);
289                         else
290                                 dbindex_abort(tx);
291                 } else if (strcmp(cmd, "list-dump") == 0) {
292                         dbtxn *tx = dbindex_begin(db, NULL, 1);
293                         dbscan scan;
294
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);
297                                 dbfile_free(file);
298                         }
299                         dbscan_free(&scan);
300                         dbindex_abort(tx);
301                 } else if (strcmp(cmd, "disks") == 0) {
302                         dbtxn *tx = dbindex_begin(db, NULL, 1);
303                         dbscan scan;
304
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);
307                                 dbdisk_free(disk);
308                         }
309                         dbscan_free(&scan);
310                         dbindex_abort(tx);
311                 } else if (strcmp(cmd, "disk-del") == 0) {
312                         if (diskid != 0) {
313                                 dbtxn *tx = dbindex_begin(db, NULL, 0);
314
315                                 dbdisk_del_id(tx, db, diskid);
316                                 dbindex_commit(tx);
317                         } else {
318                                 printf("%s: Must supply diskid\n", cmd);
319                         }
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);
326
327                         if (res >= 0) {
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]);
331                                 }
332                         } else {
333                                 printf("search failed\n");
334                         }
335                 } else if (strcmp(cmd, "play-state") == 0) {
336                         dbtxn *tx = dbindex_begin(db, NULL, 1);
337
338                         for (int i=ZPL_SIZEOF-1;i>=0;i--) {
339                                 dbstate state;
340
341                                 if (!list_names[i])
342                                         continue;
343
344                                 state.curr.listid = dblistid_get_name(tx, db, list_names[i]);
345                                 res = dbstate_get(tx, db, state.curr.listid, &state);
346                                 if (res != 0)
347                                         continue;
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);
351                         }
352                         dbindex_commit(tx);
353                 } else if (strcmp(cmd, "play-queue") == 0) {
354                         dbtxn *tx = dbindex_begin(db, NULL, 1);
355                         int res;
356                         int count = 0;
357                         int limit = argc == 3 ? atoi(argv[2]) : 10;
358
359                         for (int i=ZPL_SIZEOF-1;i>=0 && count < limit;i--) {
360                                 dbstate state;
361                                 dbscan scan;
362                                 dbfile *file;
363                                 int listcount = 0;
364
365                                 if (!list_names[i])
366                                         continue;
367
368                                 state.curr.listid = dblistid_get_name(tx, db, list_names[i]);
369                                 res = dbstate_get(tx, db, state.curr.listid, &state);
370                                 if (res != 0)
371                                         continue;
372                         loop:
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);
377                                         }
378
379                                         dbfile_free(file);
380                                         file = dbscan_list_entry_next(&scan, &state.curr);
381                                         listcount++;
382                                 }
383                                 dbfile_free(file);
384                                 res = dbscan_result(&scan);
385                                 dbscan_free(&scan);
386
387                                 // loop on default playlist always
388                                 if (res == MDB_NOTFOUND && i == 0 && state.curr.seq > 0) {
389                                         state.curr.seq = 0;
390                                         state.curr.fileid = 0;
391                                         goto loop;
392                                 }
393                         }
394
395 #if 0
396                         res = dbstate_get(tx, db, &state);
397                         if (res >= 0) {
398                                 int next = 1;
399
400                                 info = state.curr;
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];
406                                         next = 0;
407                                 }
408
409                                 while (count < limit) {
410                                         file = dbscan_list_entry(tx, &scan, db, &info);
411                                         if (file) {
412                                                 if (next) {
413                                                         dbfile_free(file);
414                                                         file = dbscan_list_entry_next(&scan, &info);
415                                                 }
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);
418                                                         dbfile_free(file);
419                                                         if (count < limit) {
420                                                                 file = dbscan_list_entry_next(&scan, &info);
421                                                         } else {
422                                                                 file = NULL;
423                                                         }
424                                                 }
425                                         }
426                                         res = dbscan_result(&scan);
427                                         dbscan_free(&scan);
428                                         if (res == MDB_NOTFOUND) {
429                                                 if (state.list_curr > 0) {
430                                                         state.list_curr -= 1;
431                                                         info = state.list[state.list_curr];
432                                                         next = 0;
433                                                 } else if (info.listid == state.list[ZPL_DEFAULT].listid && info.seq == 0 && info.fileid == 0) {
434                                                         break;
435                                                 } else {
436                                                         info.listid = state.list[ZPL_DEFAULT].listid;
437                                                         info.seq = 0;
438                                                         info.fileid = 0;
439                                                         next = 0;
440                                                 }
441                                         }
442                                 }
443                         } else {
444                                 printf("state query failed\n");
445                         }
446 #endif
447                         dbindex_commit(tx);
448                 }
449                 if (res)
450                         printf("error (%d): %s\n", res, mdb_strerror(res));
451         }
452
453         dbindex_close(db);
454
455         return rc;
456 }