diff options
Diffstat (limited to 'apps/tagtree.c')
-rw-r--r-- | apps/tagtree.c | 417 |
1 files changed, 333 insertions, 84 deletions
diff --git a/apps/tagtree.c b/apps/tagtree.c index ea2a756a46..60f8a795e3 100644 --- a/apps/tagtree.c +++ b/apps/tagtree.c | |||
@@ -56,6 +56,8 @@ | |||
56 | #include "playback.h" | 56 | #include "playback.h" |
57 | #include "strnatcmp.h" | 57 | #include "strnatcmp.h" |
58 | #include "panic.h" | 58 | #include "panic.h" |
59 | #include "onplay.h" | ||
60 | #include "plugin.h" | ||
59 | 61 | ||
60 | #define str_or_empty(x) (x ? x : "(NULL)") | 62 | #define str_or_empty(x) (x ? x : "(NULL)") |
61 | 63 | ||
@@ -64,6 +66,10 @@ | |||
64 | 66 | ||
65 | static int tagtree_play_folder(struct tree_context* c); | 67 | static int tagtree_play_folder(struct tree_context* c); |
66 | 68 | ||
69 | /* reuse of tagtree data after tagtree_play_folder() */ | ||
70 | static uint32_t loaded_entries_crc = 0; | ||
71 | |||
72 | |||
67 | /* this needs to be same size as struct entry (tree.h) and name needs to be | 73 | /* this needs to be same size as struct entry (tree.h) and name needs to be |
68 | * the first; so that they're compatible enough to walk arrays of both | 74 | * the first; so that they're compatible enough to walk arrays of both |
69 | * derefencing the name member*/ | 75 | * derefencing the name member*/ |
@@ -71,6 +77,7 @@ struct tagentry { | |||
71 | char* name; | 77 | char* name; |
72 | int newtable; | 78 | int newtable; |
73 | int extraseek; | 79 | int extraseek; |
80 | int customaction; | ||
74 | }; | 81 | }; |
75 | 82 | ||
76 | static struct tagentry* tagtree_get_entry(struct tree_context *c, int id); | 83 | static struct tagentry* tagtree_get_entry(struct tree_context *c, int id); |
@@ -78,10 +85,10 @@ static struct tagentry* tagtree_get_entry(struct tree_context *c, int id); | |||
78 | #define SEARCHSTR_SIZE 256 | 85 | #define SEARCHSTR_SIZE 256 |
79 | 86 | ||
80 | enum table { | 87 | enum table { |
81 | ROOT = 1, | 88 | TABLE_ROOT = 1, |
82 | NAVIBROWSE, | 89 | TABLE_NAVIBROWSE, |
83 | ALLSUBENTRIES, | 90 | TABLE_ALLSUBENTRIES, |
84 | PLAYTRACK, | 91 | TABLE_PLAYTRACK, |
85 | }; | 92 | }; |
86 | 93 | ||
87 | static const struct id3_to_search_mapping { | 94 | static const struct id3_to_search_mapping { |
@@ -105,9 +112,11 @@ enum variables { | |||
105 | var_include, | 112 | var_include, |
106 | var_rootmenu, | 113 | var_rootmenu, |
107 | var_format, | 114 | var_format, |
115 | menu_byfirstletter, | ||
108 | menu_next, | 116 | menu_next, |
109 | menu_load, | 117 | menu_load, |
110 | menu_reload, | 118 | menu_reload, |
119 | menu_shuffle_songs, | ||
111 | }; | 120 | }; |
112 | 121 | ||
113 | /* Capacity 10 000 entries (for example 10k different artists) */ | 122 | /* Capacity 10 000 entries (for example 10k different artists) */ |
@@ -165,11 +174,12 @@ struct display_format { | |||
165 | static struct display_format *formats[TAGMENU_MAX_FMTS]; | 174 | static struct display_format *formats[TAGMENU_MAX_FMTS]; |
166 | static int format_count; | 175 | static int format_count; |
167 | 176 | ||
177 | #define MENUENTRY_MAX_NAME 64 | ||
168 | struct menu_entry { | 178 | struct menu_entry { |
169 | char name[64]; | 179 | char name[MENUENTRY_MAX_NAME]; |
170 | int type; | 180 | int type; |
171 | struct search_instruction { | 181 | struct search_instruction { |
172 | char name[64]; | 182 | char name[MENUENTRY_MAX_NAME]; |
173 | int tagorder[MAX_TAGS]; | 183 | int tagorder[MAX_TAGS]; |
174 | int tagorder_count; | 184 | int tagorder_count; |
175 | struct tagcache_search_clause *clause[MAX_TAGS][TAGCACHE_MAX_CLAUSES]; | 185 | struct tagcache_search_clause *clause[MAX_TAGS][TAGCACHE_MAX_CLAUSES]; |
@@ -181,19 +191,12 @@ struct menu_entry { | |||
181 | }; | 191 | }; |
182 | 192 | ||
183 | struct menu_root { | 193 | struct menu_root { |
184 | char title[64]; | 194 | char title[MENUENTRY_MAX_NAME]; |
185 | char id[MAX_MENU_ID_SIZE]; | 195 | char id[MAX_MENU_ID_SIZE]; |
186 | int itemcount; | 196 | int itemcount; |
187 | struct menu_entry *items[TAGMENU_MAX_ITEMS]; | 197 | struct menu_entry *items[TAGMENU_MAX_ITEMS]; |
188 | }; | 198 | }; |
189 | 199 | ||
190 | struct match | ||
191 | { | ||
192 | const char* str; | ||
193 | uint16_t len; | ||
194 | uint16_t symbol; | ||
195 | }; | ||
196 | |||
197 | /* Statusbar text of the current view. */ | 200 | /* Statusbar text of the current view. */ |
198 | static char current_title[MAX_TAGS][128]; | 201 | static char current_title[MAX_TAGS][128]; |
199 | 202 | ||
@@ -276,6 +279,20 @@ static struct buflib_callbacks ops = { | |||
276 | .shrink_callback = NULL, | 279 | .shrink_callback = NULL, |
277 | }; | 280 | }; |
278 | 281 | ||
282 | static uint32_t tagtree_data_crc(struct tree_context* c) | ||
283 | { | ||
284 | char* buf; | ||
285 | uint32_t crc; | ||
286 | buf = core_get_data(tagtree_handle); /* data for the search clauses etc */ | ||
287 | crc = crc_32(buf, tagtree_buf_used, c->dirlength); | ||
288 | buf = core_get_data(c->cache.name_buffer_handle); /* names */ | ||
289 | crc = crc_32(buf, c->cache.name_buffer_size, crc); | ||
290 | buf = core_get_data(c->cache.entries_handle); /* tagentries */ | ||
291 | crc = crc_32(buf, c->cache.max_entries * sizeof(struct tagentry), crc); | ||
292 | logf("%s 0x%x", __func__, crc); | ||
293 | return crc; | ||
294 | } | ||
295 | |||
279 | static void* tagtree_alloc(size_t size) | 296 | static void* tagtree_alloc(size_t size) |
280 | { | 297 | { |
281 | size = ALIGN_UP(size, sizeof(void*)); | 298 | size = ALIGN_UP(size, sizeof(void*)); |
@@ -331,13 +348,15 @@ static int get_token_str(char *buf, int size) | |||
331 | static int get_tag(int *tag) | 348 | static int get_tag(int *tag) |
332 | { | 349 | { |
333 | #define TAG_MATCH(str, tag) {str, sizeof(str) - 1, tag} | 350 | #define TAG_MATCH(str, tag) {str, sizeof(str) - 1, tag} |
351 | struct match {const char* str; uint16_t len; uint16_t symbol;}; | ||
334 | static const struct match get_tag_match[] = | 352 | static const struct match get_tag_match[] = |
335 | { | 353 | { |
336 | TAG_MATCH("Lm", tag_virt_length_min), | 354 | TAG_MATCH("lm", tag_virt_length_min), |
337 | TAG_MATCH("Ls", tag_virt_length_sec), | 355 | TAG_MATCH("ls", tag_virt_length_sec), |
338 | TAG_MATCH("Pm", tag_virt_playtime_min), | 356 | TAG_MATCH("pm", tag_virt_playtime_min), |
339 | TAG_MATCH("Ps", tag_virt_playtime_sec), | 357 | TAG_MATCH("ps", tag_virt_playtime_sec), |
340 | TAG_MATCH("->", menu_next), | 358 | TAG_MATCH("->", menu_next), |
359 | TAG_MATCH("~>", menu_shuffle_songs), | ||
341 | 360 | ||
342 | TAG_MATCH("==>", menu_load), | 361 | TAG_MATCH("==>", menu_load), |
343 | 362 | ||
@@ -381,13 +400,15 @@ static int get_tag(int *tag) | |||
381 | TAG_MATCH("lastelapsed", tag_lastelapsed), | 400 | TAG_MATCH("lastelapsed", tag_lastelapsed), |
382 | TAG_MATCH("%menu_start", var_menu_start), | 401 | TAG_MATCH("%menu_start", var_menu_start), |
383 | 402 | ||
403 | TAG_MATCH("%byfirstletter", menu_byfirstletter), | ||
384 | TAG_MATCH("canonicalartist", tag_virt_canonicalartist), | 404 | TAG_MATCH("canonicalartist", tag_virt_canonicalartist), |
405 | |||
385 | TAG_MATCH("", 0) /* sentinel */ | 406 | TAG_MATCH("", 0) /* sentinel */ |
386 | }; | 407 | }; |
387 | #undef TAG_MATCH | 408 | #undef TAG_MATCH |
388 | const size_t max_cmd_sz = 32; /* needs to be >= to len of longest tagstr */ | 409 | const size_t max_cmd_sz = 32; /* needs to be >= to len of longest tagstr */ |
389 | const char *tagstr; | 410 | const char *tagstr; |
390 | unsigned int tagstr_len; | 411 | uint16_t tagstr_len; |
391 | const struct match *match; | 412 | const struct match *match; |
392 | 413 | ||
393 | /* Find the start. */ | 414 | /* Find the start. */ |
@@ -820,7 +841,7 @@ static bool parse_search(struct menu_entry *entry, const char *str) | |||
820 | return true; | 841 | return true; |
821 | } | 842 | } |
822 | 843 | ||
823 | if (entry->type != menu_next) | 844 | if (entry->type != menu_next && entry->type != menu_shuffle_songs) |
824 | return false; | 845 | return false; |
825 | 846 | ||
826 | while (inst->tagorder_count < MAX_TAGS) | 847 | while (inst->tagorder_count < MAX_TAGS) |
@@ -847,7 +868,7 @@ static bool parse_search(struct menu_entry *entry, const char *str) | |||
847 | 868 | ||
848 | inst->tagorder_count++; | 869 | inst->tagorder_count++; |
849 | 870 | ||
850 | if (get_tag(&type) <= 0 || type != menu_next) | 871 | if (get_tag(&type) <= 0 || (type != menu_next && type != menu_shuffle_songs)) |
851 | break; | 872 | break; |
852 | } | 873 | } |
853 | 874 | ||
@@ -1033,8 +1054,65 @@ int tagtree_import(void) | |||
1033 | return 0; | 1054 | return 0; |
1034 | } | 1055 | } |
1035 | 1056 | ||
1036 | static bool parse_menu(const char *filename); | 1057 | static bool alloc_menu_parse_buf(char *buf, int type) |
1058 | { | ||
1059 | /* allocate a new menu item (if needed) initialize it with data parsed | ||
1060 | from buf Note: allows setting menu type, type ignored when < 0 | ||
1061 | */ | ||
1062 | /* Allocate */ | ||
1063 | if (menu->items[menu->itemcount] == NULL) | ||
1064 | menu->items[menu->itemcount] = tagtree_alloc0(sizeof(struct menu_entry)); | ||
1065 | if (!menu->items[menu->itemcount]) | ||
1066 | { | ||
1067 | logf("tagtree failed to allocate %s", "menu items"); | ||
1068 | return false; | ||
1069 | } | ||
1037 | 1070 | ||
1071 | /* Initialize */ | ||
1072 | core_pin(tagtree_handle); | ||
1073 | if (parse_search(menu->items[menu->itemcount], buf)) | ||
1074 | { | ||
1075 | if (type >= 0) | ||
1076 | menu->items[menu->itemcount]->type = type; | ||
1077 | menu->itemcount++; | ||
1078 | } | ||
1079 | core_unpin(tagtree_handle); | ||
1080 | return true; | ||
1081 | } | ||
1082 | |||
1083 | static void build_firstletter_menu(char *buf, size_t bufsz) | ||
1084 | { | ||
1085 | const char *subitem = buf; | ||
1086 | size_t l = strlen(buf) + 1; | ||
1087 | buf+=l; | ||
1088 | bufsz-=l; | ||
1089 | |||
1090 | const char * const fmt ="\"%s\"-> %s ? %s %c\"%c\"-> %s =\"fmt_title\""; | ||
1091 | const char * const showsub = /* album subitem for canonicalartist */ | ||
1092 | ((strcasestr(subitem, "artist") == NULL) ? "title" : "album -> title"); | ||
1093 | |||
1094 | /* Numeric ex: "Numeric" -> album ? album < "A" -> title = "fmt_title" */ | ||
1095 | snprintf(buf, bufsz, fmt, | ||
1096 | str(LANG_DISPLAY_NUMERIC), subitem, subitem,'<', 'A', showsub); | ||
1097 | |||
1098 | if (!alloc_menu_parse_buf(buf, menu_byfirstletter)) | ||
1099 | { | ||
1100 | return; | ||
1101 | } | ||
1102 | |||
1103 | for (int i = 0; i < 26; i++) | ||
1104 | { | ||
1105 | snprintf(buf, bufsz, fmt, "#", subitem, subitem,'^', 'A' + i, showsub); | ||
1106 | buf[1] = 'A' + i; /* overwrite the placeholder # with the current letter */ | ||
1107 | /* ex: "A" -> title ? title ^ "A" -> title = "fmt_title" */ | ||
1108 | if (!alloc_menu_parse_buf(buf, menu_byfirstletter)) | ||
1109 | { | ||
1110 | return; | ||
1111 | } | ||
1112 | } | ||
1113 | } | ||
1114 | |||
1115 | static bool parse_menu(const char *filename); | ||
1038 | static int parse_line(int n, char *buf, void *parameters) | 1116 | static int parse_line(int n, char *buf, void *parameters) |
1039 | { | 1117 | { |
1040 | char data[256]; | 1118 | char data[256]; |
@@ -1104,7 +1182,7 @@ static int parse_line(int n, char *buf, void *parameters) | |||
1104 | logf("Load menu fail: %s", data); | 1182 | logf("Load menu fail: %s", data); |
1105 | } | 1183 | } |
1106 | break; | 1184 | break; |
1107 | 1185 | case menu_byfirstletter: /* Fallthrough */ | |
1108 | case var_menu_start: | 1186 | case var_menu_start: |
1109 | if (menu_count >= TAGMENU_MAX_MENUS) | 1187 | if (menu_count >= TAGMENU_MAX_MENUS) |
1110 | { | 1188 | { |
@@ -1146,6 +1224,19 @@ static int parse_line(int n, char *buf, void *parameters) | |||
1146 | return 0; | 1224 | return 0; |
1147 | } | 1225 | } |
1148 | logf("menu: %s", menu->title); | 1226 | logf("menu: %s", menu->title); |
1227 | |||
1228 | if (variable == menu_byfirstletter) | ||
1229 | { | ||
1230 | if (get_token_str(data, sizeof(data)) < 0) | ||
1231 | { | ||
1232 | logf("%%firstletter_menu has no subitem"); /*artist,album*/ | ||
1233 | return 0; | ||
1234 | } | ||
1235 | logf("A-Z Menu subitem: %s", data); | ||
1236 | read_menu = false; | ||
1237 | build_firstletter_menu(data, sizeof(data)); | ||
1238 | break; | ||
1239 | } | ||
1149 | read_menu = true; | 1240 | read_menu = true; |
1150 | break; | 1241 | break; |
1151 | 1242 | ||
@@ -1179,18 +1270,10 @@ static int parse_line(int n, char *buf, void *parameters) | |||
1179 | return 0; | 1270 | return 0; |
1180 | } | 1271 | } |
1181 | 1272 | ||
1182 | /* Allocate */ | 1273 | if (!alloc_menu_parse_buf(buf, -1)) |
1183 | if (menu->items[menu->itemcount] == NULL) | ||
1184 | menu->items[menu->itemcount] = tagtree_alloc0(sizeof(struct menu_entry)); | ||
1185 | if (!menu->items[menu->itemcount]) | ||
1186 | { | 1274 | { |
1187 | logf("tagtree failed to allocate %s", "menu items"); | ||
1188 | return -2; | 1275 | return -2; |
1189 | } | 1276 | } |
1190 | core_pin(tagtree_handle); | ||
1191 | if (parse_search(menu->items[menu->itemcount], buf)) | ||
1192 | menu->itemcount++; | ||
1193 | core_unpin(tagtree_handle); | ||
1194 | 1277 | ||
1195 | return 0; | 1278 | return 0; |
1196 | } | 1279 | } |
@@ -1245,6 +1328,7 @@ static void tagtree_unload(struct tree_context *c) | |||
1245 | dptr->name = NULL; | 1328 | dptr->name = NULL; |
1246 | dptr->newtable = 0; | 1329 | dptr->newtable = 0; |
1247 | dptr->extraseek = 0; | 1330 | dptr->extraseek = 0; |
1331 | dptr->customaction = ONPLAY_NO_CUSTOMACTION; | ||
1248 | dptr++; | 1332 | dptr++; |
1249 | } | 1333 | } |
1250 | } | 1334 | } |
@@ -1425,6 +1509,7 @@ static void tcs_get_basename(struct tagcache_search *tcs, bool is_basename) | |||
1425 | 1509 | ||
1426 | static int retrieve_entries(struct tree_context *c, int offset, bool init) | 1510 | static int retrieve_entries(struct tree_context *c, int offset, bool init) |
1427 | { | 1511 | { |
1512 | logf( "%s", __func__); | ||
1428 | char tcs_buf[TAGCACHE_BUFSZ]; | 1513 | char tcs_buf[TAGCACHE_BUFSZ]; |
1429 | const long tcs_bufsz = sizeof(tcs_buf); | 1514 | const long tcs_bufsz = sizeof(tcs_buf); |
1430 | struct tagcache_search tcs; | 1515 | struct tagcache_search tcs; |
@@ -1452,9 +1537,9 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init) | |||
1452 | #else | 1537 | #else |
1453 | true | 1538 | true |
1454 | #endif | 1539 | #endif |
1455 | , 0); | 1540 | , 0, 0, 0); |
1456 | 1541 | ||
1457 | if (c->currtable == ALLSUBENTRIES) | 1542 | if (c->currtable == TABLE_ALLSUBENTRIES) |
1458 | { | 1543 | { |
1459 | tag = tag_title; | 1544 | tag = tag_title; |
1460 | level--; | 1545 | level--; |
@@ -1544,17 +1629,19 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init) | |||
1544 | { | 1629 | { |
1545 | if (offset == 0) | 1630 | if (offset == 0) |
1546 | { | 1631 | { |
1547 | dptr->newtable = ALLSUBENTRIES; | 1632 | dptr->newtable = TABLE_ALLSUBENTRIES; |
1548 | dptr->name = str(LANG_TAGNAVI_ALL_TRACKS); | 1633 | dptr->name = str(LANG_TAGNAVI_ALL_TRACKS); |
1634 | dptr->customaction = ONPLAY_NO_CUSTOMACTION; | ||
1549 | dptr++; | 1635 | dptr++; |
1550 | current_entry_count++; | 1636 | current_entry_count++; |
1551 | special_entry_count++; | 1637 | special_entry_count++; |
1552 | } | 1638 | } |
1553 | if (offset <= 1) | 1639 | if (offset <= 1) |
1554 | { | 1640 | { |
1555 | dptr->newtable = NAVIBROWSE; | 1641 | dptr->newtable = TABLE_NAVIBROWSE; |
1556 | dptr->name = str(LANG_TAGNAVI_RANDOM); | 1642 | dptr->name = str(LANG_TAGNAVI_RANDOM); |
1557 | dptr->extraseek = -1; | 1643 | dptr->extraseek = -1; |
1644 | dptr->customaction = ONPLAY_NO_CUSTOMACTION; | ||
1558 | dptr++; | 1645 | dptr++; |
1559 | current_entry_count++; | 1646 | current_entry_count++; |
1560 | special_entry_count++; | 1647 | special_entry_count++; |
@@ -1568,14 +1655,15 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init) | |||
1568 | if (total_count++ < offset) | 1655 | if (total_count++ < offset) |
1569 | continue; | 1656 | continue; |
1570 | 1657 | ||
1571 | dptr->newtable = NAVIBROWSE; | 1658 | dptr->newtable = TABLE_NAVIBROWSE; |
1572 | if (tag == tag_title || tag == tag_filename) | 1659 | if (tag == tag_title || tag == tag_filename) |
1573 | { | 1660 | { |
1574 | dptr->newtable = PLAYTRACK; | 1661 | dptr->newtable = TABLE_PLAYTRACK; |
1575 | dptr->extraseek = tcs.idx_id; | 1662 | dptr->extraseek = tcs.idx_id; |
1576 | } | 1663 | } |
1577 | else | 1664 | else |
1578 | dptr->extraseek = tcs.result_seek; | 1665 | dptr->extraseek = tcs.result_seek; |
1666 | dptr->customaction = ONPLAY_NO_CUSTOMACTION; | ||
1579 | 1667 | ||
1580 | fmt = NULL; | 1668 | fmt = NULL; |
1581 | /* Check the format */ | 1669 | /* Check the format */ |
@@ -1676,7 +1764,7 @@ entry_skip_formatter: | |||
1676 | 1764 | ||
1677 | if (init) | 1765 | if (init) |
1678 | { | 1766 | { |
1679 | if (!show_search_progress(false, total_count)) | 1767 | if (!show_search_progress(false, total_count, 0, 0)) |
1680 | { /* user aborted */ | 1768 | { /* user aborted */ |
1681 | tagcache_search_finish(&tcs); | 1769 | tagcache_search_finish(&tcs); |
1682 | tree_unlock_cache(c); | 1770 | tree_unlock_cache(c); |
@@ -1710,7 +1798,7 @@ entry_skip_formatter: | |||
1710 | 1798 | ||
1711 | while (tagcache_get_next(&tcs, tcs_buf, tcs_bufsz)) | 1799 | while (tagcache_get_next(&tcs, tcs_buf, tcs_bufsz)) |
1712 | { | 1800 | { |
1713 | if (!show_search_progress(false, total_count)) | 1801 | if (!show_search_progress(false, total_count, 0, 0)) |
1714 | break; | 1802 | break; |
1715 | total_count++; | 1803 | total_count++; |
1716 | } | 1804 | } |
@@ -1758,7 +1846,7 @@ static int load_root(struct tree_context *c) | |||
1758 | int i; | 1846 | int i; |
1759 | 1847 | ||
1760 | tc = c; | 1848 | tc = c; |
1761 | c->currtable = ROOT; | 1849 | c->currtable = TABLE_ROOT; |
1762 | if (c->dirlevel == 0) | 1850 | if (c->dirlevel == 0) |
1763 | c->currextra = rootmenu; | 1851 | c->currextra = rootmenu; |
1764 | 1852 | ||
@@ -1775,13 +1863,27 @@ static int load_root(struct tree_context *c) | |||
1775 | switch (menu->items[i]->type) | 1863 | switch (menu->items[i]->type) |
1776 | { | 1864 | { |
1777 | case menu_next: | 1865 | case menu_next: |
1778 | dptr->newtable = NAVIBROWSE; | 1866 | dptr->newtable = TABLE_NAVIBROWSE; |
1779 | dptr->extraseek = i; | 1867 | dptr->extraseek = i; |
1868 | dptr->customaction = ONPLAY_NO_CUSTOMACTION; | ||
1780 | break; | 1869 | break; |
1781 | 1870 | ||
1782 | case menu_load: | 1871 | case menu_load: |
1783 | dptr->newtable = ROOT; | 1872 | dptr->newtable = TABLE_ROOT; |
1784 | dptr->extraseek = menu->items[i]->link; | 1873 | dptr->extraseek = menu->items[i]->link; |
1874 | dptr->customaction = ONPLAY_NO_CUSTOMACTION; | ||
1875 | break; | ||
1876 | |||
1877 | case menu_shuffle_songs: | ||
1878 | dptr->newtable = TABLE_NAVIBROWSE; | ||
1879 | dptr->extraseek = i; | ||
1880 | dptr->customaction = ONPLAY_CUSTOMACTION_SHUFFLE_SONGS; | ||
1881 | break; | ||
1882 | |||
1883 | case menu_byfirstletter: | ||
1884 | dptr->newtable = TABLE_NAVIBROWSE; | ||
1885 | dptr->extraseek = i; | ||
1886 | dptr->customaction = ONPLAY_CUSTOMACTION_FIRSTLETTER; | ||
1785 | break; | 1887 | break; |
1786 | } | 1888 | } |
1787 | 1889 | ||
@@ -1796,6 +1898,8 @@ static int load_root(struct tree_context *c) | |||
1796 | 1898 | ||
1797 | int tagtree_load(struct tree_context* c) | 1899 | int tagtree_load(struct tree_context* c) |
1798 | { | 1900 | { |
1901 | logf( "%s", __func__); | ||
1902 | |||
1799 | int count; | 1903 | int count; |
1800 | int table = c->currtable; | 1904 | int table = c->currtable; |
1801 | 1905 | ||
@@ -1804,20 +1908,32 @@ int tagtree_load(struct tree_context* c) | |||
1804 | if (!table) | 1908 | if (!table) |
1805 | { | 1909 | { |
1806 | c->dirfull = false; | 1910 | c->dirfull = false; |
1807 | table = ROOT; | 1911 | table = TABLE_ROOT; |
1808 | c->currtable = table; | 1912 | c->currtable = table; |
1809 | c->currextra = rootmenu; | 1913 | c->currextra = rootmenu; |
1810 | } | 1914 | } |
1811 | 1915 | ||
1812 | switch (table) | 1916 | switch (table) |
1813 | { | 1917 | { |
1814 | case ROOT: | 1918 | case TABLE_ROOT: |
1919 | logf( "root..."); | ||
1815 | count = load_root(c); | 1920 | count = load_root(c); |
1816 | break; | 1921 | break; |
1817 | 1922 | ||
1818 | case ALLSUBENTRIES: | 1923 | case TABLE_ALLSUBENTRIES: |
1819 | case NAVIBROWSE: | 1924 | case TABLE_NAVIBROWSE: |
1820 | logf("navibrowse..."); | 1925 | logf("navibrowse..."); |
1926 | |||
1927 | if (loaded_entries_crc != 0) | ||
1928 | { | ||
1929 | if (loaded_entries_crc == tagtree_data_crc(c)) | ||
1930 | { | ||
1931 | count = c->dirlength; | ||
1932 | logf("Reusing %d entries", count); | ||
1933 | break; | ||
1934 | } | ||
1935 | } | ||
1936 | |||
1821 | cpu_boost(true); | 1937 | cpu_boost(true); |
1822 | count = retrieve_entries(c, 0, true); | 1938 | count = retrieve_entries(c, 0, true); |
1823 | cpu_boost(false); | 1939 | cpu_boost(false); |
@@ -1828,6 +1944,8 @@ int tagtree_load(struct tree_context* c) | |||
1828 | return -1; | 1944 | return -1; |
1829 | } | 1945 | } |
1830 | 1946 | ||
1947 | loaded_entries_crc = 0; | ||
1948 | |||
1831 | if (count < 0) | 1949 | if (count < 0) |
1832 | { | 1950 | { |
1833 | if (count != RELOAD_TAGTREE) | 1951 | if (count != RELOAD_TAGTREE) |
@@ -1860,6 +1978,8 @@ int tagtree_load(struct tree_context* c) | |||
1860 | */ | 1978 | */ |
1861 | int tagtree_enter(struct tree_context* c, bool is_visible) | 1979 | int tagtree_enter(struct tree_context* c, bool is_visible) |
1862 | { | 1980 | { |
1981 | logf( "%s", __func__); | ||
1982 | |||
1863 | int rc = 0; | 1983 | int rc = 0; |
1864 | struct tagentry *dptr; | 1984 | struct tagentry *dptr; |
1865 | struct mp3entry *id3; | 1985 | struct mp3entry *id3; |
@@ -1921,16 +2041,16 @@ int tagtree_enter(struct tree_context* c, bool is_visible) | |||
1921 | core_pin(tagtree_handle); | 2041 | core_pin(tagtree_handle); |
1922 | 2042 | ||
1923 | switch (c->currtable) { | 2043 | switch (c->currtable) { |
1924 | case ROOT: | 2044 | case TABLE_ROOT: |
1925 | c->currextra = newextra; | 2045 | c->currextra = newextra; |
1926 | 2046 | ||
1927 | if (newextra == ROOT) | 2047 | if (newextra == TABLE_ROOT) |
1928 | { | 2048 | { |
1929 | menu = menus[seek]; | 2049 | menu = menus[seek]; |
1930 | c->currextra = seek; | 2050 | c->currextra = seek; |
1931 | } | 2051 | } |
1932 | 2052 | ||
1933 | else if (newextra == NAVIBROWSE) | 2053 | else if (newextra == TABLE_NAVIBROWSE) |
1934 | { | 2054 | { |
1935 | int i, j; | 2055 | int i, j; |
1936 | 2056 | ||
@@ -2005,9 +2125,9 @@ int tagtree_enter(struct tree_context* c, bool is_visible) | |||
2005 | 2125 | ||
2006 | break; | 2126 | break; |
2007 | 2127 | ||
2008 | case NAVIBROWSE: | 2128 | case TABLE_NAVIBROWSE: |
2009 | case ALLSUBENTRIES: | 2129 | case TABLE_ALLSUBENTRIES: |
2010 | if (newextra == PLAYTRACK) | 2130 | if (newextra == TABLE_PLAYTRACK) |
2011 | { | 2131 | { |
2012 | adjust_selection = false; | 2132 | adjust_selection = false; |
2013 | 2133 | ||
@@ -2059,6 +2179,7 @@ int tagtree_enter(struct tree_context* c, bool is_visible) | |||
2059 | /* Exits current database menu or table */ | 2179 | /* Exits current database menu or table */ |
2060 | void tagtree_exit(struct tree_context* c, bool is_visible) | 2180 | void tagtree_exit(struct tree_context* c, bool is_visible) |
2061 | { | 2181 | { |
2182 | logf( "%s", __func__); | ||
2062 | if (is_visible) /* update selection history only for user-selected items */ | 2183 | if (is_visible) /* update selection history only for user-selected items */ |
2063 | { | 2184 | { |
2064 | if (c->selected_item != selected_item_history[c->dirlevel]) | 2185 | if (c->selected_item != selected_item_history[c->dirlevel]) |
@@ -2102,44 +2223,84 @@ int tagtree_get_filename(struct tree_context* c, char *buf, int buflen) | |||
2102 | return 0; | 2223 | return 0; |
2103 | } | 2224 | } |
2104 | 2225 | ||
2226 | int tagtree_get_custom_action(struct tree_context* c) | ||
2227 | { | ||
2228 | return tagtree_get_entry(c, c->selected_item)->customaction; | ||
2229 | } | ||
2230 | |||
2231 | static void swap_array_bool(bool *a, bool *b) | ||
2232 | { | ||
2233 | bool temp = *a; | ||
2234 | *a = *b; | ||
2235 | *b = temp; | ||
2236 | } | ||
2237 | |||
2238 | /** | ||
2239 | * Randomly shuffle an array using the Fisher-Yates algorithm : | ||
2240 | * https://en.wikipedia.org/wiki/Random_permutation | ||
2241 | * This algorithm has a linear complexity. | ||
2242 | * Don't forget to srand before call to use it with a relevant seed. | ||
2243 | */ | ||
2244 | static bool* fill_random_playlist_indexes(bool *bool_array, size_t arr_sz, | ||
2245 | size_t track_count, size_t max_slots) | ||
2246 | { | ||
2247 | size_t i; | ||
2248 | if (track_count * sizeof(bool) > arr_sz || max_slots > track_count) | ||
2249 | return NULL; | ||
2250 | |||
2251 | for (i = 0; i < arr_sz; i++) /* fill max_slots with TRUE */ | ||
2252 | bool_array[i] = i < max_slots; | ||
2253 | |||
2254 | /* shuffle bool array */ | ||
2255 | for (i = track_count - 1; i > 0; i--) | ||
2256 | { | ||
2257 | int j = rand() % (i + 1); | ||
2258 | swap_array_bool(&bool_array[i], &bool_array[j]); | ||
2259 | } | ||
2260 | return bool_array; | ||
2261 | } | ||
2105 | 2262 | ||
2106 | static bool insert_all_playlist(struct tree_context *c, | 2263 | static bool insert_all_playlist(struct tree_context *c, |
2107 | const char* playlist, bool new_playlist, | 2264 | const char* playlist, bool new_playlist, |
2108 | int position, bool queue) | 2265 | int position, bool queue) |
2109 | { | 2266 | { |
2110 | struct tagcache_search tcs; | 2267 | struct tagcache_search tcs; |
2111 | int i, n; | 2268 | int n; |
2112 | int fd = -1; | 2269 | int fd = -1; |
2113 | unsigned long last_tick; | 2270 | unsigned long last_tick; |
2271 | int slots_remaining = 0; | ||
2272 | bool fill_randomly = false; | ||
2273 | bool *rand_bool_array = NULL; | ||
2114 | char buf[MAX_PATH]; | 2274 | char buf[MAX_PATH]; |
2275 | struct playlist_insert_context context; | ||
2115 | 2276 | ||
2116 | cpu_boost(true); | 2277 | cpu_boost(true); |
2278 | |||
2117 | if (!tagcache_search(&tcs, tag_filename)) | 2279 | if (!tagcache_search(&tcs, tag_filename)) |
2118 | { | 2280 | { |
2119 | splash(HZ, ID2P(LANG_TAGCACHE_BUSY)); | 2281 | splash(HZ, ID2P(LANG_TAGCACHE_BUSY)); |
2120 | cpu_boost(false); | 2282 | cpu_boost(false); |
2121 | return false; | 2283 | return false; |
2122 | } | 2284 | } /* NOTE: you need to close this search before returning */ |
2123 | 2285 | ||
2124 | if (playlist == NULL && position == PLAYLIST_REPLACE) | 2286 | if (playlist == NULL) |
2125 | { | 2287 | { |
2126 | if (playlist_remove_all_tracks(NULL) == 0) | 2288 | if (playlist_insert_context_create(NULL, &context, position, queue, false) < 0) |
2127 | position = PLAYLIST_INSERT_LAST; | ||
2128 | else | ||
2129 | { | 2289 | { |
2290 | tagcache_search_finish(&tcs); | ||
2130 | cpu_boost(false); | 2291 | cpu_boost(false); |
2131 | return false; | 2292 | return false; |
2132 | } | 2293 | } |
2133 | } | 2294 | } |
2134 | else if (playlist != NULL) | 2295 | else |
2135 | { | 2296 | { |
2136 | if (new_playlist) | 2297 | if (new_playlist) |
2137 | fd = open_utf8(playlist, O_CREAT|O_WRONLY|O_TRUNC); | 2298 | fd = open_utf8(playlist, O_CREAT|O_WRONLY|O_TRUNC); |
2138 | else | 2299 | else |
2139 | fd = open(playlist, O_CREAT|O_WRONLY|O_APPEND, 0666); | 2300 | fd = open(playlist, O_CREAT|O_WRONLY|O_APPEND, 0666); |
2140 | |||
2141 | if(fd < 0) | 2301 | if(fd < 0) |
2142 | { | 2302 | { |
2303 | tagcache_search_finish(&tcs); | ||
2143 | cpu_boost(false); | 2304 | cpu_boost(false); |
2144 | return false; | 2305 | return false; |
2145 | } | 2306 | } |
@@ -2148,45 +2309,120 @@ static bool insert_all_playlist(struct tree_context *c, | |||
2148 | last_tick = current_tick + HZ/2; /* Show splash after 0.5 seconds have passed */ | 2309 | last_tick = current_tick + HZ/2; /* Show splash after 0.5 seconds have passed */ |
2149 | splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ | 2310 | splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ |
2150 | n = c->filesindir; | 2311 | n = c->filesindir; |
2151 | for (i = 0; i < n; i++) | 2312 | |
2313 | if (playlist == NULL) | ||
2152 | { | 2314 | { |
2315 | int max_playlist_size = playlist_get_current()->max_playlist_size; | ||
2316 | slots_remaining = max_playlist_size - playlist_get_current()->amount; | ||
2317 | if (slots_remaining <= 0) | ||
2318 | { | ||
2319 | logf("Playlist has no space remaining"); | ||
2320 | tagcache_search_finish(&tcs); | ||
2321 | cpu_boost(false); | ||
2322 | return false; | ||
2323 | } | ||
2324 | |||
2325 | fill_randomly = n > slots_remaining; | ||
2326 | |||
2327 | if (fill_randomly) | ||
2328 | { | ||
2329 | srand(current_tick); | ||
2330 | size_t bufsize = 0; | ||
2331 | bool *buffer = (bool *) plugin_get_buffer(&bufsize); | ||
2332 | rand_bool_array = fill_random_playlist_indexes(buffer, bufsize, | ||
2333 | n, slots_remaining); | ||
2334 | |||
2335 | talk_id(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY, true); | ||
2336 | splashf(HZ * 2, str(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY), | ||
2337 | slots_remaining); | ||
2338 | } | ||
2339 | } | ||
2153 | 2340 | ||
2154 | splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); | 2341 | bool exit_loop_now = false; |
2342 | for (int i = 0; i < n; i++) | ||
2343 | { | ||
2155 | if (TIME_AFTER(current_tick, last_tick + HZ/4)) | 2344 | if (TIME_AFTER(current_tick, last_tick + HZ/4)) |
2156 | { | 2345 | { |
2346 | splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); | ||
2157 | if (action_userabort(TIMEOUT_NOBLOCK)) | 2347 | if (action_userabort(TIMEOUT_NOBLOCK)) |
2348 | { | ||
2349 | exit_loop_now = true; | ||
2158 | break; | 2350 | break; |
2351 | } | ||
2159 | last_tick = current_tick; | 2352 | last_tick = current_tick; |
2160 | } | 2353 | } |
2161 | 2354 | ||
2162 | if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, i)->extraseek, | 2355 | if (playlist == NULL) |
2163 | tcs.type, buf, sizeof buf)) | ||
2164 | { | 2356 | { |
2165 | continue; | 2357 | if (fill_randomly) |
2358 | { | ||
2359 | int remaining_tracks = n - i; | ||
2360 | if (remaining_tracks > slots_remaining) | ||
2361 | { | ||
2362 | if (rand_bool_array) | ||
2363 | { | ||
2364 | /* Skip the track if rand_bool_array[i] is FALSE */ | ||
2365 | if (!rand_bool_array[i]) | ||
2366 | continue; | ||
2367 | } | ||
2368 | else | ||
2369 | { | ||
2370 | /* Generate random value between 0 and remaining_tracks - 1 */ | ||
2371 | int selrange = RAND_MAX / remaining_tracks; /* Improve distribution */ | ||
2372 | int random; | ||
2373 | |||
2374 | for (int r = 0; r < 0x0FFF; r++) /* limit loops */ | ||
2375 | { | ||
2376 | random = rand() / selrange; | ||
2377 | if (random < remaining_tracks) | ||
2378 | break; | ||
2379 | else | ||
2380 | random = 0; | ||
2381 | } | ||
2382 | /* Skip the track if random >= slots_remaining */ | ||
2383 | if (random >= slots_remaining) | ||
2384 | continue; | ||
2385 | } | ||
2386 | } | ||
2387 | } | ||
2166 | } | 2388 | } |
2167 | 2389 | ||
2390 | if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, i)->extraseek, tcs.type, buf, sizeof buf)) | ||
2391 | continue; | ||
2392 | |||
2168 | if (playlist == NULL) | 2393 | if (playlist == NULL) |
2169 | { | 2394 | { |
2170 | if (playlist_insert_track(NULL, buf, position, queue, false) < 0) | 2395 | if (fill_randomly) |
2171 | { | 2396 | { |
2397 | if (--slots_remaining <= 0) | ||
2398 | { | ||
2399 | exit_loop_now = true; | ||
2400 | break; | ||
2401 | } | ||
2402 | } | ||
2403 | |||
2404 | if (playlist_insert_context_add(&context, buf) < 0) { | ||
2172 | logf("playlist_insert_track failed"); | 2405 | logf("playlist_insert_track failed"); |
2406 | exit_loop_now = true; | ||
2173 | break; | 2407 | break; |
2174 | } | 2408 | } |
2175 | } | 2409 | } |
2176 | else if (fdprintf(fd, "%s\n", buf) <= 0) | 2410 | else if (fdprintf(fd, "%s\n", buf) <= 0) |
2177 | break; | ||
2178 | |||
2179 | yield(); | ||
2180 | |||
2181 | if (playlist == NULL && position == PLAYLIST_INSERT_FIRST) | ||
2182 | { | 2411 | { |
2183 | position = PLAYLIST_INSERT; | 2412 | exit_loop_now = true; |
2413 | break; | ||
2184 | } | 2414 | } |
2415 | yield(); | ||
2416 | |||
2417 | if (exit_loop_now) | ||
2418 | break; | ||
2185 | } | 2419 | } |
2420 | |||
2186 | if (playlist == NULL) | 2421 | if (playlist == NULL) |
2187 | playlist_sync(NULL); | 2422 | playlist_insert_context_release(&context); |
2188 | else | 2423 | else |
2189 | close(fd); | 2424 | close(fd); |
2425 | |||
2190 | tagcache_search_finish(&tcs); | 2426 | tagcache_search_finish(&tcs); |
2191 | cpu_boost(false); | 2427 | cpu_boost(false); |
2192 | 2428 | ||
@@ -2196,14 +2432,14 @@ static bool insert_all_playlist(struct tree_context *c, | |||
2196 | static bool goto_allsubentries(int newtable) | 2432 | static bool goto_allsubentries(int newtable) |
2197 | { | 2433 | { |
2198 | int i = 0; | 2434 | int i = 0; |
2199 | while (i < 2 && (newtable == NAVIBROWSE || newtable == ALLSUBENTRIES)) | 2435 | while (i < 2 && (newtable == TABLE_NAVIBROWSE || newtable == TABLE_ALLSUBENTRIES)) |
2200 | { | 2436 | { |
2201 | tagtree_enter(tc, false); | 2437 | tagtree_enter(tc, false); |
2202 | tagtree_load(tc); | 2438 | tagtree_load(tc); |
2203 | newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; | 2439 | newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; |
2204 | i++; | 2440 | i++; |
2205 | } | 2441 | } |
2206 | return (newtable == PLAYTRACK); | 2442 | return (newtable == TABLE_PLAYTRACK); |
2207 | } | 2443 | } |
2208 | 2444 | ||
2209 | static void reset_tc_to_prev(int dirlevel, int selected_item) | 2445 | static void reset_tc_to_prev(int dirlevel, int selected_item) |
@@ -2229,11 +2465,11 @@ static bool tagtree_insert_selection(int position, bool queue, | |||
2229 | #else | 2465 | #else |
2230 | true | 2466 | true |
2231 | #endif | 2467 | #endif |
2232 | , 0); | 2468 | , 0, 0, 0); |
2233 | 2469 | ||
2234 | newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; | 2470 | newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; |
2235 | 2471 | ||
2236 | if (newtable == PLAYTRACK) /* Insert a single track? */ | 2472 | if (newtable == TABLE_PLAYTRACK) /* Insert a single track? */ |
2237 | { | 2473 | { |
2238 | if (tagtree_get_filename(tc, buf, sizeof buf) < 0) | 2474 | if (tagtree_get_filename(tc, buf, sizeof buf) < 0) |
2239 | return false; | 2475 | return false; |
@@ -2342,6 +2578,7 @@ int tagtree_add_to_playlist(const char* playlist, bool new_playlist) | |||
2342 | 2578 | ||
2343 | static int tagtree_play_folder(struct tree_context* c) | 2579 | static int tagtree_play_folder(struct tree_context* c) |
2344 | { | 2580 | { |
2581 | logf( "%s", __func__); | ||
2345 | int start_index = c->selected_item; | 2582 | int start_index = c->selected_item; |
2346 | 2583 | ||
2347 | if (playlist_create(NULL, NULL) < 0) | 2584 | if (playlist_create(NULL, NULL) < 0) |
@@ -2353,14 +2590,26 @@ static int tagtree_play_folder(struct tree_context* c) | |||
2353 | if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false)) | 2590 | if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false)) |
2354 | return -2; | 2591 | return -2; |
2355 | 2592 | ||
2593 | int n = c->filesindir; | ||
2594 | bool has_playlist_been_randomized = n > playlist_get_current()->max_playlist_size; | ||
2595 | if (has_playlist_been_randomized) | ||
2596 | { | ||
2597 | /* We need to recalculate the start index based on a percentage to put the user | ||
2598 | around its desired start position and avoid out of bounds */ | ||
2599 | |||
2600 | int percentage_start_index = 100 * start_index / n; | ||
2601 | start_index = percentage_start_index * playlist_get_current()->amount / 100; | ||
2602 | } | ||
2603 | |||
2356 | if (global_settings.playlist_shuffle) | 2604 | if (global_settings.playlist_shuffle) |
2357 | { | 2605 | { |
2358 | start_index = playlist_shuffle(current_tick, c->selected_item); | 2606 | start_index = playlist_shuffle(current_tick, start_index); |
2359 | if (!global_settings.play_selected) | 2607 | if (!global_settings.play_selected) |
2360 | start_index = 0; | 2608 | start_index = 0; |
2361 | } | 2609 | } |
2362 | 2610 | ||
2363 | playlist_start(start_index, 0, 0); | 2611 | playlist_start(start_index, 0, 0); |
2612 | loaded_entries_crc = tagtree_data_crc(c); /* save crc in case we return */ | ||
2364 | return 0; | 2613 | return 0; |
2365 | } | 2614 | } |
2366 | 2615 | ||
@@ -2403,11 +2652,11 @@ char *tagtree_get_title(struct tree_context* c) | |||
2403 | { | 2652 | { |
2404 | switch (c->currtable) | 2653 | switch (c->currtable) |
2405 | { | 2654 | { |
2406 | case ROOT: | 2655 | case TABLE_ROOT: |
2407 | return menu->title; | 2656 | return menu->title; |
2408 | 2657 | ||
2409 | case NAVIBROWSE: | 2658 | case TABLE_NAVIBROWSE: |
2410 | case ALLSUBENTRIES: | 2659 | case TABLE_ALLSUBENTRIES: |
2411 | return current_title[c->currextra]; | 2660 | return current_title[c->currextra]; |
2412 | } | 2661 | } |
2413 | 2662 | ||
@@ -2419,7 +2668,7 @@ int tagtree_get_attr(struct tree_context* c) | |||
2419 | int attr = -1; | 2668 | int attr = -1; |
2420 | switch (c->currtable) | 2669 | switch (c->currtable) |
2421 | { | 2670 | { |
2422 | case NAVIBROWSE: | 2671 | case TABLE_NAVIBROWSE: |
2423 | if (csi->tagorder[c->currextra] == tag_title | 2672 | if (csi->tagorder[c->currextra] == tag_title |
2424 | || csi->tagorder[c->currextra] == tag_virt_basename) | 2673 | || csi->tagorder[c->currextra] == tag_virt_basename) |
2425 | attr = FILE_ATTR_AUDIO; | 2674 | attr = FILE_ATTR_AUDIO; |
@@ -2427,7 +2676,7 @@ int tagtree_get_attr(struct tree_context* c) | |||
2427 | attr = ATTR_DIRECTORY; | 2676 | attr = ATTR_DIRECTORY; |
2428 | break; | 2677 | break; |
2429 | 2678 | ||
2430 | case ALLSUBENTRIES: | 2679 | case TABLE_ALLSUBENTRIES: |
2431 | attr = FILE_ATTR_AUDIO; | 2680 | attr = FILE_ATTR_AUDIO; |
2432 | break; | 2681 | break; |
2433 | 2682 | ||