A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 2872 lines 81 kB view raw
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}