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/>.
20 ////////////////////////////////////////////////////////////////////////////////
21 #ifdef NO_SEARCH_DBGLOG
22 # define sxdlogf(...) ((void)0)
24 # define sxdlogf dlogf
28 ////////////////////////////////////////////////////////////////////////////////
29 static int opt_files_only_in_files
= 1;
32 ////////////////////////////////////////////////////////////////////////////////
40 static filename_t
*sname_hash
= NULL
;
41 static filename_t
*sname_data
= NULL
;
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]) {
61 HASH_FIND_STR(*hash
, str
, tv
);
62 return (tv
!= NULL
? tv
->tv
: NULL
);
68 #define XCLR(var) do { \
69 HASH_CLEAR(hh, var##_hash); \
70 if (var##_data != NULL) free(var##_data); \
75 static void clear_hashes (void) {
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
]);
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
);
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
);
108 HASH_ADD_KEYPTR(hh
, tval_hash
, t
->value
, strlen(t
->value
), t
);
110 printf("%d tags in hash\n", HASH_COUNT(tval_hash
));
113 for (int f
= 0; f
< TFL_MAX
; ++f
) {
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
) {
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
);
125 tv
= &tspec_data
[tn
][spcnt
[tn
]++];
128 HASH_ADD_KEYPTR(hh
, tspec_hash
[tn
], tv
->value
, strlen(tv
->value
), tv
);
135 ////////////////////////////////////////////////////////////////////////////////
136 #define ARRAYLEN(arr) (sizeof((arr))/sizeof((arr)[0]))
146 static const special_dir_type tagtype
[TFL_MAX
] = {
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
160 const char *name
; // with colon
161 special_dir_type type
;
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
];
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 ////////////////////////////////////////////////////////////////////////////////
197 //fl_fileinfo_t **files;
198 const char **names
; // short names
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));
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
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
;
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
237 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) cnt
+= tv
->tfl
[tn
].count
;
238 files
= malloc(sizeof(files
[0])*cnt
+1);
240 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) {
243 if (sp
!= NULL
&& tn
!= sp
->tflidx
) continue;
244 if (sp
== NULL
&& tp
!= SPT_ANY
&& tagtype
[tn
] != SPT_ANY
&& tp
!= tagtype
[tn
]) continue;
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
);
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
);
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) {
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
));
299 di
->names
[pos
++] = get_str(get_tv(taglistt_data
[f
])->value
);
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
);
320 sxdlogf("path: [%s]\n", path
);
321 // select and then refine
324 while (*path
== '/') ++path
;
327 char *e
= strchr(path
, '/');
329 strcpy(condstr
, path
);
330 path
+= strlen(path
);
332 memcpy(condstr
, path
, e
-path
);
337 sxdlogf("(fcount=%d) condstr: [%s]; sptype: %d\n", HASH_COUNT(fhash
), condstr
, sptype
);
338 if (condstr
[0] == ':') {
340 if (strcmp(condstr
, ":and") == 0 || strcmp(condstr
, ":or") == 0) {
341 if (firsttime
) goto doroot
;
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
353 files
= malloc(sizeof(files
[0])*file_count
);
354 for (uint32_t f
= 0; f
< file_count
; ++f
) {
355 u32value_t
*fl
= &files
[f
];
357 HASH_ADD_UINT32(fhash
, idx
, fl
);
363 if (strcmp(condstr
, ":otags") == 0) {
364 if (sptype
== SPT_ORIG
) { sxdlogf(" error: 'otags' in tmode\n"); HASH_CLEAR(hh
, fhash
); break; } // error
366 sxdlogf("otags; path=[%s]\n", path
);
370 if (strcmp(condstr
, ":ttags") == 0) {
371 if (sptype
== SPT_TRANS
) { sxdlogf(" error: 'ttags' in tmode\n"); HASH_CLEAR(hh
, fhash
); break; } // error
373 sxdlogf("ttags; path=[%s]\n", path
);
377 if ((sdi
= get_special_by_name(condstr
)) == NULL
) {
378 sxdlogf("unknown special: [%s]\n", condstr
);
379 HASH_CLEAR(hh
, fhash
);
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
);
391 sxdlogf("special: [%s] (fc=%d)\n", condstr
, HASH_COUNT(fhash
));
392 if (sdi
->type
!= SPT_ANY
) sptype
= sdi
->type
;
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
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
);
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
412 //di->fcount += 2; // "." and ".."
413 ++di
->fcount
; // :files
416 di
->fcount
+= ARRAYLEN(spec_dirs
);
418 // path ends with tag value
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
) {
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
);
428 hx
= calloc(1, sizeof(*hx
));
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
);
437 // path ends with tag name: collect all from field
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
);
444 hx
= calloc(1, sizeof(*hx
));
446 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
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
);
456 hx
= calloc(1, sizeof(*hx
));
458 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
462 di
->fcount
+= HASH_COUNT(tl_hash
);
463 HASH_CLEAR(hh
, fhash
);
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
);
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
;
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
);
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
);
494 HASH_CLEAR(hh
, fhash
);
495 if (files
!= NULL
) free(files
);
496 // sort files, so mplayer and others will see sorted dir
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
);
510 static dirinfo_t
*list_dir_or (char *pt
) {
514 char *ta
= strstr(pt
, "/:or/");
515 if (ta
== NULL
) return list_dir_simple(pt
);
517 sxdlogf("OR: [%s]\n", pt
);
518 di
= list_dir_simple(pt
);
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; }
531 //fl_fileinfo_t **files;
532 const char *name
; // short names
537 static dirinfo_t
*list_dir (const char *path
) {
539 nlist_t
*nhash
= NULL
, *nm
, *nmtmp
;
542 void fill_nhash (dirinfo_t
*di
) {
544 for (int f
= 0; f
< di
->fcount
; ++f
) {
546 HASH_FIND_STR(nhash
, di
->names
[f
], nm
);
548 nm
= calloc(1, sizeof(*nm
));
549 nm
->name
= di
->names
[f
];
550 HASH_ADD_KEYPTR(hh
, nhash
, nm
->name
, strlen(nm
->name
), nm
);
558 if (strstr(path
, "/:and/") == NULL
&& strstr(path
, "/:or/") == NULL
) return list_dir_simple(path
);
559 pt
= alloca(strlen(path
)+1);
563 sxdlogf("***a: [%s]\n", pt
);
564 tn
= strstr(pt
, "/:and/");
566 sxdlogf("ALAST: [%s]\n", pt
);
567 fill_nhash(list_dir_or(pt
));
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
);
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
;