diff --git a/apps/misc.h b/apps/misc.h index 51684cb658..403a8c53ac 100644 --- a/apps/misc.h +++ b/apps/misc.h @@ -187,6 +187,9 @@ enum current_activity { ACTIVITY_USBSCREEN }; +/* custom string representation of activity */ +#define MAKE_ACT_STR(act) ((char[3]){'>', 'A'+ (act), 0x0}) + void beep_play(unsigned int frequency, unsigned int duration, unsigned int amplitude); diff --git a/apps/onplay.c b/apps/onplay.c index f92ed76050..96c2ddd4e3 100644 --- a/apps/onplay.c +++ b/apps/onplay.c @@ -1611,8 +1611,33 @@ static bool list_viewers(void) return false; } +#ifdef HAVE_TAGCACHE +static bool prepare_database_sel(void *param) +{ + if (context == CONTEXT_ID3DB && + (selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO) + { + if (!strcmp(param, "properties")) + strmemccpy(selected_file_path, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER), + sizeof(selected_file_path)); + else if (!tagtree_get_subentry_filename(selected_file_path, MAX_PATH)) + { + onplay_result = ONPLAY_RELOAD_DIR; + return false; + } + + selected_file = selected_file_path; + } + return true; +} +#endif + static bool onplay_load_plugin(void *param) { +#ifdef HAVE_TAGCACHE + if (!prepare_database_sel(param)) + return false; +#endif int ret = filetype_load_plugin((const char*)param, selected_file); if (ret == PLUGIN_USB_CONNECTED) onplay_result = ONPLAY_RELOAD_DIR; @@ -1717,10 +1742,8 @@ static int clipboard_callback(int action, #ifdef HAVE_TAGCACHE if (context == CONTEXT_ID3DB) { - if (((selected_file_attr & FILE_ATTR_MASK) == - FILE_ATTR_AUDIO) && - (this_item == &track_info_item || - this_item == &pictureflow_item)) + if (this_item == &track_info_item || + this_item == &pictureflow_item) return action; return ACTION_EXIT_MENUITEM; } @@ -1895,6 +1918,10 @@ static int hotkey_tree_pl_insert_shuffled(void) static int hotkey_tree_run_plugin(void *param) { +#ifdef HAVE_TAGCACHE + if (!prepare_database_sel(param)) + return ONPLAY_RELOAD_DIR; +#endif if (filetype_load_plugin((const char*)param, selected_file) == PLUGIN_GOTO_WPS) return ONPLAY_START_PLAY; diff --git a/apps/plugin.c b/apps/plugin.c index c28954e9eb..00fac21b8d 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -825,6 +825,9 @@ static const struct plugin_api rockbox_api = { splash_progress_set_delay, fix_path_part, onplay_show_playlist_cat_menu, +#if defined(HAVE_TAGCACHE) + tagtree_subentries_do_action, +#endif }; static int plugin_buffer_handle; diff --git a/apps/plugin.h b/apps/plugin.h index 850e7484d9..20df7e72f2 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -103,6 +103,7 @@ int plugin_open(const char *plugin, const char *parameter); #include "buflib.h" #include "buffering.h" #include "tagcache.h" +#include "tagtree.h" #include "viewport.h" #include "ata_idle_notify.h" #include "settings_list.h" @@ -157,7 +158,7 @@ int plugin_open(const char *plugin, const char *parameter); #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 263 +#define PLUGIN_API_VERSION 264 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -950,6 +951,9 @@ struct plugin_api { void (*fix_path_part)(char* path, int offset, int count); void (*onplay_show_playlist_cat_menu)(const char* track_name, int attr, void (*add_to_pl_cb)); +#ifdef HAVE_TAGCACHE + bool (*tagtree_subentries_do_action)(bool (*action_cb)(const char *file_name)); +#endif }; /* plugin header */ diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c index 46cf818fb4..7dacda3579 100644 --- a/apps/plugins/properties.c +++ b/apps/plugins/properties.c @@ -19,6 +19,11 @@ * ****************************************************************************/ #include "plugin.h" +#include "lib/id3.h" + +#ifdef HAVE_TAGCACHE +#include "lib/mul_id3.h" +#endif #if !defined(ARRAY_SIZE) #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) @@ -35,12 +40,16 @@ struct dir_stats { enum props_types { PROPS_FILE = 0, PROPS_ID3, + PROPS_MUL_ID3, PROPS_DIR }; static int props_type = PROPS_FILE; static struct mp3entry id3; +#ifdef HAVE_TAGCACHE +static int mul_id3_count; +#endif static char str_filename[MAX_PATH]; static char str_dirname[MAX_PATH]; @@ -118,14 +127,8 @@ static bool file_properties(const char* selected_file) rb->snprintf(str_time, sizeof str_time, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); - int fd = rb->open(selected_file, O_RDONLY); - if (fd >= 0) - { - if (rb->get_metadata(&id3, fd, selected_file)) - props_type = PROPS_ID3; - - rb->close(fd); - } + if (retrieve_id3(&id3, selected_file, false)) + props_type = PROPS_ID3; found = true; break; } @@ -369,6 +372,19 @@ static bool determine_file_or_dir(void) return false; } +#ifdef HAVE_TAGCACHE +bool mul_id3_add(const char *file_name) +{ + if (!retrieve_id3(&id3, file_name, false)) + return false; + + collect_id3(&id3, mul_id3_count == 0); + mul_id3_count++; + + return true; +} +#endif + enum plugin_status plugin_start(const void* parameter) { static struct dir_stats stats = @@ -380,40 +396,62 @@ enum plugin_status plugin_start(const void* parameter) }; const char *file = parameter; - if(!parameter || (file[0] != '/')) return PLUGIN_ERROR; + if(!parameter) + return PLUGIN_ERROR; #ifdef HAVE_TOUCHSCREEN rb->touchscreen_set_mode(rb->global_settings->touch_mode); #endif - const char* file_name = rb->strrchr(file, '/') + 1; - int dirlen = (file_name - file); - - rb->strlcpy(str_dirname, file, dirlen + 1); - rb->snprintf(str_filename, sizeof str_filename, "%s", file+dirlen); - - if(!determine_file_or_dir()) +#ifdef HAVE_TAGCACHE + if (!rb->strcmp(file, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER))) /* db table selected */ { - /* weird: we couldn't find the entry. This Should Never Happen (TM) */ - rb->splashf(0, "File/Dir not found: %s", file); - rb->action_userabort(TIMEOUT_BLOCK); - return PLUGIN_OK; - } + props_type = PROPS_MUL_ID3; + init_mul_id3(); + mul_id3_count = 0; - /* get the info depending on its_a_dir */ - if(!(props_type == PROPS_DIR ? dir_properties(file, &stats) : file_properties(file))) - { - /* something went wrong (to do: tell user what it was (nesting,...) */ - rb->splash(0, ID2P(LANG_PROPERTIES_FAIL)); - rb->action_userabort(TIMEOUT_BLOCK); - return PLUGIN_OK; + if (!rb->tagtree_subentries_do_action(&mul_id3_add) || mul_id3_count == 0) + return PLUGIN_ERROR; + + if (mul_id3_count > 1) /* otherwise, the retrieved id3 can be used as-is */ + write_id3_mul_tracks(&id3); } + else +#endif + if (file[0] == '/') /* single track selected */ + { + const char* file_name = rb->strrchr(file, '/') + 1; + int dirlen = (file_name - file); + + rb->strlcpy(str_dirname, file, dirlen + 1); + rb->snprintf(str_filename, sizeof str_filename, "%s", file+dirlen); + + if(!determine_file_or_dir()) + { + /* weird: we couldn't find the entry. This Should Never Happen (TM) */ + rb->splashf(0, "File/Dir not found: %s", file); + rb->action_userabort(TIMEOUT_BLOCK); + return PLUGIN_OK; + } + + /* get the info depending on its_a_dir */ + if(!(props_type == PROPS_DIR ? dir_properties(file, &stats) : file_properties(file))) + { + /* something went wrong (to do: tell user what it was (nesting,...) */ + rb->splash(0, ID2P(LANG_PROPERTIES_FAIL)); + rb->action_userabort(TIMEOUT_BLOCK); + return PLUGIN_OK; + } + } + else + return PLUGIN_ERROR; FOR_NB_SCREENS(i) rb->viewportmanager_theme_enable(i, true, NULL); - bool usb = props_type == PROPS_ID3 ? rb->browse_id3(&id3, 0, 0, &tm) : - browse_file_or_dir(&stats); + bool usb = props_type == PROPS_ID3 ? rb->browse_id3(&id3, 0, 0, &tm) : + (props_type == PROPS_MUL_ID3 ? rb->browse_id3(&id3, 0, 0, NULL) : + browse_file_or_dir(&stats)); FOR_NB_SCREENS(i) rb->viewportmanager_theme_undo(i, false); diff --git a/apps/tagtree.c b/apps/tagtree.c index 85359cac04..4248538f77 100644 --- a/apps/tagtree.c +++ b/apps/tagtree.c @@ -407,7 +407,7 @@ static int get_tag(int *tag) static int get_clause(int *condition) { - /* one or two operator conditionals */ + /* one or two operator conditionals */ #define OPS2VAL(op1, op2) ((int)op1 << 8 | (int)op2) #define CLAUSE(op1, op2, symbol) {OPS2VAL(op1, op2), symbol } @@ -2160,6 +2160,27 @@ static bool insert_all_playlist(struct tree_context *c, return true; } +static bool goto_allsubentries(int newtable) +{ + int i = 0; + while (i < 2 && (newtable == NAVIBROWSE || newtable == ALLSUBENTRIES)) + { + tagtree_enter(tc, false); + tagtree_load(tc); + newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; + i++; + } + return (newtable == PLAYTRACK); +} + +static void reset_tc_to_prev(int dirlevel, int selected_item) +{ + while (tc->dirlevel > dirlevel) + tagtree_exit(tc, false); + tc->selected_item = selected_item; + tagtree_load(tc); +} + static bool tagtree_insert_selection(int position, bool queue, const char* playlist, bool new_playlist) { @@ -2167,6 +2188,7 @@ static bool tagtree_insert_selection(int position, bool queue, int dirlevel = tc->dirlevel; int selected_item = tc->selected_item; int newtable; + int ret; show_search_progress( #ifdef HAVE_DISK_STORAGE @@ -2176,71 +2198,101 @@ static bool tagtree_insert_selection(int position, bool queue, #endif , 0); - - /* We need to set the table to allsubentries. */ newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; - /* Insert a single track? */ - if (newtable == PLAYTRACK) + if (newtable == PLAYTRACK) /* Insert a single track? */ { if (tagtree_get_filename(tc, buf, sizeof buf) < 0) - { - logf("tagtree_get_filename failed"); return false; - } + playlist_insert_track(NULL, buf, position, queue, true); return true; } - if (newtable == NAVIBROWSE) + ret = goto_allsubentries(newtable); + if (ret) { - tagtree_enter(tc, false); - tagtree_load(tc); - newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; - } - else if (newtable != ALLSUBENTRIES) - { - logf("unsupported table: %d", newtable); - return false; - } - - /* Now the current table should be allsubentries. */ - if (newtable != PLAYTRACK) - { - tagtree_enter(tc, false); - tagtree_load(tc); - newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; - - /* And now the newtable should be playtrack. */ - if (newtable != PLAYTRACK) - { - logf("newtable: %d !!", newtable); - while (tc->dirlevel > dirlevel) - tagtree_exit(tc, false); - tagtree_load(tc); - return false; - } - } - - if (tc->filesindir <= 0) - splash(HZ, ID2P(LANG_END_PLAYLIST)); - else - { - logf("insert_all_playlist"); - if (!insert_all_playlist(tc, playlist, new_playlist, position, queue)) + if (tc->filesindir <= 0) + splash(HZ, ID2P(LANG_END_PLAYLIST)); + else if (!insert_all_playlist(tc, playlist, new_playlist, position, queue)) splash(HZ*2, ID2P(LANG_FAILED)); } - /* Finally return the dirlevel to its original value. */ - while (tc->dirlevel > dirlevel) - tagtree_exit(tc, false); - tc->selected_item = selected_item; - tagtree_load(tc); - - return true; + reset_tc_to_prev(dirlevel, selected_item); + return ret; } +/* Execute action_cb for all subentries of the current table's + * selected item, handing over each entry's filename in the + * callback function parameter. + */ +bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name)) +{ + struct tagcache_search tcs; + int i, n; + unsigned long last_tick; + char buf[MAX_PATH]; + int ret = true; + int dirlevel = tc->dirlevel; + int selected_item = tc->selected_item; + int newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; + + cpu_boost(true); + if (!goto_allsubentries(newtable)) + ret = false; + else if (tagcache_search(&tcs, tag_filename)) + { + last_tick = current_tick + HZ/2; + splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ + n = tc->filesindir; + for (i = 0; i < n; i++) + { + splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); + if (TIME_AFTER(current_tick, last_tick + HZ/4)) + { + if (action_userabort(TIMEOUT_NOBLOCK)) + break; + last_tick = current_tick; + } + + if (!tagcache_retrieve(&tcs, tagtree_get_entry(tc, i)->extraseek, + tcs.type, buf, sizeof buf) + || !action_cb(buf)) + { + ret = false; + break; + } + yield(); + } + + tagcache_search_finish(&tcs); + } + else + { + splash(HZ, ID2P(LANG_TAGCACHE_BUSY)); + ret = false; + } + reset_tc_to_prev(dirlevel, selected_item); + cpu_boost(false); + return ret; +} + +/* Try to return first subentry's filename for current selection + */ +bool tagtree_get_subentry_filename(char *buf, size_t bufsize) +{ + int ret = true; + int dirlevel = tc->dirlevel; + int selected_item = tc->selected_item; + int newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; + + if (!goto_allsubentries(newtable) || tagtree_get_filename(tc, buf, bufsize) < 0) + ret = false; + + reset_tc_to_prev(dirlevel, selected_item); + return ret; +} bool tagtree_current_playlist_insert(int position, bool queue) { diff --git a/apps/tagtree.h b/apps/tagtree.h index 6eaaf3dfac..39ab545bd0 100644 --- a/apps/tagtree.h +++ b/apps/tagtree.h @@ -45,6 +45,8 @@ char *tagtree_get_title(struct tree_context* c); int tagtree_get_attr(struct tree_context* c); int tagtree_get_icon(struct tree_context* c); int tagtree_get_filename(struct tree_context* c, char *buf, int buflen); +bool tagtree_get_subentry_filename(char *buf, size_t bufsize); +bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name)); #endif #endif