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 ////////////////////////////////////////////////////////////////////////////////
196 #define FIDX_NONE (0)
199 const char *name
; // short names
200 uint32_t fidx
; // file index+1 (if any) or FIDX_NONE
215 static inline int zdifsign (uint32_t n0
, uint32_t n1
) {
216 return (n0
< n1
? -1 : (n0
> n1
? 1 : 0));
221 // /:artist/name/:album/name/
222 static dirinfo_t
*list_dir_simple (const char *path
) {
223 u32value_t
*files
= NULL
; // NULL: all files in list
224 u32value_t
*fhash
= NULL
; // to ease searching by id
227 special_dir_type sptype
= SPT_ANY
;
228 const special_dir_info_t
*sdi
= NULL
;
229 char *condstr
= alloca(strlen(path
)+1);
230 u32value_t
*tl_hash
= NULL
, *hx
, *thx
;
234 //TODO: accelerate this with hash
235 void add_from_tv (const fl_tagvalue_t
*tv
, const special_dir_info_t
*sp
, special_dir_type tp
) {
236 // just add all files
238 //sxdlogf("add_from_tv: [%s]; sp=%p; tp=%d\n", get_str(tv->value), sp, tp);
240 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) {
241 //sxdlogf(" tn=%d; count=%u\n", tn, tv->tfl[tn].count);
242 cnt
+= tv
->tfl
[tn
].count
;
244 files
= malloc(sizeof(files
[0])*cnt
+1);
246 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) {
249 if (sp
!= NULL
&& tn
!= sp
->tflidx
) continue;
250 if (sp
== NULL
&& tp
!= SPT_ANY
&& tagtype
[tn
] != SPT_ANY
&& tp
!= tagtype
[tn
]) continue;
252 //sxdlogf(" *:tn=%d; count=%u\n", tn, tfl->count);
253 for (uint32_t f
= 0; f
< tfl
->count
; ++f
) {
254 uint32_t idx
= tfl_data
[tfl
->idx
+f
];
255 //sxdlogf(" %u: idx=%u\n", f, tfl_data[tfl->idx+f]);
256 HASH_FIND_UINT32(fhash
, &idx
, fl
);
260 HASH_ADD_UINT32(fhash
, idx
, fl
);
266 void remove_not_have_tv (const fl_tagvalue_t
*tv
, const special_dir_info_t
*sp
, special_dir_type tp
) {
267 // remove all files that have no such tag in given field
268 u32value_t
*cf
, *cftmp
;
269 HASH_ITER(hh
, fhash
, cf
, cftmp
) {
270 const fl_fileinfo_t
*fi
= get_fi(cf
->idx
);
272 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) {
273 if (sp
!= NULL
&& tn
!= sp
->tflidx
) continue;
274 if (sp
== NULL
&& tp
!= SPT_ANY
&& tagtype
[tn
] != SPT_ANY
&& tp
!= tagtype
[tn
]) continue;
275 //sxdlogf("checking (%d)[%s]: %u : %u\n", tn, get_str(fi->shortname), fi->tags[tn], tv2idx(tv));
276 if (fi
->tags
[tn
] == tv2idx(tv
)) { dodrop
= 0; break; }
278 if (dodrop
) HASH_DEL(fhash
, cf
);
282 di
= calloc(1, sizeof(*di
));
284 if (strcmp(path
, "/") == 0 || strcmp(path
, "/:ttags") == 0 || strcmp(path
, "/:otags/:ttags") == 0) {
286 // wow! list ALL TAGS here!
287 di
= calloc(1, sizeof(*di
));
288 di
->fcount
= t_tag_count
+ARRAYLEN(spec_dirs
)+2;
289 di
->items
= calloc(di
->fcount
, sizeof(di
->items
[0]));
291 di
->items
[pos
++].name
= ":files";
292 di
->items
[pos
++].name
= ":otags";
294 for (int f
= 0; f
< ARRAYLEN(spec_dirs
); ++f
) if (spec_dirs
[f
].type
== SPT_TRANS
|| spec_dirs
[f
].type
== SPT_ANY
) {
295 di
->items
[pos
++].name
= spec_dirs
[f
].name
;
297 for (uint32_t f
= 0; f
< t_tag_count
; ++f
) {
298 if (get_str(get_tv(taglistt_data
[f
])->value
)[0] == 0) {
299 printf("tags=%u; idx=%u; ssize=%u; sofs=%u\n", tag_count
, taglistt_data
[f
], string_bytes
, get_tv(taglistt_data
[f
])->value
);
300 for (uint32_t fn
= 0; fn
< file_count
; ++fn
) {
301 for (int tn
= 0; tn
< TFL_MAX
; ++tn
) {
302 if (get_fi(fn
)->tags
[tn
] == taglistt_data
[f
]) {
303 printf("tag %d of file [%s] [%s] is empty!\n", tn
, get_str(get_fi(fn
)->realname
), get_str(get_fi(fn
)->shortname
));
309 di
->items
[pos
++].name
= get_str(get_tv(taglistt_data
[f
])->value
);
315 if (strcmp(path
, "/:otags") == 0 || strcmp(path
, "/:otags/:ttags") == 0) {
316 // wow! list ALL TAGS here!
317 di
= calloc(1, sizeof(*di
));
318 di
->fcount
= o_tag_count
+ARRAYLEN(spec_dirs
)+2;
319 di
->items
= calloc(di
->fcount
, sizeof(di
->items
[0]));
321 di
->items
[pos
++].name
= ":files";
322 di
->items
[pos
++].name
= ":ttags";
324 for (int f
= 0; f
< ARRAYLEN(spec_dirs
); ++f
) if (spec_dirs
[f
].type
== SPT_ORIG
|| spec_dirs
[f
].type
== SPT_ANY
) {
325 di
->items
[pos
++].name
= spec_dirs
[f
].name
;
327 for (uint32_t f
= 0; f
< o_tag_count
; ++f
) di
->items
[pos
++].name
= get_str(get_tv(taglisto_data
[f
])->value
);
332 sxdlogf("path: [%s]\n", path
);
333 // select and then refine
336 while (*path
== '/') ++path
;
339 char *e
= strchr(path
, '/');
341 strcpy(condstr
, path
);
342 path
+= strlen(path
);
344 memcpy(condstr
, path
, e
-path
);
349 sxdlogf("(fcount=%d) condstr: [%s]; sptype: %d\n", HASH_COUNT(fhash
), condstr
, sptype
);
350 if (condstr
[0] == ':') {
352 if (strcmp(condstr
, ":and") == 0 || strcmp(condstr
, ":or") == 0) {
353 if (firsttime
) goto doroot
;
358 if (sdi
!= NULL
) { sxdlogf("error: 2 specials\n"); HASH_CLEAR(hh
, fhash
); break; } // error
360 if (strcmp(condstr
, ":files") == 0) {
361 // we want only files
365 files
= malloc(sizeof(files
[0])*file_count
);
366 for (uint32_t f
= 0; f
< file_count
; ++f
) {
367 u32value_t
*fl
= &files
[f
];
369 HASH_ADD_UINT32(fhash
, idx
, fl
);
375 if (strcmp(condstr
, ":otags") == 0) {
376 if (sptype
== SPT_ORIG
) { sxdlogf(" error: 'otags' in tmode\n"); HASH_CLEAR(hh
, fhash
); break; } // error
378 sxdlogf("otags; path=[%s]\n", path
);
382 if (strcmp(condstr
, ":ttags") == 0) {
383 if (sptype
== SPT_TRANS
) { sxdlogf(" error: 'ttags' in tmode\n"); HASH_CLEAR(hh
, fhash
); break; } // error
385 sxdlogf("ttags; path=[%s]\n", path
);
389 if ((sdi
= get_special_by_name(condstr
)) == NULL
) {
390 sxdlogf("unknown special: [%s]\n", condstr
);
391 HASH_CLEAR(hh
, fhash
);
395 if (sdi
->type
!= SPT_ANY
&& sptype
!= SPT_ANY
) {
396 if (sdi
->type
!= sptype
) {
397 sxdlogf("bad special in mode %d: [%s]\n", sptype
, condstr
);
398 HASH_CLEAR(hh
, fhash
);
403 sxdlogf("special: [%s] (fc=%d)\n", condstr
, HASH_COUNT(fhash
));
404 if (sdi
->type
!= SPT_ANY
) sptype
= sdi
->type
;
410 tv
= get_tag_with_special(condstr
, sdi
);
411 if (tv
== NULL
) { sxdlogf(" for tag '%s': wtf?!\n", condstr
); HASH_CLEAR(hh
, fhash
); break; } // no items, nothing can be found
413 // first time: add all files with this tag
414 sxdlogf("first time: sdi=%p; sptype=%d", sdi
, sptype
);
415 add_from_tv(tv
, sdi
, sptype
);
417 // remove all files that have no such tag in given field
418 sxdlogf("filtering out: sdi=%p; sptype=%d", sdi
, sptype
);
419 remove_not_have_tv(tv
, sdi
, sptype
);
421 sdi
= NULL
; // processed
424 //di->fcount += 2; // "." and ".."
425 ++di
->fcount
; // :files
428 di
->fcount
+= ARRAYLEN(spec_dirs
);
430 // path ends with tag value
432 sxdlogf("sptype=%d\n", sptype
);
433 for (u32value_t
*cf
= fhash
; cf
!= NULL
; cf
= cf
->hh
.next
) {
434 for (size_t sn
= 0; sn
< ARRAYLEN(spec_dirs
); ++sn
) {
436 if (sptype
!= SPT_ANY
&& spec_dirs
[sn
].type
!= SPT_ANY
&& spec_dirs
[sn
].type
!= sptype
) continue;
437 idx
= get_fi(cf
->idx
)->tags
[spec_dirs
[sn
].tflidx
];
438 HASH_FIND_UINT32(tl_hash
, &idx
, hx
);
440 hx
= calloc(1, sizeof(*hx
));
442 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
446 di
->fcount
+= HASH_COUNT(tl_hash
);
447 if (opt_files_only_in_files
) HASH_CLEAR(hh
, fhash
);
449 // path ends with tag name: collect all from field
451 // collect all from full list
452 for (tagvalue_t
*tv
= tspec_hash
[sdi
->tflidx
]; tv
!= NULL
; tv
= tv
->hh
.next
) {
453 uint32_t idx
= tv2idx(tv
->tv
);
454 HASH_FIND_UINT32(tl_hash
, &idx
, hx
);
456 hx
= calloc(1, sizeof(*hx
));
458 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
462 // collect from files
463 //sxdlogf("sptype=%d; tag '%s'; fcount=%d\n", sptype, condstr, fcount);
464 for (u32value_t
*cf
= fhash
; cf
!= NULL
; cf
= cf
->hh
.next
) {
465 uint32_t idx
= get_fi(cf
->idx
)->tags
[sdi
->tflidx
];
466 HASH_FIND_UINT32(tl_hash
, &idx
, hx
);
468 hx
= calloc(1, sizeof(*hx
));
470 HASH_ADD_UINT32(tl_hash
, idx
, hx
);
474 di
->fcount
+= HASH_COUNT(tl_hash
);
475 HASH_CLEAR(hh
, fhash
);
479 //di->fcount = HASH_COUNT(fhash);
482 di
->fcount
+= HASH_COUNT(fhash
);
483 di
->items
= calloc(di
->fcount
+1, sizeof(di
->items
[0]));
484 sxdlogf("allocated %d items; pos=%d\n", di
->fcount
+1, pos
);
487 di
->items
[pos
++].name
= ":files";
488 for (size_t f
= 0; f
< ARRAYLEN(spec_dirs
); ++f
) {
489 if (sptype
== SPT_ANY
|| spec_dirs
[f
].type
== SPT_ANY
|| spec_dirs
[f
].type
== sptype
) {
490 di
->items
[pos
++].name
= spec_dirs
[f
].name
;
494 HASH_ITER(hh
, tl_hash
, hx
, thx
) {
495 di
->items
[pos
++].name
= get_str(get_tv(hx
->idx
)->value
);
496 HASH_DEL(tl_hash
, hx
);
501 if (HASH_COUNT(fhash
) > 0) {
502 for (u32value_t
*cf
= fhash
; cf
!= NULL
; cf
= cf
->hh
.next
) {
503 if (pos
>= di
->fcount
) { dlogf("FATAL: internal error in searcher!\n"); abort(); }
504 di
->items
[pos
].fidx
= cf
->idx
+1; //NOTE +1!
505 di
->items
[pos
++].name
= get_str(get_fi(cf
->idx
)->shortname
);
509 HASH_CLEAR(hh
, fhash
);
510 if (files
!= NULL
) free(files
);
511 // sort files, so mplayer and others will see sorted dir
513 qsort(di
->items
, di
->fcount
, sizeof(di
->items
[0]), lambda(int, (const void *p0
, const void *p1
) {
514 const diritem_t
*i0
= (const diritem_t
*)p0
;
515 const diritem_t
*i1
= (const diritem_t
*)p1
;
517 const fl_fileinfo_t
*f0
;
518 const fl_fileinfo_t
*f1
;
519 //sxdlogf("[%s] [%s]\n", s0, s1);
520 if (i0
->name
[0] == ':' && i1
->name
[0] != ':') return -1;
521 if (i0
->name
[0] != ':' && i1
->name
[0] == ':') return 1;
522 if (i0
->fidx
== 0) return (i1
->fidx
!= 0 ? 1 : strcmp(i0
->name
, i1
->name
));
523 if (i1
->fidx
== 0) return (i0
->fidx
!= 0 ? -1 : strcmp(i0
->name
, i1
->name
));
525 f0
= get_fi(i0
->fidx
-1);
526 f1
= get_fi(i1
->fidx
-1);
528 if (f0
->year
!= f1
->year
&& (f0
->year
!= 0 || f1
->year
!= 0)) {
529 sgn
= zdifsign(f0
->year
, f1
->year
);
533 sgn
= zdifsign(f0
->artist_t
, f1
->artist_t
);
536 sgn
= zdifsign(f0
->album_t
, f1
->album_t
);
539 sgn
= zdifsign(f0
->tracknum
, f1
->tracknum
);
542 return strcmp(i0
->name
, i1
->name
);
548 static dirinfo_t
*list_dir_or (char *pt
) {
552 char *ta
= strstr(pt
, "/:or/");
553 if (ta
== NULL
) return list_dir_simple(pt
);
555 sxdlogf("OR: [%s]\n", pt
);
556 di
= list_dir_simple(pt
);
558 pt
= strchr(ta
+1, '/');
559 // check if we have something except specials
560 for (int f
= 0; f
< di
->fcount
; ++f
) if (di
->items
[f
].name
[0] != ':') { stop
= 1; break; }
569 //fl_fileinfo_t **files;
570 const char *name
; // short names
575 static dirinfo_t
*list_dir (const char *path
) {
577 nlist_t
*nhash
= NULL
, *nm
, *nmtmp
;
580 void fill_nhash (dirinfo_t
*di
) {
582 for (int f
= 0; f
< di
->fcount
; ++f
) {
584 HASH_FIND_STR(nhash
, di
->items
[f
].name
, nm
);
586 nm
= calloc(1, sizeof(*nm
));
587 nm
->name
= di
->items
[f
].name
;
588 HASH_ADD_KEYPTR(hh
, nhash
, nm
->name
, strlen(nm
->name
), nm
);
596 if (strstr(path
, "/:and/") == NULL
&& strstr(path
, "/:or/") == NULL
) return list_dir_simple(path
);
597 pt
= alloca(strlen(path
)+1);
601 sxdlogf("***a: [%s]\n", pt
);
602 tn
= strstr(pt
, "/:and/");
604 sxdlogf("ALAST: [%s]\n", pt
);
605 fill_nhash(list_dir_or(pt
));
609 sxdlogf("AND: pt=[%s]; tn=[/%s]\n", pt
, tn
+1);
610 fill_nhash(list_dir_or(pt
));
611 pt
= strchr(tn
+1, '/');
612 sxdlogf("xxx: [%s]\n", pt
);
614 // now build result from nhash
615 HASH_SORT(nhash
, lambda(int, (const nlist_t
*i0
, const nlist_t
*i1
) {
616 if (i0
->name
[0] == ':' && i1
->name
[0] != ':') return -1;
617 if (i0
->name
[0] != ':' && i1
->name
[0] == ':') return 1;
618 return strcmp(i0
->name
, i1
->name
);
620 res
= calloc(1, sizeof(*res
));
621 res
->items
= calloc(HASH_COUNT(nhash
)+1, sizeof(res
->items
[0]));
622 HASH_ITER(hh
, nhash
, nm
, nmtmp
) {
623 res
->items
[res
->fcount
++].name
= nm
->name
;