searcher: NO_SEARCH_DBGLOG define to turn off logging
[k8muffin.git] / src / search.c
blobd1d2a78bdcd97ea1a1a6f4e9789a6baa8f34e32f
1 #include "common.h"
4 #ifdef NO_SEARCH_DBGLOG
5 # define sxdlogf(...) ((void)0)
6 #else
7 # define sxdlogf dlogf
8 #endif
11 ////////////////////////////////////////////////////////////////////////////////
12 typedef struct {
13 const char *name;
14 fl_fileinfo_t *fi;
15 UT_hash_handle hh;
16 } filename_t;
19 static filename_t *sname_hash = NULL;
20 static filename_t *sname_data = NULL;
23 typedef struct {
24 const char *value;
25 fl_tagvalue_t *tv;
26 UT_hash_handle hh;
27 } tagvalue_t;
29 // all tags
30 static tagvalue_t *tval_hash = NULL;
31 static tagvalue_t *tval_data = NULL;
33 static tagvalue_t *tspec_hash[TFL_MAX] = {NULL};
34 static tagvalue_t *tspec_data[TFL_MAX] = {NULL};
37 static fl_tagvalue_t *get_tag_by_name (const char *str, tagvalue_t **hash) {
38 if (str != NULL && str[0]) {
39 tagvalue_t *tv;
40 HASH_FIND_STR(*hash, str, tv);
41 return (tv != NULL ? tv->tv : NULL);
43 return NULL;
47 #define XCLR(var) do { \
48 HASH_CLEAR(hh, var##_hash); \
49 if (var##_data != NULL) free(var##_data); \
50 var##_data = NULL; \
51 var##_hash = NULL; \
52 } while (0)
54 static void clear_hashes (void) {
55 XCLR(sname);
56 XCLR(tval);
57 for (int f = 0; f < TFL_MAX; ++f) {
58 HASH_CLEAR(hh, tspec_hash[f]);
59 if (tspec_data[f] != NULL) free(tspec_data[f]);
60 tspec_hash[f] = NULL;
61 tspec_data[f] = NULL;
65 #undef XCLR
68 // allocate full chunks, it's faster and better than malloc()ing each item separately
69 static void build_hashes (void) {
70 sname_data = malloc(sizeof(sname_data[0])*file_count);
71 for (uint32_t f = 0; f < file_count; ++f) {
72 fl_fileinfo_t *fi = get_fi(f);
73 filename_t *fn = &sname_data[f];
74 fn->name = get_str(fi->shortname);
75 fn->fi = fi;
76 HASH_ADD_KEYPTR(hh, sname_hash, fn->name, strlen(fn->name), fn);
78 printf("%d snames in hash\n", HASH_COUNT(sname_hash));
80 tval_data = malloc(sizeof(tval_data[0])*tag_count);
81 for (uint32_t f = 0; f < tag_count; ++f) {
82 fl_tagvalue_t *tv = get_tv(f);
83 tagvalue_t *t = &tval_data[f];
84 //if (tv->value >= string_bytes) abort();
85 t->value = get_str(tv->value);
86 t->tv = tv;
87 HASH_ADD_KEYPTR(hh, tval_hash, t->value, strlen(t->value), t);
89 printf("%d tags in hash\n", HASH_COUNT(tval_hash));
91 int spcnt[TFL_MAX];
92 for (int f = 0; f < TFL_MAX; ++f) {
93 spcnt[f] = 0;
94 tspec_data[f] = malloc(sizeof(tspec_data[f][0])*tag_count); //FIXME: too much
96 for (uint32_t f = 0; f < file_count; ++f) {
97 //const fl_fileinfo_t *fi = get_fi_idx(f);
98 for (int tn = 0; tn < TFL_MAX; ++tn) {
99 tagvalue_t *tv;
100 fl_tagvalue_t *ftv = get_tv(get_fi(f)->tags[tn]);
101 const char *str = get_str(ftv->value);
102 HASH_FIND_STR(tspec_hash[tn], str, tv);
103 if (tv == NULL) {
104 tv = &tspec_data[tn][spcnt[tn]++];
105 tv->value = str;
106 tv->tv = ftv;
107 HASH_ADD_KEYPTR(hh, tspec_hash[tn], tv->value, strlen(tv->value), tv);
114 ////////////////////////////////////////////////////////////////////////////////
115 #define ARRAYLEN(arr) (sizeof((arr))/sizeof((arr)[0]))
118 typedef enum {
119 SPT_ANY,
120 SPT_ORIG,
121 SPT_TRANS
122 } special_dir_type;
125 static const special_dir_type tagtype[TFL_MAX] = {
126 SPT_ANY, // TFL_YEAR
127 SPT_ORIG, // TFL_ARTIST_O
128 SPT_TRANS, // TFL_ARTIST_T
129 SPT_ORIG, // TFL_ALBUM_O
130 SPT_TRANS, // TFL_ALBUM_T
131 SPT_ORIG, // TFL_TITLE_O
132 SPT_TRANS, // TFL_TITLE_T
133 SPT_ORIG, // TFL_GENRE_O
134 SPT_TRANS, // TFL_GENRE_T
138 typedef struct {
139 const char *name; // with colon
140 special_dir_type type;
141 int tflidx;
142 } special_dir_info_t;
145 static const special_dir_info_t spec_dirs[] = {
146 {.name=":oartist", .type=SPT_ORIG, .tflidx=TFL_ARTIST_O},
147 {.name=":oalbum", .type=SPT_ORIG, .tflidx=TFL_ALBUM_O},
148 {.name=":otitle", .type=SPT_ORIG, .tflidx=TFL_TITLE_O},
149 //{.name=":ogenre", .type=SPT_ORIG, .tflidx=TFL_GENRE_O},
150 {.name=":year", .type=SPT_ANY, .tflidx=TFL_YEAR},
151 {.name=":artist", .type=SPT_TRANS, .tflidx=TFL_ARTIST_T},
152 {.name=":album", .type=SPT_TRANS, .tflidx=TFL_ALBUM_T},
153 {.name=":title", .type=SPT_TRANS, .tflidx=TFL_TITLE_T},
154 //{.name=":genre", .type=SPT_TRANS, .tflidx=TFL_GENRE_T},
158 ////////////////////////////////////////////////////////////////////////////////
159 static inline const special_dir_info_t *get_special_by_name (const char *name) {
160 if (name != NULL && name[0] == ':') {
161 for (size_t f = 0; f < ARRAYLEN(spec_dirs); ++f) {
162 if (strcmp(spec_dirs[f].name, name) == 0) return &spec_dirs[f];
165 return NULL;
169 static inline fl_tagvalue_t *get_tag_with_special (const char *name, const special_dir_info_t *sp) {
170 return (sp != NULL ? get_tag_by_name(name, &tspec_hash[sp->tflidx]) : get_tag_by_name(name, &tval_hash));
174 ////////////////////////////////////////////////////////////////////////////////
175 typedef struct {
176 //fl_fileinfo_t **files;
177 const char **names; // short names
178 int fcount;
179 } dirinfo_t;
182 typedef struct {
183 uint32_t idx;
184 UT_hash_handle hh;
185 } u32value_t;
188 static inline int is_file_in_tfl (const fl_tfl_t *tfl, uint32_t fidx) {
189 return bsearch(&fidx, tfl_data+tfl->idx, tfl->count, sizeof(uint32_t), lambda(int, (const void *p0, const void *p1) {
190 uint32_t v0 = *((const uint32_t *)p0);
191 uint32_t v1 = *((const uint32_t *)p1);
192 return (v0 < v1 ? -1 : (v0 > v1 ? 1 : 0));
193 })) != NULL;
197 // /tag/tag/tag/
198 // /:artist/name/:album/name/
199 static dirinfo_t *list_dir_simple (const char *path) {
200 u32value_t *files = NULL; // NULL: all files in list
201 u32value_t *fhash = NULL; // to ease searching by id
202 uint32_t pos = 0;
203 int onlyfiles = 0;
204 special_dir_type sptype = SPT_ANY;
205 const special_dir_info_t *sdi = NULL;
206 char *condstr = alloca(strlen(path)+1);
207 u32value_t *tl_hash = NULL, *hx, *thx;
208 dirinfo_t *di;
209 int firsttime = 1;
211 //TODO: accelerate this with hash
212 void add_from_tv (const fl_tagvalue_t *tv, const special_dir_info_t *sp, special_dir_type tp) {
213 // just add all files
214 uint32_t cnt = 0;
215 // let it be MANY
216 for (int tn = 0; tn < TFL_MAX; ++tn) cnt += tv->tfl[tn].count;
217 files = malloc(sizeof(files[0])*cnt+1);
218 cnt = 0;
219 for (int tn = 0; tn < TFL_MAX; ++tn) {
220 const fl_tfl_t *tfl;
221 u32value_t *fl;
222 if (sp != NULL && tn != sp->tflidx) continue;
223 if (sp == NULL && tp != SPT_ANY && tagtype[tn] != SPT_ANY && tp != tagtype[tn]) continue;
224 tfl = &tv->tfl[tn];
225 for (uint32_t f = 0; f < tfl->count; ++f) {
226 uint32_t idx = tfl_data[tfl->idx+f];
227 HASH_FIND_UINT32(fhash, &idx, fl);
228 if (fl == NULL) {
229 fl = &files[cnt++];
230 fl->idx = idx;
231 HASH_ADD_UINT32(fhash, idx, fl);
237 void remove_not_have_tv (const fl_tagvalue_t *tv, const special_dir_info_t *sp, special_dir_type tp) {
238 // remove all files that have no such tag in given field
239 u32value_t *cf, *cftmp;
240 HASH_ITER(hh, fhash, cf, cftmp) {
241 const fl_fileinfo_t *fi = get_fi(cf->idx);
242 int dodrop = 1;
243 for (int tn = 0; tn < TFL_MAX; ++tn) {
244 if (sp != NULL && tn != sp->tflidx) continue;
245 if (sp == NULL && tp != SPT_ANY && tagtype[tn] != SPT_ANY && tp != tagtype[tn]) continue;
246 //sxdlogf("checking (%d)[%s]: %u : %u\n", tn, get_str(fi->shortname), fi->tags[tn], tv2idx(tv));
247 if (fi->tags[tn] == tv2idx(tv)) { dodrop = 0; break; }
249 if (dodrop) HASH_DEL(fhash, cf);
253 di = calloc(1, sizeof(*di));
255 if (strcmp(path, "/") == 0 || strcmp(path, "/:ttags") == 0 || strcmp(path, "/:otags/:ttags") == 0) {
256 doroot:
257 // wow! list ALL TAGS here!
258 di = calloc(1, sizeof(*di));
259 di->fcount = t_tag_count+ARRAYLEN(spec_dirs)+2;
260 di->names = calloc(di->fcount, sizeof(di->names[0]));
262 di->names[pos++] = ":files";
263 di->names[pos++] = ":otags";
265 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;
266 for (uint32_t f = 0; f < t_tag_count; ++f) {
267 if (get_str(get_tv(taglistt_data[f])->value)[0] == 0) {
268 printf("tags=%u; idx=%u; ssize=%u; sofs=%u\n", tag_count, taglistt_data[f], string_bytes, get_tv(taglistt_data[f])->value);
269 for (uint32_t fn = 0; fn < file_count; ++fn) {
270 for (int tn = 0; tn < TFL_MAX; ++tn) {
271 if (get_fi(fn)->tags[tn] == taglistt_data[f]) {
272 printf("tag %d of file [%s] [%s] is empty!\n", tn, get_str(get_fi(fn)->realname), get_str(get_fi(fn)->shortname));
276 abort();
278 di->names[pos++] = get_str(get_tv(taglistt_data[f])->value);
280 di->fcount = pos;
281 goto ret_sort_di;
284 if (strcmp(path, "/:otags") == 0 || strcmp(path, "/:otags/:ttags") == 0) {
285 // wow! list ALL TAGS here!
286 di = calloc(1, sizeof(*di));
287 di->fcount = o_tag_count+ARRAYLEN(spec_dirs)+2;
288 di->names = calloc(di->fcount, sizeof(di->names[0]));
290 di->names[pos++] = ":files";
291 di->names[pos++] = ":ttags";
293 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;
294 for (uint32_t f = 0; f < o_tag_count; ++f) di->names[pos++] = get_str(get_tv(taglisto_data[f])->value);
295 di->fcount = pos;
296 goto ret_sort_di;
299 sxdlogf("path: [%s]\n", path);
300 // select and then refine
301 while (*path) {
302 fl_tagvalue_t *tv;
303 while (*path == '/') ++path;
304 if (!path[0]) break;
306 char *e = strchr(path, '/');
307 if (e == NULL) {
308 strcpy(condstr, path);
309 path += strlen(path);
310 } else {
311 memcpy(condstr, path, e-path);
312 condstr[e-path] = 0;
313 path = e+1;
316 sxdlogf("(fcount=%d) condstr: [%s]; sptype: %d\n", HASH_COUNT(fhash), condstr, sptype);
317 if (condstr[0] == ':') {
318 // specific tag
319 if (strcmp(condstr, ":and") == 0 || strcmp(condstr, ":or") == 0) {
320 if (firsttime) goto doroot;
321 break;
323 firsttime = 0;
325 if (sdi != NULL) { sxdlogf("error: 2 specials\n"); HASH_CLEAR(hh, fhash); break; } // error
327 if (strcmp(condstr, ":files") == 0) {
328 // we want only files
329 onlyfiles = 1;
330 if (files == NULL) {
331 // wow, all files!
332 files = malloc(sizeof(files[0])*file_count);
333 for (uint32_t f = 0; f < file_count; ++f) {
334 u32value_t *fl = &files[f];
335 fl->idx = f;
336 HASH_ADD_UINT32(fhash, idx, fl);
339 break;
342 if (strcmp(condstr, ":otags") == 0) {
343 if (sptype == SPT_ORIG) { sxdlogf(" error: 'otags' in tmode\n"); HASH_CLEAR(hh, fhash); break; } // error
344 sptype = SPT_ORIG;
345 sxdlogf("otags; path=[%s]\n", path);
346 continue;
349 if (strcmp(condstr, ":ttags") == 0) {
350 if (sptype == SPT_TRANS) { sxdlogf(" error: 'ttags' in tmode\n"); HASH_CLEAR(hh, fhash); break; } // error
351 sptype = SPT_TRANS;
352 sxdlogf("ttags; path=[%s]\n", path);
353 continue;
356 if ((sdi = get_special_by_name(condstr)) == NULL) {
357 sxdlogf("unknown special: [%s]\n", condstr);
358 HASH_CLEAR(hh, fhash);
359 break;
362 if (sdi->type != SPT_ANY && sptype != SPT_ANY) {
363 if (sdi->type != sptype) {
364 sxdlogf("bad special in mode %d: [%s]\n", sptype, condstr);
365 HASH_CLEAR(hh, fhash);
366 break;
370 sxdlogf("special: [%s] (fc=%d)\n", condstr, HASH_COUNT(fhash));
371 if (sdi->type != SPT_ANY) sptype = sdi->type;
373 continue;
375 firsttime = 0;
376 // got tag value
377 tv = get_tag_with_special(condstr, sdi);
378 if (tv == NULL) { sxdlogf(" for tag '%s': wtf?!\n", condstr); HASH_CLEAR(hh, fhash); break; } // no items, nothing can be found
379 if (files == NULL) {
380 // first time: add all files with this tag
381 sxdlogf("first time: sdi=%p; sptype=%d", sdi, sptype);
382 add_from_tv(tv, sdi, sptype);
383 } else {
384 // remove all files that have no such tag in given field
385 sxdlogf("filtering out: sdi=%p; sptype=%d", sdi, sptype);
386 remove_not_have_tv(tv, sdi, sptype);
388 sdi = NULL; // processed
390 // search complete
391 //di->fcount += 2; // "." and ".."
392 ++di->fcount; // :files
393 if (!onlyfiles) {
394 // specials
395 di->fcount += ARRAYLEN(spec_dirs);
396 if (sdi == NULL) {
397 // path ends with tag value
398 // build tag list
399 sxdlogf("sptype=%d\n", sptype);
400 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
401 for (size_t sn = 0; sn < ARRAYLEN(spec_dirs); ++sn) {
402 uint32_t idx;
403 if (sptype != SPT_ANY && spec_dirs[sn].type != SPT_ANY && spec_dirs[sn].type != sptype) continue;
404 idx = get_fi(cf->idx)->tags[spec_dirs[sn].tflidx];
405 HASH_FIND_UINT32(tl_hash, &idx, hx);
406 if (hx == NULL) {
407 hx = calloc(1, sizeof(*hx));
408 hx->idx = idx;
409 HASH_ADD_UINT32(tl_hash, idx, hx);
413 di->fcount += HASH_COUNT(tl_hash);
414 di->fcount += HASH_COUNT(fhash);
415 } else {
416 // path ends with tag name: collect all from field
417 if (files == NULL) {
418 // collect all from full list
419 for (tagvalue_t *tv = tspec_hash[sdi->tflidx]; tv != NULL; tv = tv->hh.next) {
420 uint32_t idx = tv2idx(tv->tv);
421 HASH_FIND_UINT32(tl_hash, &idx, hx);
422 if (hx == NULL) {
423 hx = calloc(1, sizeof(*hx));
424 hx->idx = idx;
425 HASH_ADD_UINT32(tl_hash, idx, hx);
428 } else {
429 // collect from files
430 //sxdlogf("sptype=%d; tag '%s'; fcount=%d\n", sptype, condstr, fcount);
431 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) {
432 uint32_t idx = get_fi(cf->idx)->tags[sdi->tflidx];
433 HASH_FIND_UINT32(tl_hash, &idx, hx);
434 if (hx == NULL) {
435 hx = calloc(1, sizeof(*hx));
436 hx->idx = idx;
437 HASH_ADD_UINT32(tl_hash, idx, hx);
441 di->fcount += HASH_COUNT(tl_hash);
442 HASH_CLEAR(hh, fhash);
444 } else {
445 // only files
446 di->fcount = HASH_COUNT(fhash);
449 di->names = calloc(di->fcount+1, sizeof(di->names[0]));
450 sxdlogf("allocated %d names (%p); pos=%d\n", di->fcount+1, di->names, pos);
451 if (!onlyfiles) {
452 // specials
453 di->names[pos++] = ":files";
454 for (size_t f = 0; f < ARRAYLEN(spec_dirs); ++f) {
455 if (sptype == SPT_ANY || spec_dirs[f].type == SPT_ANY || spec_dirs[f].type == sptype) di->names[pos++] = spec_dirs[f].name;
457 // tags
458 HASH_ITER(hh, tl_hash, hx, thx) {
459 di->names[pos++] = get_str(get_tv(hx->idx)->value);
460 HASH_DEL(tl_hash, hx);
461 free(hx);
464 // files
465 if (HASH_COUNT(fhash) > 0) {
466 for (u32value_t *cf = fhash; cf != NULL; cf = cf->hh.next) di->names[pos++] = get_str(get_fi(cf->idx)->shortname);
468 di->fcount = pos;
469 HASH_CLEAR(hh, fhash);
470 if (files != NULL) free(files);
471 // sort files, so mplayer and others will see sorted dir
472 ret_sort_di:
473 qsort(di->names, di->fcount, sizeof(di->names[0]), lambda(int, (const void *p0, const void *p1) {
474 const char *s0 = *((const char **)p0);
475 const char *s1 = *((const char **)p1);
476 //sxdlogf("[%s] [%s]\n", s0, s1);
477 if (s0[0] == ':' && s1[0] != ':') return -1;
478 if (s0[0] != ':' && s1[0] == ':') return 1;
479 return strcmp(s0, s1);
480 }));
481 return di;
485 static dirinfo_t *list_dir_or (char *pt) {
486 for (;;) {
487 dirinfo_t *di;
488 int stop = 0;
489 char *ta = strstr(pt, "/:or/");
490 if (ta == NULL) return list_dir_simple(pt);
491 *ta = 0;
492 sxdlogf("OR: [%s]\n", pt);
493 di = list_dir_simple(pt);
494 *ta = '/';
495 pt = strchr(ta+1, '/');
496 // check if we have something except specials
497 for (int f = 0; f < di->fcount; ++f) if (di->names[f][0] != ':') { stop = 1; break; }
498 if (stop) return di;
499 free(di->names);
500 free(di);
505 typedef struct {
506 //fl_fileinfo_t **files;
507 const char *name; // short names
508 UT_hash_handle hh;
509 } nlist_t;
512 static dirinfo_t *list_dir (const char *path) {
513 char *pt;
514 nlist_t *nhash = NULL, *nm, *nmtmp;
515 dirinfo_t *res;
517 void fill_nhash (dirinfo_t *di) {
518 if (di != NULL) {
519 for (int f = 0; f < di->fcount; ++f) {
520 nlist_t *nm;
521 HASH_FIND_STR(nhash, di->names[f], nm);
522 if (nm == NULL) {
523 nm = calloc(1, sizeof(*nm));
524 nm->name = di->names[f];
525 HASH_ADD_KEYPTR(hh, nhash, nm->name, strlen(nm->name), nm);
528 free(di->names);
529 free(di);
533 if (strstr(path, "/:and/") == NULL && strstr(path, "/:or/") == NULL) return list_dir_simple(path);
534 pt = alloca(strlen(path)+1);
535 strcpy(pt, path);
536 for (;;) {
537 char *tn;
538 sxdlogf("***a: [%s]\n", pt);
539 tn = strstr(pt, "/:and/");
540 if (tn == NULL) {
541 sxdlogf("ALAST: [%s]\n", pt);
542 fill_nhash(list_dir_or(pt));
543 break;
545 *tn = 0;
546 sxdlogf("AND: pt=[%s]; tn=[/%s]\n", pt, tn+1);
547 fill_nhash(list_dir_or(pt));
548 pt = strchr(tn+1, '/');
549 sxdlogf("xxx: [%s]\n", pt);
551 // now build result from nhash
552 HASH_SORT(nhash, lambda(int, (const nlist_t *i0, const nlist_t *i1) {
553 if (i0->name[0] == ':' && i1->name[0] != ':') return -1;
554 if (i0->name[0] != ':' && i1->name[0] == ':') return 1;
555 return strcmp(i0->name, i1->name);
556 }));
557 res = calloc(1, sizeof(*res));
558 res->names = calloc(HASH_COUNT(nhash)+1, sizeof(res->names[0]));
559 HASH_ITER(hh, nhash, nm, nmtmp) {
560 res->names[res->fcount++] = nm->name;
561 HASH_DEL(nhash, nm);
562 free(nm);
564 return res;