A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005 by Miika Pekkarinen
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/**
23 * Basic structure on this file was copied from dbtree.c and modified to
24 * support the tag cache interface.
25 */
26
27//#define LOGF_ENABLE
28
29#include <stdio.h>
30#include <stdlib.h>
31#include "string-extra.h"
32#include "config.h"
33#include "system.h"
34#include "kernel.h"
35#include "splash.h"
36#include "icons.h"
37#include "tree.h"
38#include "action.h"
39#include "settings.h"
40#include "tagcache.h"
41#include "tagtree.h"
42#include "lang.h"
43#include "logf.h"
44#include "talk.h"
45#include "playlist.h"
46#include "keyboard.h"
47#include "gui/list.h"
48#include "core_alloc.h"
49#include "yesno.h"
50#include "misc.h"
51#include "filetypes.h"
52#include "audio.h"
53#include "appevents.h"
54#include "storage.h"
55#include "dir.h"
56#include "playback.h"
57#include "strnatcmp.h"
58#include "panic.h"
59#include "onplay.h"
60#include "plugin.h"
61#include "language.h"
62#include "playlist_catalog.h"
63
64#define str_or_empty(x) (x ? x : "(NULL)")
65
66#define TAGNAVI_DEFAULT_CONFIG ROCKBOX_DIR "/tagnavi.config"
67#define TAGNAVI_USER_CONFIG ROCKBOX_DIR "/tagnavi_user.config"
68
69static int tagtree_play_folder(struct tree_context* c);
70
71/* reuse of tagtree data after tagtree_play_folder() */
72static uint32_t loaded_entries_crc = 0;
73
74
75/* this needs to be same size as struct entry (tree.h) and name needs to be
76 * the first; so that they're compatible enough to walk arrays of both
77 * derefencing the name member*/
78struct tagentry {
79 char* name;
80 int newtable;
81 int extraseek;
82 int customaction;
83 char* album_name;
84};
85
86static struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
87
88#define SEARCHSTR_SIZE 256
89
90enum table {
91 TABLE_ROOT = 1,
92 TABLE_NAVIBROWSE,
93 TABLE_ALLSUBENTRIES,
94 TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS,
95 TABLE_PLAYTRACK,
96};
97
98static const struct id3_to_search_mapping {
99 char *string;
100 size_t id3_offset;
101} id3_to_search_mapping[] = {
102 { "", 0 }, /* offset n/a */
103 { "#directory#", 0 }, /* offset n/a */
104 { "#title#", offsetof(struct mp3entry, title) },
105 { "#artist#", offsetof(struct mp3entry, artist) },
106 { "#album#", offsetof(struct mp3entry, album) },
107 { "#genre#", offsetof(struct mp3entry, genre_string) },
108 { "#composer#", offsetof(struct mp3entry, composer) },
109 { "#albumartist#", offsetof(struct mp3entry, albumartist) },
110};
111enum variables {
112 var_sorttype = 100,
113 var_limit,
114 var_strip,
115 var_menu_start,
116 var_include,
117 var_rootmenu,
118 var_format,
119 menu_byfirstletter,
120 menu_next,
121 menu_load,
122 menu_reload,
123 menu_shuffle_songs,
124};
125
126/* Capacity 10 000 entries (for example 10k different artists) */
127#define UNIQBUF_SIZE (64*1024)
128static uint32_t uniqbuf[UNIQBUF_SIZE / sizeof(uint32_t)];
129
130#define MAX_TAGS 5
131#define MAX_MENU_ID_SIZE 32
132
133#define RELOAD_TAGTREE (-1024)
134
135static int(*qsort_fn)(const char*, const char*, size_t);
136/* dummmy functions to allow compatibility strncasecmp */
137static int strnatcasecmp_n(const char *a, const char *b, size_t n)
138{
139 (void)n;
140 return strnatcasecmp(a, b);
141}
142static int strnatcasecmp_n_inv(const char *a, const char *b, size_t n)
143{
144 (void)n;
145 return strnatcasecmp(b, a);
146}
147static int strncasecmp_inv(const char *a, const char *b, size_t n)
148{
149 return strncasecmp(b, a, n);
150}
151
152/*
153 * "%3d. %s" autoscore title %sort = "inverse" %limit = "100"
154 *
155 * valid = true
156 * formatstr = "%-3d. %s"
157 * tags[0] = tag_autoscore
158 * tags[1] = tag_title
159 * tag_count = 2
160 *
161 * limit = 100
162 * sort_inverse = true
163 */
164struct display_format {
165 char name[32];
166 struct tagcache_search_clause *clause[TAGCACHE_MAX_CLAUSES];
167 int clause_count;
168 char *formatstr;
169 int group_id;
170 int tags[MAX_TAGS];
171 int tag_count;
172
173 int limit;
174 int strip;
175 bool sort_inverse;
176};
177
178static struct display_format *formats[TAGMENU_MAX_FMTS];
179static int format_count;
180
181#define MENUENTRY_MAX_NAME 64
182struct menu_entry {
183 char _name[MENUENTRY_MAX_NAME];
184 const unsigned char *name;
185 int type;
186 struct search_instruction {
187 char name[MENUENTRY_MAX_NAME];
188 int tagorder[MAX_TAGS];
189 int tagorder_count;
190 struct tagcache_search_clause *clause[MAX_TAGS][TAGCACHE_MAX_CLAUSES];
191 int format_id[MAX_TAGS];
192 int clause_count[MAX_TAGS];
193 int result_seek[MAX_TAGS];
194 } si;
195 int link;
196};
197
198struct menu_root {
199 char _title[MENUENTRY_MAX_NAME];
200 const unsigned char *title;
201 char id[MAX_MENU_ID_SIZE];
202 int itemcount;
203 struct menu_entry *items[TAGMENU_MAX_ITEMS];
204};
205
206/* Statusbar text of the current view. */
207static char current_title[MAX_TAGS][128];
208
209static struct menu_root * menus[TAGMENU_MAX_MENUS];
210static struct menu_root * menu;
211static struct search_instruction *csi;
212static const char *strp;
213static int menu_count;
214static int rootmenu;
215
216static int current_offset;
217static int current_entry_count;
218
219static struct tree_context *tc;
220
221static int max_history_level; /* depth of menu levels with applicable history */
222static int selected_item_history[MAX_DIR_LEVELS];
223static int table_history[MAX_DIR_LEVELS];
224static int extra_history[MAX_DIR_LEVELS];
225
226/* a few memory alloc helper */
227static int tagtree_handle;
228static size_t tagtree_bufsize, tagtree_buf_used;
229
230#define UPDATE(x, y) { x = (typeof(x))((char*)(x) + (y)); }
231static int move_callback(int handle, void* current, void* new)
232{
233 (void)handle; (void)current; (void)new;
234 ptrdiff_t diff = new - current;
235
236 if (menu)
237 {
238 if ((char *) menu->title == (char *) menu->_title)
239 UPDATE(menu->title, diff);
240 UPDATE(menu, diff);
241 }
242
243 if (csi)
244 UPDATE(csi, diff);
245
246 /* loop over menus */
247 for(int i = 0; i < menu_count; i++)
248 {
249 struct menu_root* menuroot = menus[i];
250 /* then over the menu_entries of a menu */
251 for(int j = 0; j < menuroot->itemcount; j++)
252 {
253 struct menu_entry* mentry = menuroot->items[j];
254 /* then over the search_instructions of each menu_entry */
255 for(int k = 0; k < mentry->si.tagorder_count; k++)
256 {
257 for(int l = 0; l < mentry->si.clause_count[k]; l++)
258 {
259 if(mentry->si.clause[k][l]->str)
260 UPDATE(mentry->si.clause[k][l]->str, diff);
261 UPDATE(mentry->si.clause[k][l], diff);
262 }
263 }
264 if ((char *) menuroot->items[j]->name == (char *) menuroot->items[j]->_name)
265 UPDATE(menuroot->items[j]->name, diff);
266 UPDATE(menuroot->items[j], diff);
267 }
268 if ((char *) menus[i]->title == (char *) menus[i]->_title)
269 UPDATE(menus[i]->title, diff);
270 UPDATE(menus[i], diff);
271 }
272
273 /* now the same game for formats */
274 for(int i = 0; i < format_count; i++)
275 {
276 for(int j = 0; j < formats[i]->clause_count; j++)
277 {
278 UPDATE(formats[i]->clause[j]->str, diff);
279 UPDATE(formats[i]->clause[j], diff);
280 }
281
282 if (formats[i]->formatstr)
283 UPDATE(formats[i]->formatstr, diff);
284
285 UPDATE(formats[i], diff);
286 }
287 return BUFLIB_CB_OK;
288}
289#undef UPDATE
290
291static struct buflib_callbacks ops = {
292 .move_callback = move_callback,
293 .shrink_callback = NULL,
294};
295
296static uint32_t tagtree_data_crc(struct tree_context* c)
297{
298 char* buf;
299 uint32_t crc;
300 buf = core_get_data(tagtree_handle); /* data for the search clauses etc */
301 crc = crc_32(buf, tagtree_buf_used, c->dirlength);
302 buf = core_get_data(c->cache.name_buffer_handle); /* names */
303 crc = crc_32(buf, c->cache.name_buffer_size, crc);
304 buf = core_get_data(c->cache.entries_handle); /* tagentries */
305 crc = crc_32(buf, c->cache.max_entries * sizeof(struct tagentry), crc);
306 logf("%s 0x%x", __func__, crc);
307 return crc;
308}
309
310static void* tagtree_alloc(size_t size)
311{
312 size = ALIGN_UP(size, sizeof(void*));
313 if (size > (tagtree_bufsize - tagtree_buf_used))
314 return NULL;
315
316 char* buf = core_get_data(tagtree_handle) + tagtree_buf_used;
317
318 tagtree_buf_used += size;
319 return buf;
320}
321
322static void* tagtree_alloc0(size_t size)
323{
324 void* ret = tagtree_alloc(size);
325 if (ret)
326 memset(ret, 0, size);
327 return ret;
328}
329
330static char* tagtree_strdup(const char* buf)
331{
332 size_t len = strlen(buf) + 1;
333 char* dest = tagtree_alloc(len);
334 if (dest)
335 strcpy(dest, buf);
336 return dest;
337}
338
339/* save to call without locking */
340static int get_token_str(char *buf, int size)
341{
342 /* Find the start. */
343 while (*strp != '"' && *strp != '\0')
344 strp++;
345
346 if (*strp == '\0' || *(++strp) == '\0')
347 return -1;
348
349 /* Read the data. */
350 while (*strp != '"' && *strp != '\0' && --size > 0)
351 *(buf++) = *(strp++);
352
353 *buf = '\0';
354 if (*strp != '"')
355 return -2;
356
357 strp++;
358
359 return 0;
360}
361
362static int get_tag(int *tag)
363{
364 /* case insensitive matching ahead - these should all be lower case */
365#define TAG_TABLE \
366 TAG_MATCH("lm", tag_virt_length_min) \
367 TAG_MATCH("ls", tag_virt_length_sec) \
368 TAG_MATCH("pm", tag_virt_playtime_min) \
369 TAG_MATCH("ps", tag_virt_playtime_sec) \
370 TAG_MATCH("->", menu_next) \
371 TAG_MATCH("~>", menu_shuffle_songs) \
372 TAG_MATCH("==>", menu_load) \
373 TAG_MATCH("year", tag_year) \
374 TAG_MATCH("album", tag_album) \
375 TAG_MATCH("genre", tag_genre) \
376 TAG_MATCH("title", tag_title) \
377 TAG_MATCH("%sort", var_sorttype) \
378 TAG_MATCH("artist", tag_artist) \
379 TAG_MATCH("length", tag_length) \
380 TAG_MATCH("rating", tag_rating) \
381 TAG_MATCH("%limit", var_limit) \
382 TAG_MATCH("%strip", var_strip) \
383 TAG_MATCH("bitrate", tag_bitrate) \
384 TAG_MATCH("comment", tag_comment) \
385 TAG_MATCH("discnum", tag_discnumber) \
386 TAG_MATCH("%format", var_format) \
387 TAG_MATCH("%reload", menu_reload) \
388 TAG_MATCH("filename", tag_filename) \
389 TAG_MATCH("basename", tag_virt_basename) \
390 TAG_MATCH("tracknum", tag_tracknumber) \
391 TAG_MATCH("composer", tag_composer) \
392 TAG_MATCH("ensemble", tag_albumartist) \
393 TAG_MATCH("grouping", tag_grouping) \
394 TAG_MATCH("entryage", tag_virt_entryage) \
395 TAG_MATCH("commitid", tag_commitid) \
396 TAG_MATCH("%include", var_include) \
397 TAG_MATCH("playcount", tag_playcount) \
398 TAG_MATCH("autoscore", tag_virt_autoscore) \
399 TAG_MATCH("lastplayed", tag_lastplayed) \
400 TAG_MATCH("lastoffset", tag_lastoffset) \
401 TAG_MATCH("%root_menu", var_rootmenu) \
402 TAG_MATCH("albumartist", tag_albumartist) \
403 TAG_MATCH("lastelapsed", tag_lastelapsed) \
404 TAG_MATCH("%menu_start", var_menu_start) \
405 TAG_MATCH("%byfirstletter", menu_byfirstletter) \
406 TAG_MATCH("canonicalartist", tag_virt_canonicalartist) \
407 /* END OF TAG_TABLE MACRO */
408
409 /* build two separate arrays tag strings and symbol map*/
410 #define TAG_MATCH(str, tag) str,
411 static const char * const get_tag_match[] =
412 {
413 TAG_TABLE
414 };
415 #undef TAG_MATCH
416 #define TAG_MATCH(str, tag) tag,
417 static const uint8_t get_tag_symbol[]=
418 {
419 TAG_TABLE
420 };
421 #undef TAG_MATCH
422
423 const char *tagstr;
424 ptrdiff_t tagstr_len;
425
426 /* Find the start. */
427 while (*strp == ' ' || *strp == '>')
428 strp++;
429
430 if (*strp == '\0' || *strp == '?')
431 return 0;
432
433 tagstr = strp;
434 while(*strp != '\0' && *strp != ' ') /* walk to the end of the tag */
435 {
436 strp++;
437 }
438 tagstr_len = strp - tagstr;
439
440 char first = tolower(*tagstr++); /* get the first letter elide cmp fn call */
441
442 for (size_t i = 0; i < ARRAYLEN(get_tag_match); i++)
443 {
444 const char *match = get_tag_match[i];
445
446 if (first == match[0] && strncasecmp(tagstr, match + 1, tagstr_len - 1) == 0)
447 {
448 /* check for full match */
449 if (match[tagstr_len] == '\0')
450 {
451 *tag = get_tag_symbol[i];
452 return 1;
453 }
454 }
455 }
456 logf("NO MATCH: %.*s\n", (int)tagstr_len, tagstr);
457
458 return -1;
459}
460
461static int get_clause(int *condition)
462{
463 /* one or two operator conditionals */
464 #define OPS2VAL(op1, op2) ((uint16_t)op1 << 8 | (uint16_t)op2)
465 #define CLAUSE(op1, op2, symbol) {OPS2VAL(op1, op2), symbol }
466
467 struct clause_symbol {uint16_t value;uint16_t symbol;};
468 const struct clause_symbol *match;
469 static const struct clause_symbol get_clause_match[] =
470 {
471 CLAUSE('=', ' ', clause_is),
472 CLAUSE('=', '=', clause_is),
473 CLAUSE('!', '=', clause_is_not),
474 CLAUSE('>', ' ', clause_gt),
475 CLAUSE('>', '=', clause_gteq),
476 CLAUSE('<', ' ', clause_lt),
477 CLAUSE('<', '=', clause_lteq),
478 CLAUSE('~', ' ', clause_contains),
479 CLAUSE('!', '~', clause_not_contains),
480 CLAUSE('^', ' ', clause_begins_with),
481 CLAUSE('!', '^', clause_not_begins_with),
482 CLAUSE('$', ' ', clause_ends_with),
483 CLAUSE('!', '$', clause_not_ends_with),
484 CLAUSE('@', '^', clause_begins_oneof),
485 CLAUSE('@', '$', clause_ends_oneof),
486 CLAUSE('@', ' ', clause_oneof),
487 CLAUSE('*', '^', clause_not_begins_oneof),
488 CLAUSE('*', '$', clause_not_ends_oneof),
489 CLAUSE('!', '@', clause_not_oneof),
490 CLAUSE(0, 0, 0) /* sentinel */
491 };
492
493 /* Find the start. */
494 while (*strp == ' ' && *strp != '\0')
495 strp++;
496
497 if (*strp == '\0')
498 return 0;
499
500 char op1 = strp[0];
501 char op2 = strp[1];
502 if (op2 == '"') /*allow " to end a single op conditional */
503 op2 = ' ';
504
505 uint16_t value = OPS2VAL(op1, op2);
506
507 for (match = get_clause_match; match->value != 0; match++)
508 {
509 if (value == match->value)
510 {
511 *condition = match->symbol;
512 return 1;
513 }
514 }
515
516 return 0;
517#undef OPS2VAL
518#undef CLAUSE
519}
520
521static bool read_clause(struct tagcache_search_clause *clause)
522{
523 char buf[SEARCHSTR_SIZE];
524 unsigned int i;
525
526 if (get_tag(&clause->tag) <= 0)
527 return false;
528
529 if (get_clause(&clause->type) <= 0)
530 return false;
531
532 if (get_token_str(buf, sizeof buf) < 0)
533 return false;
534
535 for (i=0; i<ARRAYLEN(id3_to_search_mapping); i++)
536 {
537 if (!strcasecmp(buf, id3_to_search_mapping[i].string))
538 break;
539 }
540
541 if (i<ARRAYLEN(id3_to_search_mapping)) /* runtime search operand found */
542 {
543 clause->source = source_runtime+i;
544 clause->str = tagtree_alloc(SEARCHSTR_SIZE);
545 }
546 else
547 {
548 clause->source = source_constant;
549 clause->str = tagtree_strdup(buf);
550 }
551
552 if (!clause->str)
553 {
554 logf("tagtree failed to allocate %s", "clause string");
555 return false;
556 }
557 else if (TAGCACHE_IS_NUMERIC(clause->tag))
558 {
559 clause->numeric = true;
560 clause->numeric_data = atoi(clause->str);
561 }
562 else
563 clause->numeric = false;
564
565 logf("got clause: %d/%d [%s]", clause->tag, clause->type, clause->str);
566
567 return true;
568}
569
570static bool read_variable(char *buf, int size)
571{
572 int condition;
573
574 if (!get_clause(&condition))
575 return false;
576
577 if (condition != clause_is)
578 return false;
579
580 if (get_token_str(buf, size) < 0)
581 return false;
582
583 return true;
584}
585
586/* "%3d. %s" autoscore title %sort = "inverse" %limit = "100" */
587static int get_format_str(struct display_format *fmt)
588{
589 int ret;
590 char buf[128];
591 int i;
592
593 memset(fmt, 0, sizeof(struct display_format));
594
595 if (get_token_str(fmt->name, sizeof fmt->name) < 0)
596 return -10;
597
598 /* Determine the group id */
599 fmt->group_id = 0;
600 for (i = 0; i < format_count; i++)
601 {
602 if (!strcasecmp(formats[i]->name, fmt->name))
603 {
604 fmt->group_id = formats[i]->group_id;
605 break;
606 }
607
608 if (formats[i]->group_id > fmt->group_id)
609 fmt->group_id = formats[i]->group_id;
610 }
611
612 if (i == format_count)
613 fmt->group_id++;
614
615 logf("format: (%d) %s", fmt->group_id, fmt->name);
616
617 if (get_token_str(buf, sizeof buf) < 0)
618 return -10;
619
620 fmt->formatstr = tagtree_strdup(buf);
621
622 while (fmt->tag_count < MAX_TAGS)
623 {
624 ret = get_tag(&fmt->tags[fmt->tag_count]);
625 if (ret < 0)
626 return -11;
627
628 if (ret == 0)
629 break;
630
631 switch (fmt->tags[fmt->tag_count]) {
632 case var_sorttype:
633 if (!read_variable(buf, sizeof buf))
634 return -12;
635 if (!strcasecmp("inverse", buf))
636 fmt->sort_inverse = true;
637 break;
638
639 case var_limit:
640 if (!read_variable(buf, sizeof buf))
641 return -13;
642 fmt->limit = atoi(buf);
643 break;
644
645 case var_strip:
646 if (!read_variable(buf, sizeof buf))
647 return -14;
648 fmt->strip = atoi(buf);
649 break;
650
651 default:
652 fmt->tag_count++;
653 }
654 }
655
656 return 1;
657}
658
659static int add_format(const char *buf)
660{
661 if (format_count >= TAGMENU_MAX_FMTS)
662 {
663 logf("too many formats");
664 return -1;
665 }
666
667 strp = buf;
668
669 if (formats[format_count] == NULL)
670 formats[format_count] = tagtree_alloc0(sizeof(struct display_format));
671 if (!formats[format_count])
672 {
673 logf("tagtree failed to allocate %s", "format string");
674 return -2;
675 }
676 if (get_format_str(formats[format_count]) < 0)
677 {
678 logf("get_format_str() parser failed!");
679 if (formats[format_count])
680 memset(formats[format_count], 0, sizeof(struct display_format));
681 return -4;
682 }
683
684 while (*strp != '\0' && *strp != '?')
685 strp++;
686
687 if (*strp == '?')
688 {
689 int clause_count = 0;
690 strp++;
691
692 core_pin(tagtree_handle);
693 while (1)
694 {
695 struct tagcache_search_clause *new_clause;
696
697 if (clause_count >= TAGCACHE_MAX_CLAUSES)
698 {
699 logf("too many clauses");
700 break;
701 }
702
703 new_clause = tagtree_alloc(sizeof(struct tagcache_search_clause));
704 if (!new_clause)
705 {
706 logf("tagtree failed to allocate %s", "search clause");
707 return -3;
708 }
709 formats[format_count]->clause[clause_count] = new_clause;
710 if (!read_clause(new_clause))
711 break;
712
713 clause_count++;
714 }
715 core_unpin(tagtree_handle);
716
717 formats[format_count]->clause_count = clause_count;
718 }
719
720 format_count++;
721
722 return 1;
723}
724
725static int get_condition(struct search_instruction *inst)
726{
727 struct tagcache_search_clause *new_clause;
728 int clause_count;
729 char buf[128];
730
731 switch (*strp)
732 {
733 case '=':
734 {
735 int i;
736
737 if (get_token_str(buf, sizeof buf) < 0)
738 return -1;
739
740 for (i = 0; i < format_count; i++)
741 {
742 if (!strcasecmp(formats[i]->name, buf))
743 break;
744 }
745
746 if (i == format_count)
747 {
748 logf("format not found: %s", buf);
749 return -2;
750 }
751
752 inst->format_id[inst->tagorder_count] = formats[i]->group_id;
753 return 1;
754 }
755 case '?':
756 case ' ':
757 case '&':
758 strp++;
759 return 1;
760 case '-':
761 case '\0':
762 return 0;
763 }
764
765 clause_count = inst->clause_count[inst->tagorder_count];
766 if (clause_count >= TAGCACHE_MAX_CLAUSES)
767 {
768 logf("Too many clauses");
769 return -2;
770 }
771
772 new_clause = tagtree_alloc0(sizeof(struct tagcache_search_clause));
773 if (!new_clause)
774 {
775 logf("tagtree failed to allocate %s", "search clause");
776 return -3;
777 }
778
779 inst->clause[inst->tagorder_count][clause_count] = new_clause;
780
781 if (*strp == '|')
782 {
783 strp++;
784 new_clause->type = clause_logical_or;
785 }
786 else
787 {
788 core_pin(tagtree_handle);
789 bool ret = read_clause(new_clause);
790 core_unpin(tagtree_handle);
791 if (!ret)
792 return -1;
793 }
794 inst->clause_count[inst->tagorder_count]++;
795
796 return 1;
797}
798
799/* example search:
800 * "Best" artist ? year >= "2000" & title !^ "crap" & genre = "good genre" \
801 * : album ? year >= "2000" : songs
802 * ^ begins with
803 * * contains
804 * $ ends with
805 */
806
807static bool parse_search(struct menu_entry *entry, const char *str)
808{
809 int ret;
810 int type;
811 struct search_instruction *inst = &entry->si;
812 char buf[MAX_PATH];
813 int i;
814
815 strp = str;
816
817 /* Parse entry name */
818 if (get_token_str(entry->_name, sizeof entry->_name) < 0)
819 {
820 logf("No name found.");
821 return false;
822 }
823
824 /* Attempt to entry name to lang_id for voicing/translation
825 (excepted for single character entries like those in the 'First Letter' menus)
826 */
827 if (entry->_name[0] != '\0' && entry->_name[1] != '\0')
828 {
829 int lang_id = lang_english_to_id(entry->_name);
830 if (lang_id >= 0)
831 entry->name = ID2P(lang_id);
832 else
833 entry->name = entry->_name;
834 }
835 else
836 entry->name = entry->_name;
837
838 /* Parse entry type */
839 if (get_tag(&entry->type) <= 0)
840 return false;
841
842 if (entry->type == menu_load)
843 {
844 if (get_token_str(buf, sizeof buf) < 0)
845 return false;
846
847 /* Find the matching root menu or "create" it */
848 for (i = 0; i < menu_count; i++)
849 {
850 if (!strcasecmp(menus[i]->id, buf))
851 {
852 entry->link = i;
853 return true;
854 }
855 }
856
857 if (menu_count >= TAGMENU_MAX_MENUS)
858 {
859 logf("max menucount reached");
860 return false;
861 }
862
863 /* Allocate a new menu unless link is found. */
864 menus[menu_count] = tagtree_alloc0(sizeof(struct menu_root));
865 if (!menus[menu_count])
866 {
867 logf("tagtree failed to allocate %s", "menu");
868 return false;
869 }
870 strmemccpy(menus[menu_count]->id, buf, MAX_MENU_ID_SIZE);
871 entry->link = menu_count;
872 ++menu_count;
873
874 return true;
875 }
876
877 if (entry->type != menu_next && entry->type != menu_shuffle_songs)
878 return false;
879
880 while (inst->tagorder_count < MAX_TAGS)
881 {
882 ret = get_tag(&inst->tagorder[inst->tagorder_count]);
883 if (ret < 0)
884 {
885 logf("Parse error #1");
886 logf("%s", strp);
887 return false;
888 }
889
890 if (ret == 0)
891 break ;
892
893 logf("tag: %d", inst->tagorder[inst->tagorder_count]);
894
895 core_pin(tagtree_handle);
896 while ( (ret = get_condition(inst)) > 0 ) ;
897 core_unpin(tagtree_handle);
898
899 if (ret < 0)
900 return false;
901
902 inst->tagorder_count++;
903
904 if (get_tag(&type) <= 0 || (type != menu_next && type != menu_shuffle_songs))
905 break;
906 }
907
908 return true;
909}
910
911static int compare(const void *p1, const void *p2)
912{
913 struct tagentry *e1 = (struct tagentry *)p1;
914 struct tagentry *e2 = (struct tagentry *)p2;
915 return qsort_fn(e1->name, e2->name, MAX_PATH);
916}
917
918static int compare_with_albums(const void *p1, const void *p2)
919{
920 struct tagentry *e1 = (struct tagentry *)p1;
921 struct tagentry *e2 = (struct tagentry *)p2;
922 int sort_album_res = qsort_fn(
923 e1->album_name == NULL ? "" : e1->album_name,
924 e2->album_name == NULL ? "" : e2->album_name, MAX_PATH);
925 if (sort_album_res != 0)
926 {
927 /* If album name is different */
928 return sort_album_res;
929 }
930 return qsort_fn(e1->name, e2->name, MAX_PATH);
931}
932
933static void tagtree_buffer_event(unsigned short id, void *ev_data)
934{
935 (void)id;
936 struct tagcache_search tcs;
937 struct mp3entry *id3 = ((struct track_event *)ev_data)->id3;
938
939 bool runtimedb = global_settings.runtimedb;
940 bool autoresume = global_settings.autoresume_enable;
941
942 /* Do not gather data unless proper setting has been enabled. */
943 if (!runtimedb && !autoresume)
944 return;
945
946 logf("be:%s", id3->path);
947
948 while (! tagcache_is_fully_initialized())
949 yield();
950
951 if (!tagcache_find_index(&tcs, id3->path))
952 {
953 logf("tc stat: not found: %s", id3->path);
954 return;
955 }
956
957 if (runtimedb)
958 {
959 id3->playcount = tagcache_get_numeric(&tcs, tag_playcount);
960 if (!id3->rating)
961 id3->rating = tagcache_get_numeric(&tcs, tag_rating);
962 id3->lastplayed = tagcache_get_numeric(&tcs, tag_lastplayed);
963 id3->score = tagcache_get_numeric(&tcs, tag_virt_autoscore) / 10;
964 id3->playtime = tagcache_get_numeric(&tcs, tag_playtime);
965
966 logf("-> %ld/%ld", id3->playcount, id3->playtime);
967 }
968
969 if (autoresume)
970 {
971 /* Load current file resume info if not already defined (by
972 another resume mechanism) */
973 if (id3->elapsed == 0)
974 {
975 id3->elapsed = tagcache_get_numeric(&tcs, tag_lastelapsed);
976
977 logf("tagtree_buffer_event: Set elapsed for %s to %lX\n",
978 str_or_empty(id3->title), id3->elapsed);
979 }
980
981 if (id3->offset == 0)
982 {
983 id3->offset = tagcache_get_numeric(&tcs, tag_lastoffset);
984
985 logf("tagtree_buffer_event: Set offset for %s to %lX\n",
986 str_or_empty(id3->title), id3->offset);
987 }
988 }
989
990 /* Store our tagcache index pointer. */
991 id3->tagcache_idx = tcs.idx_id+1;
992
993 tagcache_search_finish(&tcs);
994}
995
996static void tagtree_track_finish_event(unsigned short id, void *ev_data)
997{
998 (void)id;
999 struct track_event *te = (struct track_event *)ev_data;
1000 struct mp3entry *id3 = te->id3;
1001
1002 long tagcache_idx = id3->tagcache_idx;
1003 if (!tagcache_idx)
1004 {
1005 logf("No tagcache index pointer found");
1006 return;
1007 }
1008 tagcache_idx--;
1009
1010 bool auto_skip = te->flags & TEF_AUTO_SKIP;
1011 bool runtimedb = global_settings.runtimedb;
1012 bool autoresume = global_settings.autoresume_enable;
1013
1014 /* Don't process unplayed tracks, or tracks interrupted within the
1015 first 15 seconds but always process autoresume point */
1016 if (runtimedb && (id3->elapsed == 0
1017 || (id3->elapsed < 15 * 1000 && !auto_skip)
1018 ))
1019 {
1020 logf("not db logging unplayed or skipped track");
1021 runtimedb = false;
1022 }
1023
1024 /* 3s because that is the threshold the WPS uses to rewind instead
1025 of skip backwards */
1026 if (autoresume && (id3->elapsed == 0
1027 || (id3->elapsed < 3 * 1000 && !auto_skip)))
1028 {
1029 logf("not logging autoresume");
1030 autoresume = false;
1031 }
1032
1033 /* Do not gather data unless proper setting has been enabled and at least
1034 one is still slated to be recorded */
1035 if (!(runtimedb || autoresume))
1036 {
1037 logf("runtimedb gathering and autoresume not enabled/ignored");
1038 return;
1039 }
1040
1041 long lastplayed = tagcache_increase_serial();
1042 if (lastplayed < 0)
1043 {
1044 logf("incorrect tc serial:%ld", lastplayed);
1045 return;
1046 }
1047
1048 if (runtimedb)
1049 {
1050 long playcount;
1051 long playtime;
1052
1053 playcount = id3->playcount + 1;
1054
1055 /* Ignore the last 15s (crossfade etc.) */
1056 playtime = id3->playtime + MIN(id3->length, id3->elapsed + 15 * 1000);
1057
1058 logf("ube:%s", id3->path);
1059 logf("-> %ld/%ld", playcount, playtime);
1060 logf("-> %ld/%ld/%ld", id3->elapsed, id3->length,
1061 MIN(id3->length, id3->elapsed + 15 * 1000));
1062
1063 /* Queue the updates to the tagcache system. */
1064 tagcache_update_numeric(tagcache_idx, tag_playcount, playcount);
1065 tagcache_update_numeric(tagcache_idx, tag_playtime, playtime);
1066 tagcache_update_numeric(tagcache_idx, tag_lastplayed, lastplayed);
1067 }
1068
1069 if (autoresume)
1070 {
1071 unsigned long elapsed = auto_skip ? 0 : id3->elapsed;
1072 unsigned long offset = auto_skip ? 0 : id3->offset;
1073 tagcache_update_numeric(tagcache_idx, tag_lastelapsed, elapsed);
1074 tagcache_update_numeric(tagcache_idx, tag_lastoffset, offset);
1075
1076 logf("tagtree_track_finish_event: Save resume for %s: %lX %lX",
1077 str_or_empty(id3->title), elapsed, offset);
1078 }
1079}
1080
1081int tagtree_export(void)
1082{
1083 struct tagcache_search tcs;
1084
1085 splash(0, ID2P(LANG_CREATING));
1086 if (!tagcache_create_changelog(&tcs))
1087 {
1088 splash(HZ*2, ID2P(LANG_FAILED));
1089 }
1090
1091 return 0;
1092}
1093
1094int tagtree_import(void)
1095{
1096 splash(0, ID2P(LANG_WAIT));
1097 if (!tagcache_import_changelog())
1098 {
1099 splash(HZ*2, ID2P(LANG_FAILED));
1100 }
1101
1102 return 0;
1103}
1104
1105static bool alloc_menu_parse_buf(char *buf, int type)
1106{
1107 /* allocate a new menu item (if needed) initialize it with data parsed
1108 from buf Note: allows setting menu type, type ignored when < 0
1109 */
1110 /* Allocate */
1111 if (menu->items[menu->itemcount] == NULL)
1112 menu->items[menu->itemcount] = tagtree_alloc0(sizeof(struct menu_entry));
1113 if (!menu->items[menu->itemcount])
1114 {
1115 logf("tagtree failed to allocate %s", "menu items");
1116 return false;
1117 }
1118
1119 /* Initialize */
1120 core_pin(tagtree_handle);
1121 if (parse_search(menu->items[menu->itemcount], buf))
1122 {
1123 if (type >= 0)
1124 menu->items[menu->itemcount]->type = type;
1125 menu->itemcount++;
1126 }
1127 core_unpin(tagtree_handle);
1128 return true;
1129}
1130
1131static void build_firstletter_menu(char *buf, size_t bufsz)
1132{
1133#if 0 /* GCC complains about this I can't find a definitive answer */
1134 const char *subitem = buf;
1135 size_t l = strlen(buf) + 1;
1136 buf+=l;
1137 bufsz-=l;
1138#else
1139 char subitem[32]; /* canonicalartist longest subitem we expect add a bit extra..*/
1140 strmemccpy(subitem, buf, sizeof(subitem));
1141#endif
1142
1143 const char * const fmt ="\"%s\"-> %s ? %s %c\"%c\"-> %s =\"fmt_title\"";
1144 const char * const showsub = /* album subitem for canonicalartist */
1145 ((strcasestr(subitem, "artist") == NULL) ? "title" : "album -> title");
1146
1147 /* Using >= "0" & <= "9" does not work as intended in all cases
1148 we need to use the previous & the next character in the ASCII table
1149 which are "/" and ":" to get the intended effect */
1150 const char * fmt_numeric ="\"%s\"-> %s ? %s > \"/\" & %s < \":\" -> %s =\"fmt_title\"";
1151 snprintf(buf, bufsz, fmt_numeric,
1152 str(LANG_DISPLAY_NUMERIC), subitem, subitem, subitem, showsub);
1153
1154 if (!alloc_menu_parse_buf(buf, menu_byfirstletter))
1155 {
1156 return;
1157 }
1158
1159 for (int i = 0; i < 26; i++)
1160 {
1161 snprintf(buf, bufsz, fmt, "#", subitem, subitem,'^', 'A' + i, showsub);
1162 buf[1] = 'A' + i; /* overwrite the placeholder # with the current letter */
1163 /* ex: "A" -> title ? title ^ "A" -> title = "fmt_title" */
1164 if (!alloc_menu_parse_buf(buf, menu_byfirstletter))
1165 {
1166 return;
1167 }
1168 }
1169
1170 /* use lower character there because strncasecmp works will convert them anyway */
1171 const char * fmt_special ="\"%s\"-> %s ? %s *^ "\
1172 "\"a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9\" -> %s =\"fmt_title\"";
1173 snprintf(buf, bufsz, fmt_special,
1174 str(LANG_DISPLAY_SPECIAL_CHARACTER), subitem, subitem, showsub);
1175
1176 if (!alloc_menu_parse_buf(buf, menu_byfirstletter))
1177 {
1178 return;
1179 }
1180}
1181
1182static bool parse_menu(const char *filename);
1183static int parse_line(int n, char *buf, void *parameters)
1184{
1185 char data[256];
1186 int variable;
1187 static bool read_menu;
1188 int i;
1189 char *p;
1190
1191 (void)parameters;
1192
1193 /* Strip possible <CR> at end of line. */
1194 p = strchr(buf, '\r');
1195 if (p != NULL)
1196 *p = '\0';
1197
1198 logf("parse:%d/%s", n, buf);
1199
1200 /* First line, do initialisation. */
1201 if (n == 0)
1202 {
1203 if (strcasecmp(TAGNAVI_VERSION, buf))
1204 {
1205 logf("Version mismatch");
1206 return -1;
1207 }
1208
1209 read_menu = false;
1210 }
1211
1212 if (buf[0] == '#')
1213 return 0;
1214
1215 if (buf[0] == '\0')
1216 {
1217 if (read_menu)
1218 {
1219 /* End the menu */
1220 read_menu = false;
1221 }
1222 return 0;
1223 }
1224
1225 if (!read_menu)
1226 {
1227 strp = buf;
1228 if (get_tag(&variable) <= 0)
1229 return 0;
1230
1231 switch (variable)
1232 {
1233 case var_format:
1234 if (add_format(strp) < 0)
1235 {
1236 logf("Format add fail: %s", data);
1237 }
1238 break;
1239
1240 case var_include:
1241 if (get_token_str(data, sizeof(data)) < 0)
1242 {
1243 logf("%%include empty");
1244 return 0;
1245 }
1246
1247 if (!parse_menu(data))
1248 {
1249 logf("Load menu fail: %s", data);
1250 }
1251 break;
1252 case menu_byfirstletter: /* Fallthrough */
1253 case var_menu_start:
1254 if (menu_count >= TAGMENU_MAX_MENUS)
1255 {
1256 logf("max menucount reached");
1257 return 0;
1258 }
1259
1260 if (get_token_str(data, sizeof data) < 0)
1261 {
1262 logf("%%menu_start id empty");
1263 return 0;
1264 }
1265
1266 menu = NULL;
1267 for (i = 0; i < menu_count; i++)
1268 {
1269 if (!strcasecmp(menus[i]->id, data))
1270 {
1271 menu = menus[i];
1272 }
1273 }
1274
1275 if (menu == NULL)
1276 {
1277 menus[menu_count] = tagtree_alloc0(sizeof(struct menu_root));
1278 if (!menus[menu_count])
1279 {
1280 logf("tagtree failed to allocate %s", "menu");
1281 return -2;
1282 }
1283 menu = menus[menu_count];
1284 ++menu_count;
1285 strmemccpy(menu->id, data, MAX_MENU_ID_SIZE);
1286 }
1287
1288 if (get_token_str(menu->_title, sizeof(menu->_title)) < 0)
1289 {
1290 logf("%%menu_start title empty");
1291 return 0;
1292 }
1293
1294 /* Attempt to match title to lang_id for voicing/translation */
1295 int lang_id = lang_english_to_id(menu->_title);
1296 if (lang_id >= 0)
1297 menu->title = ID2P(lang_id);
1298 else
1299 menu->title = menu->_title;
1300
1301 logf("menu: %s id: %ld", P2STR(menu->title), P2ID(menu->title));
1302
1303 if (variable == menu_byfirstletter)
1304 {
1305 if (get_token_str(data, sizeof(data)) < 0)
1306 {
1307 logf("%%firstletter_menu has no subitem"); /*artist,album*/
1308 return 0;
1309 }
1310 logf("A-Z Menu subitem: %s", data);
1311 read_menu = false;
1312 build_firstletter_menu(data, sizeof(data));
1313 break;
1314 }
1315 read_menu = true;
1316 break;
1317
1318 case var_rootmenu:
1319 /* Only set root menu once. */
1320 if (rootmenu >= 0)
1321 break;
1322
1323 if (get_token_str(data, sizeof(data)) < 0)
1324 {
1325 logf("%%rootmenu empty");
1326 return 0;
1327 }
1328
1329 for (i = 0; i < menu_count; i++)
1330 {
1331 if (!strcasecmp(menus[i]->id, data))
1332 {
1333 rootmenu = i;
1334 }
1335 }
1336 break;
1337 }
1338
1339 return 0;
1340 }
1341
1342 if (menu->itemcount >= TAGMENU_MAX_ITEMS)
1343 {
1344 logf("max itemcount reached");
1345 return 0;
1346 }
1347
1348 if (!alloc_menu_parse_buf(buf, -1))
1349 {
1350 return -2;
1351 }
1352
1353 return 0;
1354}
1355
1356static bool parse_menu(const char *filename)
1357{
1358 int fd;
1359 char buf[1024];
1360 int rc;
1361
1362 if (menu_count >= TAGMENU_MAX_MENUS)
1363 {
1364 logf("max menucount reached");
1365 return false;
1366 }
1367
1368 fd = open(filename, O_RDONLY);
1369 if (fd < 0)
1370 {
1371 logf("Search instruction file not found.");
1372 return false;
1373 }
1374
1375 /* Now read file for real, parsing into si */
1376 rc = fast_readline(fd, buf, sizeof buf, NULL, parse_line);
1377 close(fd);
1378
1379 return (rc >= 0);
1380}
1381
1382static void tagtree_unload(struct tree_context *c)
1383{
1384 /* may be spurious... */
1385 core_pin(tagtree_handle);
1386
1387 remove_event(PLAYBACK_EVENT_TRACK_BUFFER, tagtree_buffer_event);
1388 remove_event(PLAYBACK_EVENT_TRACK_FINISH, tagtree_track_finish_event);
1389
1390 if (c)
1391 {
1392 tree_lock_cache(c);
1393 struct tagentry *dptr = core_get_data(c->cache.entries_handle);
1394 menu = menus[c->currextra];
1395 if (!menu)
1396 {
1397 logf("tagtree menu doesn't exist");
1398 return;
1399 }
1400
1401 for (int i = 0; i < menu->itemcount; i++)
1402 {
1403 dptr->name = NULL;
1404 dptr->newtable = 0;
1405 dptr->extraseek = 0;
1406 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1407 dptr++;
1408 }
1409 }
1410
1411 for (int i = 0; i < menu_count; i++)
1412 menus[i] = NULL;
1413 menu_count = 0;
1414
1415 for (int i = 0; i < format_count; i++)
1416 formats[i] = NULL;
1417 format_count = 0;
1418
1419 core_free(tagtree_handle);
1420 tagtree_handle = 0;
1421 tagtree_buf_used = 0;
1422 tagtree_bufsize = 0;
1423
1424 if (c)
1425 tree_unlock_cache(c);
1426}
1427
1428static bool initialize_tagtree(void) /* also used when user selects 'Reload' in 'custom menu'*/
1429{
1430 max_history_level = 0;
1431 format_count = 0;
1432 menu_count = 0;
1433 menu = NULL;
1434 rootmenu = -1;
1435 tagtree_handle = core_alloc_maximum(&tagtree_bufsize, &ops);
1436 if (tagtree_handle < 0)
1437 panicf("tagtree OOM");
1438
1439 /* Use the user tagnavi config if present, otherwise use the default. */
1440 const char* tagnavi_file;
1441 if(file_exists(TAGNAVI_USER_CONFIG))
1442 tagnavi_file = TAGNAVI_USER_CONFIG;
1443 else
1444 tagnavi_file = TAGNAVI_DEFAULT_CONFIG;
1445
1446 if (!parse_menu(tagnavi_file))
1447 {
1448 tagtree_unload(NULL);
1449 return false;
1450 }
1451
1452 /* safety check since tree.c needs to cast tagentry to entry */
1453 if (sizeof(struct tagentry) != sizeof(struct entry))
1454 panicf("tagentry(%zu) and entry mismatch(%zu)",
1455 sizeof(struct tagentry), sizeof(struct entry));
1456
1457 /* If no root menu is set, assume it's the first single menu
1458 * we have. That shouldn't normally happen. */
1459 if (rootmenu < 0)
1460 rootmenu = 0;
1461
1462 add_event(PLAYBACK_EVENT_TRACK_BUFFER, tagtree_buffer_event);
1463 add_event(PLAYBACK_EVENT_TRACK_FINISH, tagtree_track_finish_event);
1464
1465 core_shrink(tagtree_handle, NULL, tagtree_buf_used);
1466 return true;
1467}
1468
1469void tagtree_init(void)
1470{
1471 initialize_tagtree();
1472}
1473
1474static int format_str(struct tagcache_search *tcs, struct display_format *fmt,
1475 char *buf, int buf_size)
1476{
1477 static char fmtbuf[20];
1478 bool read_format = false;
1479 unsigned fmtbuf_pos = 0;
1480 int parpos = 0;
1481 int buf_pos = 0;
1482 int i;
1483
1484 /* memset(buf, 0, buf_size); probably uneeded */
1485 for (i = 0; fmt->formatstr[i] != '\0'; i++)
1486 {
1487 if (fmt->formatstr[i] == '%')
1488 {
1489 read_format = true;
1490 fmtbuf_pos = 0;
1491 if (parpos >= fmt->tag_count)
1492 {
1493 logf("too many format tags");
1494 return -1;
1495 }
1496 }
1497
1498 char formatchar = fmt->formatstr[i];
1499
1500 if (read_format)
1501 {
1502 fmtbuf[fmtbuf_pos++] = formatchar;
1503 if (fmtbuf_pos >= sizeof fmtbuf)
1504 {
1505 logf("format parse error");
1506 return -2;
1507 }
1508
1509 if (formatchar == 's' || formatchar == 'd')
1510 {
1511 unsigned space_left = buf_size - buf_pos;
1512 char tmpbuf[MAX_PATH];
1513 char *result;
1514
1515 fmtbuf[fmtbuf_pos] = '\0';
1516 read_format = false;
1517
1518 switch (formatchar)
1519 {
1520 case 's':
1521 if (fmt->tags[parpos] == tcs->type)
1522 {
1523 result = tcs->result;
1524 }
1525 else
1526 {
1527 /* Need to fetch the tag data. */
1528 int tag = fmt->tags[parpos];
1529
1530 if (!tagcache_retrieve(tcs, tcs->idx_id,
1531 tag, tmpbuf, sizeof tmpbuf))
1532 {
1533 logf("retrieve failed");
1534 return -3;
1535 }
1536
1537 result = tmpbuf;
1538 }
1539 buf_pos +=
1540 snprintf(&buf[buf_pos], space_left, fmtbuf, result);
1541 break;
1542
1543 case 'd':
1544 buf_pos +=
1545 snprintf(&buf[buf_pos], space_left, fmtbuf,
1546 tagcache_get_numeric(tcs, fmt->tags[parpos]));
1547 }
1548
1549 parpos++;
1550 }
1551 }
1552 else
1553 buf[buf_pos++] = formatchar;
1554
1555 if (buf_pos >= buf_size - 1) /* need at least one more byte for \0 */
1556 {
1557 logf("buffer overflow");
1558 return -4;
1559 }
1560 }
1561
1562 buf[buf_pos++] = '\0';
1563
1564 return 0;
1565}
1566
1567static struct tagentry* get_entries(struct tree_context *tc)
1568{
1569 return core_get_data(tc->cache.entries_handle);
1570}
1571
1572static void tcs_get_basename(struct tagcache_search *tcs, bool is_basename)
1573{
1574 if (is_basename)
1575 {
1576 char* basename = strrchr(tcs->result, '/');
1577 if (basename != NULL)
1578 {
1579 tcs->result = basename + 1;
1580 tcs->result_len = strlen(tcs->result) + 1;
1581 }
1582 }
1583}
1584
1585static int retrieve_entries(struct tree_context *c, int offset, bool init)
1586{
1587 logf( "%s", __func__);
1588 char tcs_buf[TAGCACHE_BUFSZ];
1589 const long tcs_bufsz = sizeof(tcs_buf);
1590 struct tagcache_search tcs;
1591 struct display_format *fmt;
1592 int i;
1593 int namebufused = 0;
1594 int total_count = 0;
1595 c->special_entry_count = 0;
1596 int level = c->currextra;
1597 int tag;
1598 bool sort = false;
1599 bool sort_inverse;
1600 bool is_basename = false;
1601 int sort_limit;
1602 int strip;
1603
1604 /* Show search progress straight away if the disk needs to spin up,
1605 otherwise show it after the normal 1/2 second delay */
1606 show_search_progress(
1607#ifdef HAVE_DISK_STORAGE
1608#ifdef HAVE_TC_RAMCACHE
1609 tagcache_is_in_ram() ? true :
1610#endif
1611 storage_disk_is_active()
1612#else
1613 true
1614#endif
1615 , 0, 0, 0);
1616
1617 if (c->currtable == TABLE_ALLSUBENTRIES || c->currtable == TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS)
1618 {
1619 tag = tag_title;
1620 level--;
1621 }
1622 else
1623 tag = csi->tagorder[level];
1624
1625 if (tag == menu_reload)
1626 return RELOAD_TAGTREE;
1627
1628 if (tag == tag_virt_basename) /* basename shortcut */
1629 {
1630 is_basename = true;
1631 tag = tag_filename;
1632 }
1633
1634 if (!tagcache_search(&tcs, tag))
1635 return -1;
1636
1637 /* Prevent duplicate entries in the search list. */
1638 tagcache_search_set_uniqbuf(&tcs, uniqbuf, UNIQBUF_SIZE);
1639
1640 if (level || is_basename|| csi->clause_count[0] || TAGCACHE_IS_NUMERIC(tag))
1641 sort = true;
1642
1643 for (i = 0; i < level; i++)
1644 {
1645 if (TAGCACHE_IS_NUMERIC(csi->tagorder[i]))
1646 {
1647 static struct tagcache_search_clause cc;
1648
1649 memset(&cc, 0, sizeof(struct tagcache_search_clause));
1650 cc.tag = csi->tagorder[i];
1651 cc.type = clause_is;
1652 cc.numeric = true;
1653 cc.numeric_data = csi->result_seek[i];
1654 tagcache_search_add_clause(&tcs, &cc);
1655 }
1656 else
1657 {
1658 tagcache_search_add_filter(&tcs, csi->tagorder[i],
1659 csi->result_seek[i]);
1660 }
1661 }
1662
1663 /* because tagcache saves the clauses, we need to lock the buffer
1664 * for the entire duration of the search */
1665 core_pin(tagtree_handle);
1666 for (i = 0; i <= level; i++)
1667 {
1668 int j;
1669
1670 for (j = 0; j < csi->clause_count[i]; j++)
1671 tagcache_search_add_clause(&tcs, csi->clause[i][j]);
1672 }
1673
1674 current_offset = offset;
1675 current_entry_count = 0;
1676 c->dirfull = false;
1677
1678 fmt = NULL;
1679 for (i = 0; i < format_count; i++)
1680 {
1681 if (c->currtable == TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS)
1682 {
1683 /* If it is sorted by albums, we need to use the proper view
1684 that includes the disc number etc so sorting will be correct for each albums.
1685 Otherwise, tracks will be sorted by title names */
1686 if (formats[i]->group_id != csi->format_id[level + 1])
1687 continue;
1688 }
1689 else if (formats[i]->group_id != csi->format_id[level])
1690 continue;
1691 fmt = formats[i];
1692 }
1693
1694 if (fmt)
1695 {
1696 sort_inverse = fmt->sort_inverse;
1697 sort_limit = fmt->limit;
1698 strip = fmt->strip;
1699 sort = true;
1700 }
1701 else
1702 {
1703 sort_inverse = false;
1704 sort_limit = 0;
1705 strip = 0;
1706 }
1707
1708 /* lock buflib out due to possible yields */
1709 tree_lock_cache(c);
1710 struct tagentry *dptr = core_get_data(c->cache.entries_handle);
1711
1712 if (tag != tag_title && tag != tag_filename)
1713 {
1714 bool show_album_sorted = (tag == tag_album);
1715 int show_album_sorted_offset = (show_album_sorted ? 1 : 0);
1716 if (offset == 0 && show_album_sorted)
1717 {
1718 dptr->newtable = TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS;
1719 dptr->name = ID2P(LANG_TAGNAVI_ALL_TRACKS_SORTED_BY_ALBUM);
1720 dptr->extraseek = 0;
1721 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1722 dptr++;
1723 current_entry_count++;
1724 c->special_entry_count++;
1725 }
1726 if (offset <= (show_album_sorted_offset))
1727 {
1728 dptr->newtable = TABLE_ALLSUBENTRIES;
1729 dptr->name = ID2P(LANG_TAGNAVI_ALL_TRACKS);
1730 dptr->extraseek = 0;
1731 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1732 dptr++;
1733 current_entry_count++;
1734 c->special_entry_count++;
1735 }
1736 if (offset <= (1 + show_album_sorted_offset))
1737 {
1738 dptr->newtable = TABLE_NAVIBROWSE;
1739 dptr->name = ID2P(LANG_TAGNAVI_RANDOM);
1740 dptr->extraseek = -1;
1741 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1742 dptr++;
1743 current_entry_count++;
1744 c->special_entry_count++;
1745 }
1746
1747 total_count += 2;
1748 if (show_album_sorted)
1749 total_count++;
1750 }
1751
1752 while (tagcache_get_next(&tcs, tcs_buf, tcs_bufsz))
1753 {
1754 if (total_count++ < offset)
1755 continue;
1756
1757 dptr->newtable = TABLE_NAVIBROWSE;
1758 if (tag == tag_title || tag == tag_filename)
1759 {
1760 dptr->newtable = TABLE_PLAYTRACK;
1761 dptr->extraseek = tcs.idx_id;
1762 }
1763 else
1764 dptr->extraseek = tcs.result_seek;
1765 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1766
1767 fmt = NULL;
1768 /* Check the format */
1769 for (i = 0; i < format_count; i++)
1770 {
1771 if (c->currtable == TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS)
1772 {
1773 /* If it is sorted by albums, we need to use the proper view
1774 that includes the disc number etc so sorting will be correct for each albums.
1775 Otherwise, tracks will be sorted by title names */
1776 if (formats[i]->group_id != csi->format_id[level + 1])
1777 continue;
1778 }
1779 else if (formats[i]->group_id != csi->format_id[level])
1780 continue;
1781
1782 if (tagcache_check_clauses(&tcs, formats[i]->clause,
1783 formats[i]->clause_count))
1784 {
1785 fmt = formats[i];
1786 break;
1787 }
1788 }
1789
1790 if (strcmp(tcs.result, UNTAGGED) == 0)
1791 {
1792 if (tag == tag_title && tcs.type == tag_title && tcs.filter_count <= 1)
1793 { /* Fallback to basename */
1794 char *lastname = dptr->name;
1795 dptr->name = core_get_data(c->cache.name_buffer_handle)+namebufused;
1796 if ((c->cache.name_buffer_size - namebufused) > 0 &&
1797 tagcache_retrieve(&tcs, tcs.idx_id, tag_virt_basename, dptr->name,
1798 c->cache.name_buffer_size - namebufused))
1799 {
1800 namebufused += strlen(dptr->name)+1;
1801 dptr->album_name = core_get_data(c->cache.name_buffer_handle)+namebufused;
1802 if ((c->cache.name_buffer_size - namebufused) > 0 &&
1803 tagcache_retrieve(&tcs, tcs.idx_id, tag_album, dptr->album_name,
1804 c->cache.name_buffer_size - namebufused))
1805 namebufused += strlen(dptr->album_name)+1;
1806 else
1807 dptr->album_name = NULL;
1808 goto entry_skip_formatter;
1809 }
1810 dptr->name = lastname; /* restore last entry if filename failed */
1811 dptr->album_name = NULL;
1812 }
1813
1814 tcs.result = str(LANG_TAGNAVI_UNTAGGED);
1815 tcs.result_len = strlen(tcs.result);
1816 tcs.ramresult = true;
1817 }
1818
1819 if (!tcs.ramresult || fmt)
1820 {
1821
1822 dptr->name = core_get_data(c->cache.name_buffer_handle)+namebufused;
1823
1824 if (fmt)
1825 {
1826 int ret = format_str(&tcs, fmt, dptr->name,
1827 c->cache.name_buffer_size - namebufused);
1828 bool error_on_str_format = ret < 0;
1829 if (!error_on_str_format)
1830 {
1831 namebufused += strlen(dptr->name)+1; /* include NULL */
1832 dptr->album_name = core_get_data(c->cache.name_buffer_handle)+namebufused;
1833 if ((c->cache.name_buffer_size - namebufused) > 0 &&
1834 tagcache_retrieve(&tcs, tcs.idx_id, tag_album, dptr->album_name,
1835 c->cache.name_buffer_size - namebufused))
1836 namebufused += strlen(dptr->album_name)+1;
1837 else
1838 dptr->album_name = NULL;
1839 }
1840 else
1841 {
1842 if (ret == -4) /* buffer full */
1843 {
1844 logf("chunk mode #2: %d", current_entry_count);
1845 c->dirfull = true;
1846 sort = false;
1847 break;
1848 }
1849
1850 logf("format_str() failed");
1851 tagcache_search_finish(&tcs);
1852 tree_unlock_cache(c);
1853 core_unpin(tagtree_handle);
1854 return 0;
1855 }
1856 }
1857 else
1858 {
1859 tcs_get_basename(&tcs, is_basename);
1860 namebufused += tcs.result_len;
1861 bool buffer_full = (namebufused >= c->cache.name_buffer_size);
1862 if (!buffer_full)
1863 {
1864 dptr->album_name = core_get_data(c->cache.name_buffer_handle)+namebufused;
1865 if (tagcache_retrieve(&tcs, tcs.idx_id, tag_album, dptr->album_name,
1866 c->cache.name_buffer_size - namebufused))
1867 namebufused += strlen(dptr->album_name)+1;
1868 else
1869 dptr->album_name = NULL;
1870 strcpy(dptr->name, tcs.result);
1871 }
1872 else
1873 {
1874 logf("chunk mode #2a: %d", current_entry_count);
1875 c->dirfull = true;
1876 sort = false;
1877 break ;
1878 }
1879 }
1880 }
1881 else
1882 {
1883 tcs_get_basename(&tcs, is_basename);
1884 dptr->name = tcs.result;
1885 dptr->album_name = NULL;
1886 }
1887entry_skip_formatter:
1888 dptr++;
1889 current_entry_count++;
1890
1891 if (current_entry_count >= c->cache.max_entries)
1892 {
1893 logf("chunk mode #3: %d", current_entry_count);
1894 c->dirfull = true;
1895 sort = false;
1896 break ;
1897 }
1898
1899 if (init)
1900 {
1901 if (!show_search_progress(false, total_count, 0, 0))
1902 { /* user aborted */
1903 tagcache_search_finish(&tcs);
1904 tree_unlock_cache(c);
1905 core_unpin(tagtree_handle);
1906 return current_entry_count;
1907 }
1908 }
1909 }
1910
1911 if (sort)
1912 {
1913 if (global_settings.interpret_numbers)
1914 qsort_fn = sort_inverse ? strnatcasecmp_n_inv : strnatcasecmp_n;
1915 else
1916 qsort_fn = sort_inverse ? strncasecmp_inv : strncasecmp;
1917
1918 struct tagentry *entries = get_entries(c);
1919 qsort(&entries[c->special_entry_count],
1920 current_entry_count - c->special_entry_count,
1921 sizeof(struct tagentry),
1922 c->currtable == TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS ? compare_with_albums : compare
1923 );
1924 }
1925
1926 if (!init)
1927 {
1928 tagcache_search_finish(&tcs);
1929 tree_unlock_cache(c);
1930 core_unpin(tagtree_handle);
1931 return current_entry_count;
1932 }
1933
1934 while (tagcache_get_next(&tcs, tcs_buf, tcs_bufsz))
1935 {
1936 if (!show_search_progress(false, total_count, 0, 0))
1937 break;
1938 total_count++;
1939 }
1940
1941 tagcache_search_finish(&tcs);
1942 tree_unlock_cache(c);
1943 core_unpin(tagtree_handle);
1944
1945 if (!sort && (sort_inverse || sort_limit))
1946 {
1947 if (global_settings.talk_menu) {
1948 talk_id(LANG_SHOWDIR_BUFFER_FULL, true);
1949 talk_value(total_count, UNIT_INT, true);
1950 }
1951
1952 /* (voiced above) */
1953 splashf(HZ*4, str(LANG_SHOWDIR_BUFFER_FULL), total_count);
1954 logf("Too small dir buffer");
1955 return 0;
1956 }
1957
1958 if (sort_limit)
1959 total_count = MIN(total_count, sort_limit);
1960
1961 if (strip)
1962 {
1963 dptr = get_entries(c);
1964 for (i = c->special_entry_count; i < current_entry_count; i++, dptr++)
1965 {
1966 int len = strlen(dptr->name);
1967
1968 if (len < strip)
1969 continue;
1970
1971 dptr->name = &dptr->name[strip];
1972 }
1973 }
1974
1975 return total_count;
1976
1977}
1978
1979static int load_root(struct tree_context *c)
1980{
1981 struct tagentry *dptr = core_get_data(c->cache.entries_handle);
1982 int i;
1983
1984 tc = c;
1985 c->currtable = TABLE_ROOT;
1986 if (c->dirlevel == 0)
1987 c->currextra = rootmenu;
1988
1989 menu = menus[c->currextra];
1990 if (menu == NULL)
1991 return 0;
1992
1993 if (menu->itemcount > c->cache.max_entries)
1994 panicf("%s tree_cache too small", __func__);
1995
1996 for (i = 0; i < menu->itemcount; i++)
1997 {
1998
1999 dptr->name = (char*)menu->items[i]->name;
2000
2001 logf( "%s loading menu %d name: %s, lang_id %ld", __func__, i,
2002 P2STR((unsigned char*)dptr->name),P2ID((unsigned char*)dptr->name));
2003
2004 switch (menu->items[i]->type)
2005 {
2006 case menu_next:
2007 dptr->newtable = TABLE_NAVIBROWSE;
2008 dptr->extraseek = i;
2009 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
2010 break;
2011
2012 case menu_load:
2013 dptr->newtable = TABLE_ROOT;
2014 dptr->extraseek = menu->items[i]->link;
2015 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
2016 break;
2017
2018 case menu_shuffle_songs:
2019 dptr->newtable = TABLE_NAVIBROWSE;
2020 dptr->extraseek = i;
2021 dptr->customaction = ONPLAY_CUSTOMACTION_SHUFFLE_SONGS;
2022 break;
2023
2024 case menu_byfirstletter:
2025 dptr->newtable = TABLE_NAVIBROWSE;
2026 dptr->extraseek = i;
2027 dptr->customaction = ONPLAY_CUSTOMACTION_FIRSTLETTER;
2028 break;
2029 }
2030
2031 dptr++;
2032 }
2033
2034 current_offset = 0;
2035 current_entry_count = i;
2036
2037 return i;
2038}
2039
2040int tagtree_load(struct tree_context* c)
2041{
2042 logf( "%s", __func__);
2043
2044 int count;
2045 int table = c->currtable;
2046
2047 c->dirsindir = 0;
2048
2049 if (!table)
2050 {
2051 c->dirfull = false;
2052 table = TABLE_ROOT;
2053 c->currtable = table;
2054 c->currextra = rootmenu;
2055 }
2056
2057 switch (table)
2058 {
2059 case TABLE_ROOT:
2060 logf( "root...");
2061 count = load_root(c);
2062 break;
2063
2064 case TABLE_ALLSUBENTRIES:
2065 case TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS:
2066 case TABLE_NAVIBROWSE:
2067 logf("navibrowse...");
2068
2069 if (loaded_entries_crc != 0)
2070 {
2071 if (loaded_entries_crc == tagtree_data_crc(c))
2072 {
2073 count = c->dirlength;
2074 logf("Reusing %d entries", count);
2075 break;
2076 }
2077 }
2078
2079 cpu_boost(true);
2080 count = retrieve_entries(c, 0, true);
2081 cpu_boost(false);
2082 break;
2083
2084 default:
2085 logf("Unsupported table %d\n", table);
2086 return -1;
2087 }
2088
2089 loaded_entries_crc = 0;
2090
2091 if (count < 0)
2092 {
2093 if (count != RELOAD_TAGTREE)
2094 splash(HZ, ID2P(LANG_TAGCACHE_BUSY));
2095 else /* unload and re-init tagtree */
2096 {
2097 splash(HZ, ID2P(LANG_WAIT));
2098 tagtree_unload(c);
2099 if (!initialize_tagtree())
2100 return 0;
2101 }
2102 c->dirlevel = 0;
2103 count = load_root(c);
2104 }
2105
2106 /* The _total_ numer of entries available. */
2107 c->dirlength = c->filesindir = count;
2108
2109 return count;
2110}
2111
2112/* Enters menu or table for selected item in the database.
2113 *
2114 * Call this with the is_visible parameter set to false to
2115 * prevent selected_item_history from being updated or applied, in
2116 * case the menus aren't displayed to the user.
2117 * Before calling tagtree_enter again with the parameter set to
2118 * true, make sure that you are back at the previous dirlevel, by
2119 * calling tagtree_exit as needed, with is_visible set to false.
2120 */
2121int tagtree_enter(struct tree_context* c, bool is_visible)
2122{
2123 logf( "%s", __func__);
2124
2125 int rc = 0;
2126 struct tagentry *dptr;
2127 struct mp3entry *id3;
2128 int newextra;
2129 int seek;
2130 int source;
2131 bool is_random_item = false;
2132 bool adjust_selection = true;
2133
2134 dptr = tagtree_get_entry(c, c->selected_item);
2135
2136 c->dirfull = false;
2137 seek = dptr->extraseek;
2138 if (seek == -1) /* <Random> menu item was selected */
2139 {
2140 is_random_item = true;
2141 if(c->filesindir<=c->special_entry_count) /* Menu contains only special entries */
2142 return 0;
2143 srand(current_tick);
2144 dptr = (tagtree_get_entry(c, c->special_entry_count+(rand() % (c->filesindir-c->special_entry_count))));
2145 seek = dptr->extraseek;
2146 }
2147 newextra = dptr->newtable;
2148
2149 if (c->dirlevel >= MAX_DIR_LEVELS)
2150 return 0;
2151
2152 if (is_visible) /* update selection history only for user-selected items */
2153 {
2154 /* We need to discard selected item history for levels
2155 descending from current one if selection has changed */
2156 if (max_history_level < c->dirlevel + 1
2157 || (max_history_level > c->dirlevel
2158 && selected_item_history[c->dirlevel] != c->selected_item)
2159 || is_random_item)
2160 {
2161 max_history_level = c->dirlevel + 1;
2162 if (max_history_level < MAX_DIR_LEVELS)
2163 selected_item_history[max_history_level] = 0;
2164 }
2165
2166 selected_item_history[c->dirlevel]=c->selected_item;
2167 }
2168 table_history[c->dirlevel] = c->currtable;
2169 extra_history[c->dirlevel] = c->currextra;
2170
2171 if (c->dirlevel + 1 < MAX_DIR_LEVELS)
2172 {
2173 c->dirlevel++;
2174 /*DEBUGF("Tagtree depth %d\n", c->dirlevel);*/
2175 }
2176 else
2177 {
2178 DEBUGF("Tagtree depth exceeded\n");
2179 }
2180
2181 /* lock buflib for possible I/O to protect dptr */
2182 tree_lock_cache(c);
2183 core_pin(tagtree_handle);
2184
2185 switch (c->currtable) {
2186 case TABLE_ROOT:
2187 c->currextra = newextra;
2188
2189 if (newextra == TABLE_ROOT)
2190 {
2191 menu = menus[seek];
2192 c->currextra = seek;
2193 }
2194
2195 else if (newextra == TABLE_NAVIBROWSE)
2196 {
2197 int i, j;
2198
2199 csi = &menu->items[seek]->si;
2200 c->currextra = 0;
2201
2202 unsigned char *name = dptr->name;
2203
2204 strmemccpy(current_title[c->currextra], P2STR(name),
2205 sizeof(current_title[0]));
2206
2207 logf("%s (ROOT) current title %s", __func__, P2STR(name));
2208
2209 /* Read input as necessary. */
2210 for (i = 0; i < csi->tagorder_count; i++)
2211 {
2212 for (j = 0; j < csi->clause_count[i]; j++)
2213 {
2214 char* searchstring;
2215
2216 if (csi->clause[i][j]->type == clause_logical_or)
2217 continue;
2218
2219 source = csi->clause[i][j]->source;
2220
2221 if (source == source_constant)
2222 continue;
2223
2224 /* discard history for lower levels when doing runtime searches */
2225 if (is_visible)
2226 max_history_level = c->dirlevel - 1;
2227
2228 searchstring=csi->clause[i][j]->str;
2229 *searchstring = '\0';
2230
2231 id3 = audio_current_track();
2232
2233 if (source == source_current_path && id3)
2234 {
2235 char *e;
2236 strmemccpy(searchstring, id3->path, SEARCHSTR_SIZE);
2237 e = strrchr(searchstring, '/');
2238 if (e)
2239 *e = '\0';
2240 }
2241 else if (source > source_runtime && id3)
2242 {
2243
2244 int k = source-source_runtime;
2245 int offset = id3_to_search_mapping[k].id3_offset;
2246 char **src = (char**)((char*)id3 + offset);
2247 if (*src)
2248 {
2249 strmemccpy(searchstring, *src, SEARCHSTR_SIZE);
2250 }
2251 }
2252 else
2253 {
2254 rc = kbd_input(searchstring, SEARCHSTR_SIZE, NULL);
2255 if (rc < 0 || !searchstring[0])
2256 {
2257 tagtree_exit(c, is_visible);
2258 tree_unlock_cache(c);
2259 core_unpin(tagtree_handle);
2260 return 0;
2261 }
2262 if (csi->clause[i][j]->numeric)
2263 csi->clause[i][j]->numeric_data = atoi(searchstring);
2264 }
2265
2266
2267 }
2268 }
2269 }
2270 c->currtable = newextra;
2271
2272 break;
2273
2274 case TABLE_NAVIBROWSE:
2275 case TABLE_ALLSUBENTRIES:
2276 case TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS:
2277 if (newextra == TABLE_PLAYTRACK)
2278 {
2279 adjust_selection = false;
2280
2281 if (global_settings.party_mode && audio_status()) {
2282 splash(HZ, ID2P(LANG_PARTY_MODE));
2283 break;
2284 }
2285 c->dirlevel--;
2286 /* about to create a new current playlist...
2287 allow user to cancel the operation */
2288 if (!warn_on_pl_erase())
2289 break;
2290 if (tagtree_play_folder(c) >= 0)
2291 rc = 2;
2292 break;
2293 }
2294
2295 c->currtable = newextra;
2296 csi->result_seek[c->currextra] = seek;
2297 if (c->currextra < csi->tagorder_count-1)
2298 c->currextra++;
2299 else
2300 c->dirlevel--;
2301
2302 unsigned char *name = dptr->name;
2303 name = P2STR(name);
2304 logf("%s (NAVI/ALLSUB) current title %s", __func__, name);
2305 /* Update the statusbar title */
2306 strmemccpy(current_title[c->currextra], name, sizeof(current_title[0]));
2307 break;
2308
2309 default:
2310 c->dirlevel--;
2311 break;
2312 }
2313
2314 if (adjust_selection)
2315 {
2316 if (is_visible && c->dirlevel <= max_history_level)
2317 c->selected_item = selected_item_history[c->dirlevel];
2318 else
2319 c->selected_item = 0;
2320 }
2321
2322 tree_unlock_cache(c);
2323 core_unpin(tagtree_handle);
2324
2325 return rc;
2326}
2327
2328/* Exits current database menu or table */
2329void tagtree_exit(struct tree_context* c, bool is_visible)
2330{
2331 logf( "%s", __func__);
2332 if (is_visible) /* update selection history only for user-selected items */
2333 {
2334 if (c->selected_item != selected_item_history[c->dirlevel])
2335 max_history_level = c->dirlevel; /* discard descending item history */
2336 selected_item_history[c->dirlevel] = c->selected_item;
2337 }
2338 c->dirfull = false;
2339 if (c->dirlevel > 0)
2340 {
2341 c->dirlevel--;
2342 /*DEBUGF("Tagtree depth %d\n", c->dirlevel);*/
2343 }
2344 else
2345 {
2346 DEBUGF("Tagtree nothing to exit\n");
2347 }
2348
2349 if (is_visible)
2350 c->selected_item = selected_item_history[c->dirlevel];
2351 c->currtable = table_history[c->dirlevel];
2352 c->currextra = extra_history[c->dirlevel];
2353}
2354
2355int tagtree_get_filename(struct tree_context* c, char *buf, int buflen)
2356{
2357 struct tagcache_search tcs;
2358 int extraseek = tagtree_get_entry(c, c->selected_item)->extraseek;
2359
2360
2361 if (!tagcache_search(&tcs, tag_filename))
2362 return -1;
2363
2364 if (!tagcache_retrieve(&tcs, extraseek, tcs.type, buf, buflen))
2365 {
2366 tagcache_search_finish(&tcs);
2367 return -2;
2368 }
2369
2370 tagcache_search_finish(&tcs);
2371
2372 return 0;
2373}
2374
2375int tagtree_get_custom_action(struct tree_context* c)
2376{
2377 if (c->dirlength == 0)
2378 return 0;
2379 return tagtree_get_entry(c, c->selected_item)->customaction;
2380}
2381
2382static void swap_array_bool(bool *a, bool *b)
2383{
2384 bool temp = *a;
2385 *a = *b;
2386 *b = temp;
2387}
2388
2389/**
2390 * Randomly shuffle an array using the Fisher-Yates algorithm :
2391 * https://en.wikipedia.org/wiki/Random_permutation
2392 * This algorithm has a linear complexity.
2393 * Don't forget to srand before call to use it with a relevant seed.
2394 */
2395static bool* fill_random_playlist_indexes(bool *bool_array, size_t arr_sz,
2396 size_t track_count, size_t max_slots)
2397{
2398 size_t i;
2399 if (track_count * sizeof(bool) > arr_sz || max_slots > track_count)
2400 return NULL;
2401
2402 for (i = 0; i < arr_sz; i++) /* fill max_slots with TRUE */
2403 bool_array[i] = i < max_slots;
2404
2405 /* shuffle bool array */
2406 for (i = track_count - 1; i > 0; i--)
2407 {
2408 int j = rand() % (i + 1);
2409 swap_array_bool(&bool_array[i], &bool_array[j]);
2410 }
2411 return bool_array;
2412}
2413
2414static bool insert_all_playlist(struct tree_context *c,
2415 const char* playlist, bool new_playlist,
2416 int position, bool queue)
2417{
2418 struct tagcache_search tcs;
2419 int n;
2420 int fd = -1;
2421 unsigned long last_tick;
2422 int slots_remaining = 0;
2423 bool fill_randomly = false;
2424 bool *rand_bool_array = NULL;
2425 char buf[MAX_PATH];
2426 struct playlist_insert_context context;
2427
2428 cpu_boost(true);
2429
2430 if (!tagcache_search(&tcs, tag_filename))
2431 {
2432 splash(HZ, ID2P(LANG_TAGCACHE_BUSY));
2433 cpu_boost(false);
2434 return false;
2435 } /* NOTE: you need to close this search before returning */
2436
2437 if (playlist == NULL)
2438 {
2439 if (playlist_insert_context_create(NULL, &context, position, queue, false) < 0)
2440 {
2441 tagcache_search_finish(&tcs);
2442 cpu_boost(false);
2443 return false;
2444 }
2445 }
2446 else
2447 {
2448 if (new_playlist)
2449 fd = open_utf8(playlist, O_CREAT|O_WRONLY|O_TRUNC);
2450 else
2451 fd = open(playlist, O_CREAT|O_WRONLY|O_APPEND, 0666);
2452 if(fd < 0)
2453 {
2454 tagcache_search_finish(&tcs);
2455 cpu_boost(false);
2456 return false;
2457 }
2458 }
2459
2460 last_tick = current_tick + HZ/2; /* Show splash after 0.5 seconds have passed */
2461 splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */
2462 n = c->filesindir;
2463
2464 if (playlist == NULL)
2465 {
2466 int max_playlist_size = playlist_get_current()->max_playlist_size;
2467 slots_remaining = max_playlist_size - playlist_get_current()->amount;
2468 if (slots_remaining <= 0)
2469 {
2470 logf("Playlist has no space remaining");
2471 tagcache_search_finish(&tcs);
2472 cpu_boost(false);
2473 return false;
2474 }
2475
2476 fill_randomly = n > slots_remaining;
2477
2478 if (fill_randomly)
2479 {
2480 srand(current_tick);
2481 size_t bufsize = 0;
2482 bool *buffer = (bool *) plugin_get_buffer(&bufsize);
2483 rand_bool_array = fill_random_playlist_indexes(buffer, bufsize,
2484 n, slots_remaining);
2485
2486 splashf(HZ * 2, ID2P(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY),
2487 slots_remaining); /* voiced above */
2488 }
2489 }
2490
2491 bool exit_loop_now = false;
2492 for (int i = 0; i < n; i++)
2493 {
2494 if (TIME_AFTER(current_tick, last_tick + HZ/4))
2495 {
2496 /* (voiced) */
2497 splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT));
2498 if (action_userabort(TIMEOUT_NOBLOCK))
2499 {
2500 exit_loop_now = true;
2501 break;
2502 }
2503 last_tick = current_tick;
2504 }
2505
2506 if (playlist == NULL)
2507 {
2508 if (fill_randomly)
2509 {
2510 int remaining_tracks = n - i;
2511 if (remaining_tracks > slots_remaining)
2512 {
2513 if (rand_bool_array)
2514 {
2515 /* Skip the track if rand_bool_array[i] is FALSE */
2516 if (!rand_bool_array[i])
2517 continue;
2518 }
2519 else
2520 {
2521 /* Generate random value between 0 and remaining_tracks - 1 */
2522 int selrange = RAND_MAX / remaining_tracks; /* Improve distribution */
2523 int random;
2524
2525 for (int r = 0; r < 0x0FFF; r++) /* limit loops */
2526 {
2527 random = rand() / selrange;
2528 if (random < remaining_tracks)
2529 break;
2530 else
2531 random = 0;
2532 }
2533 /* Skip the track if random >= slots_remaining */
2534 if (random >= slots_remaining)
2535 continue;
2536 }
2537 }
2538 }
2539 }
2540
2541 if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, i)->extraseek, tcs.type, buf, sizeof buf))
2542 continue;
2543
2544 if (playlist == NULL)
2545 {
2546 if (fill_randomly)
2547 {
2548 if (--slots_remaining <= 0)
2549 {
2550 exit_loop_now = true;
2551 break;
2552 }
2553 }
2554
2555 if (playlist_insert_context_add(&context, buf) < 0) {
2556 logf("playlist_insert_track failed");
2557 exit_loop_now = true;
2558 break;
2559 }
2560 }
2561 else if (fdprintf(fd, "%s\n", buf) <= 0)
2562 {
2563 exit_loop_now = true;
2564 break;
2565 }
2566 yield();
2567
2568 if (exit_loop_now)
2569 break;
2570 }
2571
2572 if (playlist == NULL)
2573 playlist_insert_context_release(&context);
2574 else
2575 close(fd);
2576
2577 tagcache_search_finish(&tcs);
2578 cpu_boost(false);
2579
2580 return true;
2581}
2582
2583static bool goto_allsubentries(int newtable)
2584{
2585 int i = 0;
2586 while (i < 2 && (newtable == TABLE_NAVIBROWSE || newtable == TABLE_ALLSUBENTRIES
2587 || newtable == TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS))
2588 {
2589 tagtree_enter(tc, false);
2590 tagtree_load(tc);
2591 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2592 i++;
2593 }
2594 return (newtable == TABLE_PLAYTRACK);
2595}
2596
2597static void reset_tc_to_prev(int dirlevel, int selected_item)
2598{
2599 while (tc->dirlevel > dirlevel)
2600 tagtree_exit(tc, false);
2601 tc->selected_item = selected_item;
2602 tagtree_load(tc);
2603}
2604
2605static bool tagtree_insert_selection(int position, bool queue,
2606 const char* playlist, bool new_playlist)
2607{
2608 char buf[MAX_PATH];
2609 int dirlevel = tc->dirlevel;
2610 int selected_item = tc->selected_item;
2611 int newtable;
2612 int ret;
2613
2614 show_search_progress(
2615#ifdef HAVE_DISK_STORAGE
2616 storage_disk_is_active()
2617#else
2618 true
2619#endif
2620 , 0, 0, 0);
2621
2622 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2623
2624 if (newtable == TABLE_PLAYTRACK) /* Insert a single track? */
2625 {
2626 if (tagtree_get_filename(tc, buf, sizeof buf) < 0)
2627 return false;
2628
2629 if (!playlist)
2630 playlist_insert_track(NULL, buf, position, queue, true);
2631 else
2632 catalog_insert_into(playlist, new_playlist, buf, FILE_ATTR_AUDIO);
2633
2634 return true;
2635 }
2636
2637 ret = goto_allsubentries(newtable);
2638 if (ret)
2639 {
2640 if (tc->filesindir <= 0)
2641 splash(HZ, ID2P(LANG_END_PLAYLIST));
2642 else if (!insert_all_playlist(tc, playlist, new_playlist, position, queue))
2643 splash(HZ*2, ID2P(LANG_FAILED));
2644 }
2645
2646 reset_tc_to_prev(dirlevel, selected_item);
2647 return ret;
2648}
2649
2650/* Execute action_cb for all subentries of the current table's
2651 * selected item, handing over each entry's filename in the
2652 * callback function parameter. Parameter will be NULL for
2653 * entries whose filename couldn't be retrieved.
2654 */
2655bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name))
2656{
2657 struct tagcache_search tcs;
2658 int i, n;
2659 unsigned long last_tick;
2660 char buf[MAX_PATH];
2661 int ret = true;
2662 int dirlevel = tc->dirlevel;
2663 int selected_item = tc->selected_item;
2664 int newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2665
2666 cpu_boost(true);
2667 if (!goto_allsubentries(newtable))
2668 ret = false;
2669 else if (tagcache_search(&tcs, tag_filename))
2670 {
2671 last_tick = current_tick + HZ/2;
2672 splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */
2673 n = tc->filesindir;
2674 for (i = 0; i < n; i++)
2675 {
2676 /* (voiced) */
2677 splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT));
2678 if (TIME_AFTER(current_tick, last_tick + HZ/4))
2679 {
2680 if (action_userabort(TIMEOUT_NOBLOCK))
2681 break;
2682 last_tick = current_tick;
2683 }
2684
2685 if (!action_cb(tagcache_retrieve(&tcs, tagtree_get_entry(tc, i)->extraseek,
2686 tcs.type, buf, sizeof buf) ? buf : NULL))
2687 {
2688 ret = false;
2689 break;
2690 }
2691 yield();
2692 }
2693
2694 tagcache_search_finish(&tcs);
2695 }
2696 else
2697 {
2698 splash(HZ, ID2P(LANG_TAGCACHE_BUSY));
2699 ret = false;
2700 }
2701 reset_tc_to_prev(dirlevel, selected_item);
2702 cpu_boost(false);
2703 return ret;
2704}
2705
2706/* Try to return first subentry's filename for current selection
2707 */
2708bool tagtree_get_subentry_filename(char *buf, size_t bufsize)
2709{
2710 int ret = true;
2711 int dirlevel = tc->dirlevel;
2712 int selected_item = tc->selected_item;
2713 int newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2714
2715 if (!goto_allsubentries(newtable) || tagtree_get_filename(tc, buf, bufsize) < 0)
2716 ret = false;
2717
2718 reset_tc_to_prev(dirlevel, selected_item);
2719 return ret;
2720}
2721
2722bool tagtree_current_playlist_insert(int position, bool queue)
2723{
2724 return tagtree_insert_selection(position, queue, NULL, false);
2725}
2726
2727
2728int tagtree_add_to_playlist(const char* playlist, bool new_playlist)
2729{
2730 if (!new_playlist)
2731 tagtree_load(tc); /* because display_playlists was called */
2732 return tagtree_insert_selection(0, false, playlist, new_playlist) ? 0 : -1;
2733}
2734
2735static int tagtree_play_folder(struct tree_context* c)
2736{
2737 logf( "%s", __func__);
2738 int start_index = c->selected_item;
2739
2740 if (playlist_create(NULL, NULL) < 0)
2741 {
2742 logf("Failed creating playlist\n");
2743 return -1;
2744 }
2745
2746 if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false))
2747 return -2;
2748
2749 int n = c->filesindir;
2750 bool has_playlist_been_randomized = n > playlist_get_current()->max_playlist_size;
2751 if (has_playlist_been_randomized)
2752 {
2753 /* We need to recalculate the start index based on a percentage to put the user
2754 around its desired start position and avoid out of bounds */
2755
2756 int percentage_start_index = 100 * start_index / n;
2757 start_index = percentage_start_index * playlist_get_current()->amount / 100;
2758 }
2759
2760 if (global_settings.playlist_shuffle)
2761 {
2762 start_index = playlist_shuffle(current_tick, start_index);
2763 if (!global_settings.play_selected)
2764 start_index = 0;
2765 }
2766
2767 playlist_start(start_index, 0, 0);
2768 loaded_entries_crc = tagtree_data_crc(c); /* save crc in case we return */
2769 return 0;
2770}
2771
2772static struct tagentry* tagtree_get_entry(struct tree_context *c, int id)
2773{
2774 struct tagentry *entry;
2775 int realid = id - current_offset;
2776
2777 /* Load the next chunk if necessary. */
2778 if (realid >= current_entry_count || realid < 0)
2779 {
2780 cpu_boost(true);
2781 if (retrieve_entries(c, MAX(0, id - (current_entry_count / 2)),
2782 false) < 0)
2783 {
2784 logf("retrieve failed");
2785 cpu_boost(false);
2786 return NULL;
2787 }
2788 realid = id - current_offset;
2789 cpu_boost(false);
2790 }
2791
2792 entry = get_entries(c);
2793 return &entry[realid];
2794}
2795
2796char* tagtree_get_entry_name(struct tree_context *c, int id,
2797 char* buf, size_t bufsize)
2798{
2799 struct tagentry *entry = tagtree_get_entry(c, id);
2800 if (!entry)
2801 return NULL;
2802
2803 unsigned char *name = entry->name;
2804
2805 int lang_id = P2ID(name);
2806 logf("%s: '%s' id: %d\n", __func__,
2807 P2STR(name), lang_id);
2808 if (lang_id >= 0)
2809 {
2810 strmemccpy(buf,P2STR(name), bufsize);
2811 return entry->name;
2812 }
2813
2814 strmemccpy(buf, entry->name, bufsize);
2815 return buf;
2816}
2817
2818
2819char *tagtree_get_title(struct tree_context* c)
2820{
2821 switch (c->currtable)
2822 {
2823 case TABLE_ROOT:
2824 logf("%s (ROOT) %s", __func__, P2STR(menu->title));
2825 return P2STR(menu->title);
2826
2827 case TABLE_NAVIBROWSE:
2828 case TABLE_ALLSUBENTRIES:
2829 case TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS:
2830 logf("%s (NAVI/ALLSUB) idx: %d %s", __func__,
2831 c->currextra, current_title[c->currextra]);
2832 return current_title[c->currextra];
2833 }
2834
2835 return "?";
2836}
2837
2838int tagtree_get_attr(struct tree_context* c)
2839{
2840 int attr = -1;
2841 switch (c->currtable)
2842 {
2843 case TABLE_NAVIBROWSE:
2844 if (csi->tagorder[c->currextra] == tag_title
2845 || csi->tagorder[c->currextra] == tag_virt_basename)
2846 attr = FILE_ATTR_AUDIO;
2847 else
2848 attr = ATTR_DIRECTORY;
2849 break;
2850
2851 case TABLE_ALLSUBENTRIES:
2852 case TABLE_ALLSUBENTRIES_SORTED_BY_ALBUMS:
2853 attr = FILE_ATTR_AUDIO;
2854 break;
2855
2856 default:
2857 attr = ATTR_DIRECTORY;
2858 break;
2859 }
2860
2861 return attr;
2862}
2863
2864int tagtree_get_icon(struct tree_context* c)
2865{
2866 int icon = Icon_Folder;
2867
2868 if (tagtree_get_attr(c) == FILE_ATTR_AUDIO)
2869 icon = Icon_Audio;
2870
2871 return icon;
2872}