4 #ifdef NO_SEARCH_DBGLOG
5 # define sxdlogf(...) ((void)0)
11 ////////////////////////////////////////////////////////////////////////////////
19 static filename_t
*sname_hash
= NULL
;
20 static filename_t
*sname_data
= NULL
;
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]) {
40 HASH_FIND_STR(*hash
, str
, tv
);
41 return (tv
!= NULL
? tv
->tv
: NULL
);
47 #define XCLR(var) do { \
48 HASH_CLEAR(hh, var##_hash); \
49 if (var##_data != NULL) free(var##_data); \
54 static void clear_hashes (void) {
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
]);
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
);
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
);
87 HASH_ADD_KEYPTR(hh
, tval_hash
, t
->value
, strlen(t
->value
), t
);
89 printf("%d tags in hash\n", HASH_COUNT(tval_hash
));
92 for (int f
= 0; f
< TFL_MAX
; ++f
) {
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
) {
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
);
104 tv
= &tspec_data
[tn
][spcnt
[tn
]++];
107 HASH_ADD_KEYPTR(hh
, tspec_hash
[tn
], tv
->value
, strlen(tv
->value
), tv
);
114 ////////////////////////////////////////////////////////////////////////////////
115 #define ARRAYLEN(arr) (sizeof((arr))/sizeof((arr)[0]))
125 static const special_dir_type tagtype
[TFL_MAX
] = {
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
139 const char *name
; // with colon
140 special_dir_type type
;
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
];
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 ////////////////////////////////////////////////////////////////////////////////
176 //fl_fileinfo_t **files;
177 const char **names
; // short names
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));
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
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
;
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
216 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) cnt
+= tv
->tfl
[tn
].count
;
217 files
= malloc(sizeof(files
[0])*cnt
+1);
219 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) {
222 if (sp
!= NULL
&& tn
!= sp
->tflidx
) continue;
223 if (sp
== NULL
&& tp
!= SPT_ANY
&& tagtype
[tn
] != SPT_ANY
&& tp
!= tagtype
[tn
]) continue;
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
);
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
);
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) {
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
));
278 di
->names
[pos
++] = get_str(get_tv(taglistt_data
[f
])->value
);
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
);
299 sxdlogf("path: [%s]\n", path
);
300 // select and then refine
303 while (*path
== '/') ++path
;
306 char *e
= strchr(path
, '/');
308 strcpy(condstr
, path
);
309 path
+= strlen(path
);
311 memcpy(condstr
, path
, e
-path
);
316 sxdlogf("(fcount=%d) condstr: [%s]; sptype: %d\n", HASH_COUNT(fhash
), condstr
, sptype
);
317 if (condstr
[0] == ':') {
319 if (strcmp(condstr
, ":and") == 0 || strcmp(condstr
, ":or") == 0) {
320 if (firsttime
) goto doroot
;
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
332 files
= malloc(sizeof(files
[0])*file_count
);
333 for (uint32_t f
= 0; f
< file_count
; ++f
) {
334 u32value_t
*fl
= &files
[f
];
336 HASH_ADD_UINT32(fhash
, idx
, fl
);
342 if (strcmp(condstr
, ":otags") == 0) {
343 if (sptype
== SPT_ORIG
) { sxdlogf(" error: 'otags' in tmode\n"); HASH_CLEAR(hh
, fhash
); break; } // error
345 sxdlogf("otags; path=[%s]\n", path
);
349 if (strcmp(condstr
, ":ttags") == 0) {
350 if (sptype
== SPT_TRANS
) { sxdlogf(" error: 'ttags' in tmode\n"); HASH_CLEAR(hh
, fhash
); break; } // error
352 sxdlogf("ttags; path=[%s]\n", path
);
356 if ((sdi
= get_special_by_name(condstr
)) == NULL
) {
357 sxdlogf("unknown special: [%s]\n", condstr
);
358 HASH_CLEAR(hh
, fhash
);
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
);
370 sxdlogf("special: [%s] (fc=%d)\n", condstr
, HASH_COUNT(fhash
));
371 if (sdi
->type
!= SPT_ANY
) sptype
= sdi
->type
;
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
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
);
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
391 //di->fcount += 2; // "." and ".."
392 ++di
->fcount
; // :files
395 di
->fcount
+= ARRAYLEN(spec_dirs
);
397 // path ends with tag value
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
) {
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
);
407 hx
= calloc(1, sizeof(*hx
));
409 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
413 di
->fcount
+= HASH_COUNT(tl_hash
);
414 di
->fcount
+= HASH_COUNT(fhash
);
416 // path ends with tag name: collect all from field
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
);
423 hx
= calloc(1, sizeof(*hx
));
425 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
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
);
435 hx
= calloc(1, sizeof(*hx
));
437 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
441 di
->fcount
+= HASH_COUNT(tl_hash
);
442 HASH_CLEAR(hh
, fhash
);
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
);
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
;
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
);
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
);
469 HASH_CLEAR(hh
, fhash
);
470 if (files
!= NULL
) free(files
);
471 // sort files, so mplayer and others will see sorted dir
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
);
485 static dirinfo_t
*list_dir_or (char *pt
) {
489 char *ta
= strstr(pt
, "/:or/");
490 if (ta
== NULL
) return list_dir_simple(pt
);
492 sxdlogf("OR: [%s]\n", pt
);
493 di
= list_dir_simple(pt
);
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; }
506 //fl_fileinfo_t **files;
507 const char *name
; // short names
512 static dirinfo_t
*list_dir (const char *path
) {
514 nlist_t
*nhash
= NULL
, *nm
, *nmtmp
;
517 void fill_nhash (dirinfo_t
*di
) {
519 for (int f
= 0; f
< di
->fcount
; ++f
) {
521 HASH_FIND_STR(nhash
, di
->names
[f
], nm
);
523 nm
= calloc(1, sizeof(*nm
));
524 nm
->name
= di
->names
[f
];
525 HASH_ADD_KEYPTR(hh
, nhash
, nm
->name
, strlen(nm
->name
), nm
);
533 if (strstr(path
, "/:and/") == NULL
&& strstr(path
, "/:or/") == NULL
) return list_dir_simple(path
);
534 pt
= alloca(strlen(path
)+1);
538 sxdlogf("***a: [%s]\n", pt
);
539 tn
= strstr(pt
, "/:and/");
541 sxdlogf("ALAST: [%s]\n", pt
);
542 fill_nhash(list_dir_or(pt
));
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
);
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
;