tagscan now tries to guess track number
[k8muffin.git] / src / search.c
blobfbe59e293081d6f1351e566a2428ef0d5ee240ee
1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "common.h"
20 ////////////////////////////////////////////////////////////////////////////////
21 #ifdef NO_SEARCH_DBGLOG
22 # define sxdlogf(...) ((void)0)
23 #else
24 # define sxdlogf dlogf
25 #endif
28 ////////////////////////////////////////////////////////////////////////////////
29 static int opt_files_only_in_files = 1;
32 ////////////////////////////////////////////////////////////////////////////////
33 typedef struct {
34 const char *name;
35 fl_fileinfo_t *fi;
36 UT_hash_handle hh;
37 } filename_t;
40 static filename_t *sname_hash = NULL;
41 static filename_t *sname_data = NULL;
44 typedef struct {
45 const char *value;
46 fl_tagvalue_t *tv;
47 UT_hash_handle hh;
48 } tagvalue_t;
50 // all tags
51 static tagvalue_t *tval_hash = NULL;
52 static tagvalue_t *tval_data = NULL;
54 static tagvalue_t *tspec_hash[TFL_MAX] = {NULL};
55 static tagvalue_t *tspec_data[TFL_MAX] = {NULL};
58 static fl_tagvalue_t *get_tag_by_name (const char *str, tagvalue_t **hash) {
59 if (str != NULL && str[0]) {
60 tagvalue_t *tv;
61 HASH_FIND_STR(*hash, str, tv);
62 return (tv != NULL ? tv->tv : NULL);
64 return NULL;
68 #define XCLR(var) do { \
69 HASH_CLEAR(hh, var##_hash); \
70 if (var##_data != NULL) free(var##_data); \
71 var##_data = NULL; \
72 var##_hash = NULL; \
73 } while (0)
75 static void clear_hashes (void) {
76 XCLR(sname);
77 XCLR(tval);
78 for (int f = 0; f < TFL_MAX; ++f) {
79 HASH_CLEAR(hh, tspec_hash[f]);
80 if (tspec_data[f] != NULL) free(tspec_data[f]);
81 tspec_hash[f] = NULL;
82 tspec_data[f] = NULL;
86 #undef XCLR
89 // allocate full chunks, it's faster and better than malloc()ing each item separately
90 static void build_hashes (void) {
91 sname_data = malloc(sizeof(sname_data[0])*file_count);
92 for (uint32_t f = 0; f < file_count; ++f) {
93 fl_fileinfo_t *fi = get_fi(f);
94 filename_t *fn = &sname_data[f];
95 fn->name = get_str(fi->shortname);
96 fn->fi = fi;
97 HASH_ADD_KEYPTR(hh, sname_hash, fn->name, strlen(fn->name), fn);
99 printf("%d snames in hash\n", HASH_COUNT(sname_hash));
101 tval_data = malloc(sizeof(tval_data[0])*tag_count);
102 for (uint32_t f = 0; f < tag_count; ++f) {
103 fl_tagvalue_t *tv = get_tv(f);
104 tagvalue_t *t = &tval_data[f];
105 //if (tv->value >= string_bytes) abort();
106 t->value = get_str(tv->value);
107 t->tv = tv;
108 HASH_ADD_KEYPTR(hh, tval_hash, t->value, strlen(t->value), t);
110 printf("%d tags in hash\n", HASH_COUNT(tval_hash));
112 int spcnt[TFL_MAX];
113 for (int f = 0; f < TFL_MAX; ++f) {
114 spcnt[f] = 0;
115 tspec_data[f] = malloc(sizeof(tspec_data[f][0])*tag_count); //FIXME: too much
117 for (uint32_t f = 0; f < file_count; ++f) {
118 //const fl_fileinfo_t *fi = get_fi_idx(f);
119 for (int tn = 0; tn < TFL_MAX; ++tn) {
120 tagvalue_t *tv;
121 fl_tagvalue_t *ftv = get_tv(get_fi(f)->tags[tn]);
122 const char *str = get_str(ftv->value);
123 HASH_FIND_STR(tspec_hash[tn], str, tv);
124 if (tv == NULL) {
125 tv = &tspec_data[tn][spcnt[tn]++];
126 tv->value = str;
127 tv->tv = ftv;
128 HASH_ADD_KEYPTR(hh, tspec_hash[tn], tv->value, strlen(tv->value), tv);
135 ////////////////////////////////////////////////////////////////////////////////
136 #define ARRAYLEN(arr) (sizeof((arr))/sizeof((arr)[0]))
139 typedef enum {
140 SPT_ANY,
141 SPT_ORIG,
142 SPT_TRANS
143 } special_dir_type;
146 static const special_dir_type tagtype[TFL_MAX] = {
147 SPT_ANY, // TFL_YEAR
148 SPT_ORIG, // TFL_ARTIST_O
149 SPT_TRANS, // TFL_ARTIST_T
150 SPT_ORIG, // TFL_ALBUM_O
151 SPT_TRANS, // TFL_ALBUM_T
152 SPT_ORIG, // TFL_TITLE_O
153 SPT_TRANS, // TFL_TITLE_T
154 SPT_ORIG, // TFL_GENRE_O
155 SPT_TRANS, // TFL_GENRE_T
159 typedef struct {
160 const char *name; // with colon
161 special_dir_type type;
162 int tflidx;
163 } special_dir_info_t;
166 static const special_dir_info_t spec_dirs[] = {
167 {.name=":oartist", .type=SPT_ORIG, .tflidx=TFL_ARTIST_O},
168 {.name=":oalbum", .type=SPT_ORIG, .tflidx=TFL_ALBUM_O},
169 {.name=":otitle", .type=SPT_ORIG, .tflidx=TFL_TITLE_O},
170 //{.name=":ogenre", .type=SPT_ORIG, .tflidx=TFL_GENRE_O},
171 {.name=":year", .type=SPT_ANY, .tflidx=TFL_YEAR},
172 {.name=":artist", .type=SPT_TRANS, .tflidx=TFL_ARTIST_T},
173 {.name=":album", .type=SPT_TRANS, .tflidx=TFL_ALBUM_T},
174 {.name=":title", .type=SPT_TRANS, .tflidx=TFL_TITLE_T},
175 //{.name=":genre", .type=SPT_TRANS, .tflidx=TFL_GENRE_T},
179 ////////////////////////////////////////////////////////////////////////////////
180 static inline const special_dir_info_t *get_special_by_name (const char *name) {
181 if (name != NULL && name[0] == ':') {
182 for (size_t f = 0; f < ARRAYLEN(spec_dirs); ++f) {
183 if (strcmp(spec_dirs[f].name, name) == 0) return &spec_dirs[f];
186 return NULL;
190 static inline fl_tagvalue_t *get_tag_with_special (const char *name, const special_dir_info_t *sp) {
191 return (sp != NULL ? get_tag_by_name(name, &tspec_hash[sp->tflidx]) : get_tag_by_name(name, &tval_hash));
195 ////////////////////////////////////////////////////////////////////////////////
196 typedef struct {
197 //fl_fileinfo_t **files;
198 const char **names; // short names
199 int fcount;
200 } dirinfo_t;
203 typedef struct {
204 uint32_t idx;
205 UT_hash_handle hh;
206 } u32value_t;
209 static inline int is_file_in_tfl (const fl_tfl_t *tfl, uint32_t fidx) {
210 return bsearch(&fidx, tfl_data+tfl->idx, tfl->count, sizeof(uint32_t), lambda(int, (const void *p0, const void *p1) {
211 uint32_t v0 = *((const uint32_t *)p0);
212 uint32_t v1 = *((const uint32_t *)p1);
213 return (v0 < v1 ? -1 : (v0 > v1 ? 1 : 0));
214 })) != NULL;
218 // /tag/tag/tag/
219 // /:artist/name/:album/name/
220 static dirinfo_t *list_dir_simple (const char *path) {
221 u32value_t *files = NULL; // NULL: all files in list
222 u32value_t *fhash = NULL; // to ease searching by id
223 uint32_t pos = 0;
224 int onlyfiles = 0;
225 special_dir_type sptype = SPT_ANY;
226 const special_dir_info_t *sdi = NULL;
227 char *condstr = alloca(strlen(path)+1);
228 u32value_t *tl_hash = NULL, *hx, *thx;
229 dirinfo_t *di;
230 int firsttime = 1;
232 //TODO: accelerate this with hash
233 void add_from_tv (const fl_tagvalue_t *tv, const special_dir_info_t *sp, special_dir_type tp) {
234 // just add all files
235 uint32_t cnt = 0;
236 // let it be MANY
237 for (int tn = 0; tn < TFL_MAX; ++tn) cnt += tv->tfl[tn].count;
238 files = malloc(sizeof(files[0])*cnt+1);
239 cnt = 0;
240 for (int tn = 0; tn < TFL_MAX; ++tn) {
241 const fl_tfl_t *tfl;
242 u32value_t *fl;
243 if (sp != NULL && tn != sp->tflidx) continue;
244 if (sp == NULL && tp != SPT_ANY && tagtype[tn] != SPT_ANY && tp != tagtype[tn]) continue;
245 tfl = &tv->tfl[tn];
246 for (uint32_t f = 0; f < tfl->count; ++f) {
247 uint32_t idx = tfl_data[tfl->idx+f];
248 HASH_FIND_UINT32(fhash, &idx, fl);
249 if (fl == NULL) {
250 fl = &files[cnt++];
251 fl->idx = idx;
252 HASH_ADD_UINT32(fhash, idx, fl);
258 void remove_not_have_tv (const fl_tagvalue_t *tv, const special_dir_info_t *sp, special_dir_type tp) {
259 // remove all files that have no such tag in given field
260 u32value_t *cf, *cftmp;
261 HASH_ITER(hh, fhash, cf, cftmp) {
262 const fl_fileinfo_t *fi = get_fi(cf->idx);
263 int dodrop = 1;
264 for (int tn = 0; tn < TFL_MAX; ++tn) {
265 if (sp != NULL && tn != sp->tflidx) continue;
266 if (sp == NULL && tp != SPT_ANY && tagtype[tn] != SPT_ANY && tp != tagtype[tn]) continue;
267 //sxdlogf("checking (%d)[%s]: %u : %u\n", tn, get_str(fi->shortname), fi->tags[tn], tv2idx(tv));
268 if (fi->tags[tn] == tv2idx(tv)) { dodrop = 0; break; }
270 if (dodrop) HASH_DEL(fhash, cf);
274 di = calloc(1, sizeof(*di));
276 if (strcmp(path, "/") == 0 || strcmp(path, "/:ttags") == 0 || strcmp(path, "/:otags/:ttags") == 0) {
277 doroot:
278 // wow! list ALL TAGS here!
279 di = calloc(1, sizeof(*di));
280 di->fcount = t_tag_count+ARRAYLEN(spec_dirs)+2;
281 di->names = calloc(di->fcount, sizeof(di->names[0]));
283 di->names[pos++] = ":files";
284 di->names[pos++] = ":otags";
286 for (int f = 0; f < ARRAYLEN(spec_dirs); ++f) if (spec_dirs[f].type == SPT_TRANS || spec_dirs[f].type == SPT_ANY) di->names[pos++] = spec_dirs[f].name;
287 for (uint32_t f = 0; f < t_tag_count; ++f) {
288 if (get_str(get_tv(taglistt_data[f])->value)[0] == 0) {
289 printf("tags=%u; idx=%u; ssize=%u; sofs=%u\n", tag_count, taglistt_data[f], string_bytes, get_tv(taglistt_data[f])->value);
290 for (uint32_t fn = 0; fn < file_count; ++fn) {
291 for (int tn = 0; tn < TFL_MAX; ++tn) {
292 if (get_fi(fn)->tags[tn] == taglistt_data[f]) {
293 printf("tag %d of file [%s] [%s] is empty!\n", tn, get_str(get_fi(fn)->realname), get_str(get_fi(fn)->shortname));
297 abort();
299 di->names[pos++] = get_str(get_tv(taglistt_data[f])->value);
301 di->fcount = pos;
302 goto ret_sort_di;
305 if (strcmp(path, "/:otags") == 0 || strcmp(path, "/:otags/:ttags") == 0) {
306 // wow! list ALL TAGS here!
307 di = calloc(1, sizeof(*di));
308 di->fcount = o_tag_count+ARRAYLEN(spec_dirs)+2;
309 di->names = calloc(di->fcount, sizeof(di->names[0]));
311 di->names[pos++] = ":files";
312 di->names[pos++] = ":ttags";
314 for (int f = 0; f < ARRAYLEN(spec_dirs); ++f) if (spec_dirs[f].type == SPT_ORIG || spec_dirs[f].type == SPT_ANY) di->names[pos++] = spec_dirs[f].name;
315 for (uint32_t f = 0; f < o_tag_count; ++f) di->names[pos++] = get_str(get_tv(taglisto_data[f])->value);
316 di->fcount = pos;
317 goto ret_sort_di;
320 sxdlogf("path: [%s]\n", path);
321 // select and then refine
322 while (*path) {
323 fl_tagvalue_t *tv;
324 while (*path == '/') ++path;
325 if (!path[0]) break;
327 char *e = strchr(path, '/');
328 if (e == NULL) {
329 strcpy(condstr, path);
330 path += strlen(path);
331 } else {
332 memcpy(condstr, path, e-path);
333 condstr[e-path] = 0;
334 path = e+1;
337 sxdlogf("(fcount=%d) condstr: [%s]; sptype: %d\n", HASH_COUNT(fhash), condstr, sptype);
338 if (condstr[0] == ':') {
339 // specific tag
340 if (strcmp(condstr, ":and") == 0 || strcmp(condstr, ":or") == 0) {
341 if (firsttime) goto doroot;
342 break;
344 firsttime = 0;
346 if (sdi != NULL) { sxdlogf("error: 2 specials\n"); HASH_CLEAR(hh, fhash); break; } // error
348 if (strcmp(condstr, ":files") == 0) {
349 // we want only files
350 onlyfiles = 1;
351 if (files == NULL) {
352 // wow, all files!
353 files = malloc(sizeof(files[0])*file_count);
354 for (uint32_t f = 0; f < file_count; ++f) {
355 u32value_t *fl = &files[f];
356 fl->idx = f;
357 HASH_ADD_UINT32(fhash, idx, fl);
360 break;
363 if (strcmp(condstr, ":otags") == 0) {
364 if (sptype == SPT_ORIG) { sxdlogf(" error: 'otags' in tmode\n"); HASH_CLEAR(hh, fhash); break; } // error
365 sptype = SPT_ORIG;
366 sxdlogf("otags; path=[%s]\n", path);
367 continue;
370 if (strcmp(condstr, ":ttags") == 0) {
371 if (sptype == SPT_TRANS) { sxdlogf(" error: 'ttags' in tmode\n"); HASH_CLEAR(hh, fhash); break; } // error
372 sptype = SPT_TRANS;
373 sxdlogf("ttags; path=[%s]\n", path);
374 continue;
377 if ((sdi = get_special_by_name(condstr)) == NULL) {
378 sxdlogf("unknown special: [%s]\n", condstr);
379 HASH_CLEAR(hh, fhash);
380 break;
383 if (sdi->type != SPT_ANY && sptype != SPT_ANY) {
384 if (sdi->type != sptype) {
385 sxdlogf("bad special in mode %d: [%s]\n", sptype, condstr);
386 HASH_CLEAR(hh, fhash);
387 break;
391 sxdlogf("special: [%s] (fc=%d)\n", condstr, HASH_COUNT(fhash));
392 if (sdi->type != SPT_ANY) sptype = sdi->type;
394 continue;
396 firsttime = 0;
397 // got tag value
398 tv = get_tag_with_special(condstr, sdi);
399 if (tv == NULL) { sxdlogf(" for tag '%s': wtf?!\n", condstr); HASH_CLEAR(hh, fhash); break; } // no items, nothing can be found
400 if (files == NULL) {
401 // first time: add all files with this tag
402 sxdlogf("first time: sdi=%p; sptype=%d", sdi, sptype);
403 add_from_tv(tv, sdi, sptype);
404 } else {
405 // remove all files that have no such tag in given field
406 sxdlogf("filtering out: sdi=%p; sptype=%d", sdi, sptype);
407 remove_not_have_tv(tv, sdi, sptype);
409 sdi = NULL; // processed
411 // search complete
412 //di->fcount += 2; // "." and ".."
413 ++di->fcount; // :files
414 if (!onlyfiles) {
415 // specials
416 di->fcount += ARRAYLEN(spec_dirs);
417 if (sdi == NULL) {
418 // path ends with tag value
419 // build tag list
420 sxdlogf("sptype=%d\n", sptype);
421 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
422 for (size_t sn = 0; sn < ARRAYLEN(spec_dirs); ++sn) {
423 uint32_t idx;
424 if (sptype != SPT_ANY && spec_dirs[sn].type != SPT_ANY && spec_dirs[sn].type != sptype) continue;
425 idx = get_fi(cf->idx)->tags[spec_dirs[sn].tflidx];
426 HASH_FIND_UINT32(tl_hash, &idx, hx);
427 if (hx == NULL) {
428 hx = calloc(1, sizeof(*hx));
429 hx->idx = idx;
430 HASH_ADD_UINT32(tl_hash, idx, hx);
434 di->fcount += HASH_COUNT(tl_hash);
435 if (opt_files_only_in_files) HASH_CLEAR(hh, fhash);
436 } else {
437 // path ends with tag name: collect all from field
438 if (files == NULL) {
439 // collect all from full list
440 for (tagvalue_t *tv = tspec_hash[sdi->tflidx]; tv != NULL; tv = tv->hh.next) {
441 uint32_t idx = tv2idx(tv->tv);
442 HASH_FIND_UINT32(tl_hash, &idx, hx);
443 if (hx == NULL) {
444 hx = calloc(1, sizeof(*hx));
445 hx->idx = idx;
446 HASH_ADD_UINT32(tl_hash, idx, hx);
449 } else {
450 // collect from files
451 //sxdlogf("sptype=%d; tag '%s'; fcount=%d\n", sptype, condstr, fcount);
452 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
453 uint32_t idx = get_fi(cf->idx)->tags[sdi->tflidx];
454 HASH_FIND_UINT32(tl_hash, &idx, hx);
455 if (hx == NULL) {
456 hx = calloc(1, sizeof(*hx));
457 hx->idx = idx;
458 HASH_ADD_UINT32(tl_hash, idx, hx);
462 di->fcount += HASH_COUNT(tl_hash);
463 HASH_CLEAR(hh, fhash);
465 } else {
466 // only files
467 //di->fcount = HASH_COUNT(fhash);
470 di->fcount += HASH_COUNT(fhash);
471 di->names = calloc(di->fcount+1, sizeof(di->names[0]));
472 sxdlogf("allocated %d names (%p); pos=%d\n", di->fcount+1, di->names, pos);
473 if (!onlyfiles) {
474 // specials
475 di->names[pos++] = ":files";
476 for (size_t f = 0; f < ARRAYLEN(spec_dirs); ++f) {
477 if (sptype == SPT_ANY || spec_dirs[f].type == SPT_ANY || spec_dirs[f].type == sptype) di->names[pos++] = spec_dirs[f].name;
479 // tags
480 HASH_ITER(hh, tl_hash, hx, thx) {
481 di->names[pos++] = get_str(get_tv(hx->idx)->value);
482 HASH_DEL(tl_hash, hx);
483 free(hx);
486 // files
487 if (HASH_COUNT(fhash) > 0) {
488 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
489 if (pos >= di->fcount) { dlogf("FATAL: internal error in searcher!\n"); abort(); }
490 di->names[pos++] = get_str(get_fi(cf->idx)->shortname);
493 di->fcount = pos;
494 HASH_CLEAR(hh, fhash);
495 if (files != NULL) free(files);
496 // sort files, so mplayer and others will see sorted dir
497 ret_sort_di:
498 qsort(di->names, di->fcount, sizeof(di->names[0]), lambda(int, (const void *p0, const void *p1) {
499 const char *s0 = *((const char **)p0);
500 const char *s1 = *((const char **)p1);
501 //sxdlogf("[%s] [%s]\n", s0, s1);
502 if (s0[0] == ':' && s1[0] != ':') return -1;
503 if (s0[0] != ':' && s1[0] == ':') return 1;
504 return strcmp(s0, s1);
505 }));
506 return di;
510 static dirinfo_t *list_dir_or (char *pt) {
511 for (;;) {
512 dirinfo_t *di;
513 int stop = 0;
514 char *ta = strstr(pt, "/:or/");
515 if (ta == NULL) return list_dir_simple(pt);
516 *ta = 0;
517 sxdlogf("OR: [%s]\n", pt);
518 di = list_dir_simple(pt);
519 *ta = '/';
520 pt = strchr(ta+1, '/');
521 // check if we have something except specials
522 for (int f = 0; f < di->fcount; ++f) if (di->names[f][0] != ':') { stop = 1; break; }
523 if (stop) return di;
524 free(di->names);
525 free(di);
530 typedef struct {
531 //fl_fileinfo_t **files;
532 const char *name; // short names
533 UT_hash_handle hh;
534 } nlist_t;
537 static dirinfo_t *list_dir (const char *path) {
538 char *pt;
539 nlist_t *nhash = NULL, *nm, *nmtmp;
540 dirinfo_t *res;
542 void fill_nhash (dirinfo_t *di) {
543 if (di != NULL) {
544 for (int f = 0; f < di->fcount; ++f) {
545 nlist_t *nm;
546 HASH_FIND_STR(nhash, di->names[f], nm);
547 if (nm == NULL) {
548 nm = calloc(1, sizeof(*nm));
549 nm->name = di->names[f];
550 HASH_ADD_KEYPTR(hh, nhash, nm->name, strlen(nm->name), nm);
553 free(di->names);
554 free(di);
558 if (strstr(path, "/:and/") == NULL && strstr(path, "/:or/") == NULL) return list_dir_simple(path);
559 pt = alloca(strlen(path)+1);
560 strcpy(pt, path);
561 for (;;) {
562 char *tn;
563 sxdlogf("***a: [%s]\n", pt);
564 tn = strstr(pt, "/:and/");
565 if (tn == NULL) {
566 sxdlogf("ALAST: [%s]\n", pt);
567 fill_nhash(list_dir_or(pt));
568 break;
570 *tn = 0;
571 sxdlogf("AND: pt=[%s]; tn=[/%s]\n", pt, tn+1);
572 fill_nhash(list_dir_or(pt));
573 pt = strchr(tn+1, '/');
574 sxdlogf("xxx: [%s]\n", pt);
576 // now build result from nhash
577 HASH_SORT(nhash, lambda(int, (const nlist_t *i0, const nlist_t *i1) {
578 if (i0->name[0] == ':' && i1->name[0] != ':') return -1;
579 if (i0->name[0] != ':' && i1->name[0] == ':') return 1;
580 return strcmp(i0->name, i1->name);
581 }));
582 res = calloc(1, sizeof(*res));
583 res->names = calloc(HASH_COUNT(nhash)+1, sizeof(res->names[0]));
584 HASH_ITER(hh, nhash, nm, nmtmp) {
585 res->names[res->fcount++] = nm->name;
586 HASH_DEL(nhash, nm);
587 free(nm);
589 return res;