From e7733abc409ec2f30bf750ae455077fb761b9701 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Wed, 15 Jan 2020 17:59:15 +0000 Subject: [PATCH] (Playlist Management) Add 'Clean Playlist' option --- intl/msg_hash_lbl.h | 2 + intl/msg_hash_us.h | 20 ++ menu/cbs/menu_cbs_ok.c | 22 ++ menu/cbs/menu_cbs_sublabel.c | 8 + menu/drivers/materialui.c | 9 +- menu/drivers/ozone/ozone_texture.c | 1 + menu/drivers/xmb.c | 1 + menu/menu_displaylist.c | 11 +- menu/menu_driver.h | 2 + msg_hash.h | 5 + playlist.c | 43 +++ playlist.h | 7 + tasks/task_playlist_manager.c | 545 ++++++++++++++++++++++++++--- tasks/tasks_internal.h | 1 + 14 files changed, 626 insertions(+), 51 deletions(-) diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 8e70c2f39e..54fb67f026 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -974,6 +974,8 @@ MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE, "playlist_manager_right_thumbnail_mode") MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, "playlist_manager_left_thumbnail_mode") +MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST, + "playlist_manager_clean_playlist") MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_SETTINGS_BEGIN, "playlist_settings_begin") MSG_HASH(MENU_ENUM_LABEL_POINTER_ENABLE, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 9377adf68c..3081630f42 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -2507,6 +2507,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_THUMBNAIL_MODE_DEFAULT, "System Default" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_CLEAN_PLAYLIST, + "Clean Playlist" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST, + "Remove invalid/duplicate entries and validate core associations." + ) +MSG_HASH( + MSG_PLAYLIST_MANAGER_CLEANING_PLAYLIST, + "Cleaning playlist: " + ) +MSG_HASH( + MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED, + "Playlist cleaned: " + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_POINTER_ENABLE, "Touch Support" @@ -10094,6 +10110,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_DELETE_PLAYLIST, "Delete Playlist" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_DELETE_PLAYLIST, + "Remove playlist from filesystem." + ) #ifdef HAVE_LAKKA MSG_HASH( MENU_ENUM_LABEL_VALUE_LOCALAP_ENABLE, diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index b785a2c829..2486a83280 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -6370,6 +6370,25 @@ static int action_ok_playlist_reset_cores(const char *path, return 0; } +static int action_ok_playlist_clean(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + playlist_t *playlist = playlist_get_cached(); + const char *playlist_path = NULL; + + if (!playlist) + return -1; + + playlist_path = playlist_get_conf_path(playlist); + + if (string_is_empty(playlist_path)) + return -1; + + task_push_pl_manager_clean_playlist(playlist_path); + + return 0; +} + static int is_rdb_entry(enum msg_hash_enums enum_idx) { switch (enum_idx) @@ -6785,6 +6804,9 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES: BIND_ACTION_OK(cbs, action_ok_playlist_reset_cores); break; + case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST: + BIND_ACTION_OK(cbs, action_ok_playlist_clean); + break; case MENU_ENUM_LABEL_RECORDING_SETTINGS: BIND_ACTION_OK(cbs, action_ok_push_recording_settings_list); break; diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index bbd3fd90e8..8d2372a6f9 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -148,6 +148,8 @@ default_sublabel_macro(action_bind_sublabel_playlist_manager_list, MENU_ default_sublabel_macro(action_bind_sublabel_playlist_manager_default_core, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_DEFAULT_CORE) default_sublabel_macro(action_bind_sublabel_playlist_manager_reset_cores, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_RESET_CORES) default_sublabel_macro(action_bind_sublabel_playlist_manager_label_display_mode, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE) +default_sublabel_macro(action_bind_sublabel_playlist_manager_clean_playlist, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST) +default_sublabel_macro(action_bind_sublabel_delete_playlist, MENU_ENUM_SUBLABEL_DELETE_PLAYLIST) default_sublabel_macro(action_bind_sublabel_network_settings_list, MENU_ENUM_SUBLABEL_NETWORK_SETTINGS) default_sublabel_macro(action_bind_sublabel_network_on_demand_thumbnails, MENU_ENUM_SUBLABEL_NETWORK_ON_DEMAND_THUMBNAILS) default_sublabel_macro(action_bind_sublabel_user_settings_list, MENU_ENUM_SUBLABEL_USER_SETTINGS) @@ -2796,6 +2798,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_manager_label_display_mode); break; + case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_manager_clean_playlist); + break; case MENU_ENUM_LABEL_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE: settings = config_get_ptr(); /* Uses same sublabels as MENU_ENUM_LABEL_THUMBNAILS */ @@ -2832,6 +2837,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_left_thumbnails); } break; + case MENU_ENUM_LABEL_DELETE_PLAYLIST: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_delete_playlist); + break; case MENU_ENUM_LABEL_AI_SERVICE_URL: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_ai_service_url); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 410e526884..c4e4f2a434 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -7511,7 +7511,8 @@ static void materialui_list_insert( } else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_RENAME_ENTRY)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_RESET_CORE_ASSOCIATION)) || - string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES))) + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST))) { node->icon_texture_index = MUI_TEXTURE_RENAME; node->has_icon = true; @@ -7618,7 +7619,8 @@ static void materialui_list_insert( node->icon_texture_index = MUI_TEXTURE_LOAD_CONTENT; node->has_icon = true; } - else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DELETE_ENTRY))) + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DELETE_ENTRY)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DELETE_PLAYLIST))) { node->icon_texture_index = MUI_TEXTURE_REMOVE; node->has_icon = true; @@ -7751,11 +7753,8 @@ static void materialui_list_insert( string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CHEAT_SEARCH_SETTINGS)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CHEAT_START_OR_CONT)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_INPUT_META_CHEAT_SEARCH)) || - string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_DEFAULT_CORE)) || - string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_THUMBNAILS_MATERIALUI)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LEFT_THUMBNAILS_MATERIALUI)) || - string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DELETE_PLAYLIST)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_VIDEO_SHADER_NUM_PASSES)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CHEAT_FILE_LOAD)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CHEAT_FILE_LOAD_APPEND)) || diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c index fe05d0a9f3..7bb6d281cf 100644 --- a/menu/drivers/ozone/ozone_texture.c +++ b/menu/drivers/ozone/ozone_texture.c @@ -285,6 +285,7 @@ menu_texture_item ozone_entries_icon_get_texture(ozone_handle_t *ozone, case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE: case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL: case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS: + case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD]; case MENU_ENUM_LABEL_SHUTDOWN: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHUTDOWN]; diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 5d2ae2d2ed..15da4b7e7e 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2353,6 +2353,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE: case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL: case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS: + case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST: return xmb->textures.list[XMB_TEXTURE_RELOAD]; case MENU_ENUM_LABEL_RENAME_ENTRY: return xmb->textures.list[XMB_TEXTURE_RENAME]; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index f074cb2a1c..940ea71a65 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -2869,7 +2869,7 @@ static bool menu_displaylist_parse_playlist_manager_settings( msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_RESET_CORES), msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES), MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES, - FILE_TYPE_PLAYLIST_ENTRY, 0, 0); + MENU_SETTING_ACTION_PLAYLIST_MANAGER_RESET_CORES, 0, 0); /* Label display mode */ menu_entries_append_enum(info->list, @@ -2916,9 +2916,14 @@ static bool menu_displaylist_parse_playlist_manager_settings( MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, MENU_SETTING_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, 0, 0); - /* TODO - Add: - * - Remove invalid entries */ + /* Clean playlist */ + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_CLEAN_PLAYLIST), + msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST), + MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST, + MENU_SETTING_ACTION_PLAYLIST_MANAGER_CLEAN_PLAYLIST, 0, 0); + /* Delete playlist */ menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DELETE_PLAYLIST), msg_hash_to_str(MENU_ENUM_LABEL_DELETE_PLAYLIST), diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 8aba059670..0aac3add12 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -213,6 +213,8 @@ enum menu_settings_type MENU_SET_LOAD_CDROM_LIST, MENU_SET_CDROM_INFO, MENU_SETTING_ACTION_DELETE_PLAYLIST, + MENU_SETTING_ACTION_PLAYLIST_MANAGER_RESET_CORES, + MENU_SETTING_ACTION_PLAYLIST_MANAGER_CLEAN_PLAYLIST, MENU_SETTING_MANUAL_CONTENT_SCAN_DIR, MENU_SETTING_MANUAL_CONTENT_SCAN_SYSTEM_NAME, diff --git a/msg_hash.h b/msg_hash.h index d8459f1eb7..25b63312ab 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -2027,6 +2027,11 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_THUMBNAIL_MODE_DEFAULT, + MENU_LABEL(PLAYLIST_MANAGER_CLEAN_PLAYLIST), + + MSG_PLAYLIST_MANAGER_CLEANING_PLAYLIST, + MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED, + MENU_LABEL(CORE_UPDATER_SETTINGS), MENU_LABEL(LAKKA_SERVICES), MENU_LABEL(SHADER_APPLY_CHANGES), diff --git a/playlist.c b/playlist.c index ec7efd9f2b..b7a6653469 100644 --- a/playlist.c +++ b/playlist.c @@ -2498,6 +2498,49 @@ bool playlist_index_is_valid(playlist_t *playlist, size_t idx, string_is_equal(path_basename(playlist->entries[idx].core_path), path_basename(core_path)); } +bool playlist_entries_are_equal( + const struct playlist_entry *entry_a, + const struct playlist_entry *entry_b, + bool fuzzy_archive_match) +{ + char real_path_a[PATH_MAX_LENGTH]; + char real_core_path_a[PATH_MAX_LENGTH]; + + real_path_a[0] = '\0'; + real_core_path_a[0] = '\0'; + + /* Sanity check */ + if (!entry_a || !entry_b) + return false; + + if (string_is_empty(entry_a->path) && + string_is_empty(entry_a->core_path) && + string_is_empty(entry_b->path) && + string_is_empty(entry_b->core_path)) + return true; + + /* Check content paths */ + if (!string_is_empty(entry_a->path)) + { + strlcpy(real_path_a, entry_a->path, sizeof(real_path_a)); + path_resolve_realpath(real_path_a, sizeof(real_path_a), true); + } + + if (!playlist_path_equal( + real_path_a, entry_b->path, fuzzy_archive_match)) + return false; + + /* Check core paths */ + if (!string_is_empty(entry_a->core_path)) + { + strlcpy(real_core_path_a, entry_a->core_path, sizeof(real_core_path_a)); + if (!string_is_equal(real_core_path_a, "DETECT")) + path_resolve_realpath(real_core_path_a, sizeof(real_core_path_a), true); + } + + return playlist_core_path_equal(real_core_path_a, entry_b->core_path); +} + void playlist_get_crc32(playlist_t *playlist, size_t idx, const char **crc32) { diff --git a/playlist.h b/playlist.h index 8eac8e8ccd..d724e73f73 100644 --- a/playlist.h +++ b/playlist.h @@ -252,6 +252,13 @@ void command_playlist_update_write( bool playlist_index_is_valid(playlist_t *playlist, size_t idx, const char *path, const char *core_path); +/* Returns true if specified playlist entries have + * identical content and core paths */ +bool playlist_entries_are_equal( + const struct playlist_entry *entry_a, + const struct playlist_entry *entry_b, + bool fuzzy_archive_match); + void playlist_get_crc32(playlist_t *playlist, size_t idx, const char **crc32); diff --git a/tasks/task_playlist_manager.c b/tasks/task_playlist_manager.c index 149f68c850..af892e1c40 100644 --- a/tasks/task_playlist_manager.c +++ b/tasks/task_playlist_manager.c @@ -22,6 +22,7 @@ #include #include +#include #include "tasks_internal.h" @@ -29,11 +30,15 @@ #include "../msg_hash.h" #include "../file_path_special.h" #include "../playlist.h" +#include "../core_info.h" enum pl_manager_status { PL_MANAGER_BEGIN = 0, - PL_MANAGER_ITERATE_ENTRY, + PL_MANAGER_ITERATE_ENTRY_RESET_CORE, + PL_MANAGER_ITERATE_ENTRY_VALIDATE, + PL_MANAGER_VALIDATE_END, + PL_MANAGER_ITERATE_ENTRY_CHECK_DUPLICATE, PL_MANAGER_END }; @@ -46,11 +51,12 @@ typedef struct pl_manager_handle size_t list_index; enum pl_manager_status status; bool use_old_format; + bool fuzzy_archive_match; } pl_manager_handle_t; -/**************************/ -/* Reset Associated Cores */ -/**************************/ +/*********************/ +/* Utility Functions */ +/*********************/ static void free_pl_manager_handle(pl_manager_handle_t *pl_manager) { @@ -79,6 +85,36 @@ static void free_pl_manager_handle(pl_manager_handle_t *pl_manager) pl_manager = NULL; } +static void pl_manager_write_playlist( + playlist_t *playlist, const char *playlist_path, bool use_old_format) +{ + playlist_t *cached_playlist = playlist_get_cached(); + + /* Sanity check */ + if (!playlist || string_is_empty(playlist_path)) + return; + + /* Write any changes to playlist file */ + playlist_write_file(playlist, use_old_format); + + /* If this is the currently cached playlist, then + * it must be re-cached (otherwise changes will be + * lost if the currently cached playlist is saved + * to disk for any reason...) */ + if (cached_playlist) + { + if (string_is_equal(playlist_path, playlist_get_conf_path(cached_playlist))) + { + playlist_free_cached(); + playlist_init_cached(playlist_path, COLLECTION_SIZE); + } + } +} + +/**************************/ +/* Reset Associated Cores */ +/**************************/ + static void task_pl_manager_reset_cores_handler(retro_task_t *task) { pl_manager_handle_t *pl_manager = NULL; @@ -113,10 +149,10 @@ static void task_pl_manager_reset_cores_handler(retro_task_t *task) goto task_finished; /* All good - can start iterating */ - pl_manager->status = PL_MANAGER_ITERATE_ENTRY; + pl_manager->status = PL_MANAGER_ITERATE_ENTRY_RESET_CORE; } break; - case PL_MANAGER_ITERATE_ENTRY: + case PL_MANAGER_ITERATE_ENTRY_RESET_CORE: { const struct playlist_entry *entry = NULL; @@ -128,10 +164,8 @@ static void task_pl_manager_reset_cores_handler(retro_task_t *task) { struct playlist_entry update_entry = {0}; char task_title[PATH_MAX_LENGTH]; - char detect_string[PATH_MAX_LENGTH]; - task_title[0] = '\0'; - detect_string[0] = '\0'; + task_title[0] = '\0'; /* Update progress display */ task_free_title(task); @@ -154,17 +188,11 @@ static void task_pl_manager_reset_cores_handler(retro_task_t *task) task_set_title(task, strdup(task_title)); task_set_progress(task, (pl_manager->list_index * 100) / pl_manager->list_size); - /* Reset core association */ - detect_string[0] = 'D'; - detect_string[1] = 'E'; - detect_string[2] = 'T'; - detect_string[3] = 'E'; - detect_string[4] = 'C'; - detect_string[5] = 'T'; - detect_string[6] = '\0'; - - update_entry.core_path = detect_string; - update_entry.core_name = detect_string; + /* Reset core association + * > The update function reads our entry as const, + * so these casts are safe */ + update_entry.core_path = (char*)"DETECT"; + update_entry.core_name = (char*)"DETECT"; playlist_update( pl_manager->playlist, pl_manager->list_index, &update_entry); @@ -178,26 +206,15 @@ static void task_pl_manager_reset_cores_handler(retro_task_t *task) break; case PL_MANAGER_END: { - playlist_t *cached_playlist = playlist_get_cached(); char task_title[PATH_MAX_LENGTH]; task_title[0] = '\0'; /* Save playlist changes to disk */ - playlist_write_file(pl_manager->playlist, pl_manager->use_old_format); - - /* If this is the currently cached playlist, then - * it must be re-cached (otherwise changes will be - * lost if the currently cached playlist is saved - * to disk for any reason...) */ - if (cached_playlist) - { - if (string_is_equal(pl_manager->playlist_path, playlist_get_conf_path(cached_playlist))) - { - playlist_free_cached(); - playlist_init_cached(pl_manager->playlist_path, COLLECTION_SIZE); - } - } + pl_manager_write_playlist( + pl_manager->playlist, + pl_manager->playlist_path, + pl_manager->use_old_format); /* Update progress display */ task_free_title(task); @@ -255,7 +272,7 @@ bool task_push_pl_manager_reset_cores(const char *playlist_path) task_title[0] = '\0'; /* Sanity check */ - if (!task || !pl_manager) + if (!task || !pl_manager || !settings) goto error; if (string_is_empty(playlist_path)) @@ -287,13 +304,455 @@ bool task_push_pl_manager_reset_cores(const char *playlist_path) task->progress = 0; /* Configure handle */ - pl_manager->playlist_path = strdup(playlist_path); - pl_manager->playlist_name = strdup(playlist_name); - pl_manager->playlist = NULL; - pl_manager->list_size = 0; - pl_manager->list_index = 0; - pl_manager->status = PL_MANAGER_BEGIN; - pl_manager->use_old_format = settings->bools.playlist_use_old_format; + pl_manager->playlist_path = strdup(playlist_path); + pl_manager->playlist_name = strdup(playlist_name); + pl_manager->playlist = NULL; + pl_manager->list_size = 0; + pl_manager->list_index = 0; + pl_manager->status = PL_MANAGER_BEGIN; + pl_manager->use_old_format = settings->bools.playlist_use_old_format; + pl_manager->fuzzy_archive_match = false; /* Not relevant here */ + + task_queue_push(task); + + return true; + +error: + + if (task) + { + free(task); + task = NULL; + } + + if (pl_manager) + { + free(pl_manager); + pl_manager = NULL; + } + + return false; +} + +/******************/ +/* Clean Playlist */ +/******************/ + +static bool pl_manager_content_exists(const char *path) +{ + /* Sanity check */ + if (string_is_empty(path)) + return false; + + /* If content is inside an archive, special + * handling is required... */ + if (path_contains_compressed_file(path)) + { + const char *delim = path_get_archive_delim(path); + char archive_path[PATH_MAX_LENGTH] = {0}; + size_t len = 0; + struct string_list *archive_list = NULL; + const char *content_file = NULL; + bool content_found = false; + + if (!delim) + return false; + + /* Get path of 'parent' archive file */ + len = (size_t)(1 + delim - path); + strlcpy( + archive_path, path, + (len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char)); + + /* Check if archive itself exists */ + if (!path_is_valid(archive_path)) + return false; + + /* Check if file exists inside archive */ + archive_list = file_archive_get_file_list(archive_path, NULL); + + if (!archive_list) + return false; + + /* > Get playlist entry content file name + * (sans archive file path) */ + content_file = delim; + content_file++; + + if (!string_is_empty(content_file)) + { + size_t i; + + /* > Loop over archive file contents */ + for (i = 0; i < archive_list->size; i++) + { + const char *archive_file = archive_list->elems[i].data; + + if (string_is_empty(archive_file)) + continue; + + if (string_is_equal(content_file, archive_file)) + { + content_found = true; + break; + } + } + } + + /* Clean up */ + string_list_free(archive_list); + + return content_found; + } + /* This is a 'normal' path - just check if + * it's valid */ + else + return path_is_valid(path); +} + +static void pl_manager_validate_core_association( + playlist_t *playlist, size_t entry_index, + const char *core_path, const char *core_name) +{ + struct playlist_entry update_entry = {0}; + + /* Sanity check */ + if (!playlist) + return; + + if (entry_index >= playlist_size(playlist)) + return; + + if (string_is_empty(core_path)) + goto reset_core; + + /* Handle 'DETECT' entries */ + if (string_is_equal(core_path, "DETECT")) + { + if (!string_is_equal(core_name, "DETECT")) + goto reset_core; + } + /* Handle 'builtin' entries */ + else if (string_is_equal(core_path, "builtin")) + { + if (string_is_empty(core_name)) + goto reset_core; + } + /* Handle file path entries */ + else if (!path_is_valid(core_path)) + goto reset_core; + else + { + const char *core_path_basename = path_basename(core_path); + core_info_list_t *core_info = NULL; + char core_display_name[PATH_MAX_LENGTH]; + size_t i; + + core_display_name[0] = '\0'; + + if (string_is_empty(core_path_basename)) + goto reset_core; + + /* Final check - search core info */ + core_info_get_list(&core_info); + + if (core_info) + { + for (i = 0; i < core_info->count; i++) + { + const char *info_display_name = core_info->list[i].display_name; + + if (!string_is_equal( + path_basename(core_info->list[i].path), core_path_basename)) + continue; + + if (!string_is_empty(info_display_name)) + strlcpy(core_display_name, info_display_name, sizeof(core_display_name)); + + break; + } + } + + /* If core_display_name string is empty, it means the + * core wasn't found -> reset association */ + if (string_is_empty(core_display_name)) + goto reset_core; + + /* ...Otherwise, check that playlist entry + * core name is correct */ + if (!string_is_equal(core_name, core_display_name)) + { + update_entry.core_name = core_display_name; + playlist_update(playlist, entry_index, &update_entry); + } + } + + return; + +reset_core: + /* The update function reads our entry as const, + * so these casts are safe */ + update_entry.core_path = (char*)"DETECT"; + update_entry.core_name = (char*)"DETECT"; + + playlist_update(playlist, entry_index, &update_entry); +} + +static void task_pl_manager_clean_playlist_handler(retro_task_t *task) +{ + pl_manager_handle_t *pl_manager = NULL; + + if (!task) + goto task_finished; + + pl_manager = (pl_manager_handle_t*)task->state; + + if (!pl_manager) + goto task_finished; + + if (task_get_cancelled(task)) + goto task_finished; + + switch (pl_manager->status) + { + case PL_MANAGER_BEGIN: + { + /* Load playlist */ + if (!path_is_valid(pl_manager->playlist_path)) + goto task_finished; + + pl_manager->playlist = playlist_init(pl_manager->playlist_path, COLLECTION_SIZE); + + if (!pl_manager->playlist) + goto task_finished; + + pl_manager->list_size = playlist_size(pl_manager->playlist); + + if (pl_manager->list_size < 1) + goto task_finished; + + /* All good - can start iterating */ + pl_manager->status = PL_MANAGER_ITERATE_ENTRY_VALIDATE; + } + break; + case PL_MANAGER_ITERATE_ENTRY_VALIDATE: + { + const struct playlist_entry *entry = NULL; + bool entry_deleted = false; + + /* Update progress display */ + task_set_progress(task, (pl_manager->list_index * 50) / pl_manager->list_size); + + /* Get current entry */ + playlist_get_index( + pl_manager->playlist, pl_manager->list_index, &entry); + + if (entry) + { + /* Check whether playlist content exists on + * the filesystem */ + if (!pl_manager_content_exists(entry->path)) + { + /* Invalid content - delete entry */ + playlist_delete_index(pl_manager->playlist, pl_manager->list_index); + entry_deleted = true; + + /* Update list_size */ + pl_manager->list_size = playlist_size(pl_manager->playlist); + } + /* Content is valid - check if core is valid */ + else + pl_manager_validate_core_association( + pl_manager->playlist, pl_manager->list_index, + entry->core_path, entry->core_name); + } + + /* Increment entry index *if* current entry still + * exists (i.e. if entry was deleted, current index + * will already point to the *next* entry) */ + if (!entry_deleted) + pl_manager->list_index++; + + if (pl_manager->list_index >= pl_manager->list_size) + pl_manager->status = PL_MANAGER_VALIDATE_END; + } + break; + case PL_MANAGER_VALIDATE_END: + { + /* Sanity check - if all (or all but one) + * playlist entries were removed during the + * 'validate' phase, we can stop now */ + if (pl_manager->list_size < 2) + { + pl_manager->status = PL_MANAGER_END; + break; + } + + /* ...otherwise, reset index counter and + * start the duplicates check */ + pl_manager->list_index = 0; + pl_manager->status = PL_MANAGER_ITERATE_ENTRY_CHECK_DUPLICATE; + } + break; + case PL_MANAGER_ITERATE_ENTRY_CHECK_DUPLICATE: + { + const struct playlist_entry *entry = NULL; + bool entry_deleted = false; + + /* Update progress display */ + task_set_progress(task, 50 + (pl_manager->list_index * 50) / pl_manager->list_size); + + /* Get current entry */ + playlist_get_index( + pl_manager->playlist, pl_manager->list_index, &entry); + + if (entry) + { + size_t i; + + /* Loop over all subsequent entries, and check + * whether content + core paths are the same */ + for (i = pl_manager->list_index + 1; i < pl_manager->list_size; i++) + { + const struct playlist_entry *next_entry = NULL; + + /* Get next entry */ + playlist_get_index(pl_manager->playlist, i, &next_entry); + + if (!next_entry) + continue; + + if (playlist_entries_are_equal( + entry, next_entry, pl_manager->fuzzy_archive_match)) + { + /* Duplicate found - delete entry */ + playlist_delete_index(pl_manager->playlist, pl_manager->list_index); + entry_deleted = true; + + /* Update list_size */ + pl_manager->list_size = playlist_size(pl_manager->playlist); + } + } + } + + /* Increment entry index *if* current entry still + * exists (i.e. if entry was deleted, current index + * will already point to the *next* entry) */ + if (!entry_deleted) + pl_manager->list_index++; + + if (pl_manager->list_index + 1 >= pl_manager->list_size) + pl_manager->status = PL_MANAGER_END; + } + break; + case PL_MANAGER_END: + { + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Save playlist changes to disk */ + pl_manager_write_playlist( + pl_manager->playlist, + pl_manager->playlist_path, + pl_manager->use_old_format); + + /* Update progress display */ + task_free_title(task); + + strlcpy( + task_title, msg_hash_to_str(MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED), + sizeof(task_title)); + strlcat(task_title, pl_manager->playlist_name, sizeof(task_title)); + + task_set_title(task, strdup(task_title)); + } + /* fall-through */ + default: + task_set_progress(task, 100); + goto task_finished; + } + + return; + +task_finished: + + if (task) + task_set_finished(task, true); + + free_pl_manager_handle(pl_manager); +} + +static bool task_pl_manager_clean_playlist_finder(retro_task_t *task, void *user_data) +{ + pl_manager_handle_t *pl_manager = NULL; + + if (!task || !user_data) + return false; + + if (task->handler != task_pl_manager_clean_playlist_handler) + return false; + + pl_manager = (pl_manager_handle_t*)task->state; + if (!pl_manager) + return false; + + return string_is_equal((const char*)user_data, pl_manager->playlist_path); +} + +bool task_push_pl_manager_clean_playlist(const char *playlist_path) +{ + task_finder_data_t find_data; + char playlist_name[PATH_MAX_LENGTH]; + char task_title[PATH_MAX_LENGTH]; + settings_t *settings = config_get_ptr(); + retro_task_t *task = task_init(); + pl_manager_handle_t *pl_manager = (pl_manager_handle_t*)calloc(1, sizeof(pl_manager_handle_t)); + + playlist_name[0] = '\0'; + task_title[0] = '\0'; + + /* Sanity check */ + if (!task || !pl_manager || !settings) + goto error; + + if (string_is_empty(playlist_path)) + goto error; + + fill_pathname_base_noext(playlist_name, playlist_path, sizeof(playlist_name)); + + if (string_is_empty(playlist_name)) + goto error; + + /* Concurrent management of the same playlist + * is not allowed */ + find_data.func = task_pl_manager_clean_playlist_finder; + find_data.userdata = (void*)playlist_path; + + if (task_queue_find(&find_data)) + goto error; + + /* Configure task */ + strlcpy( + task_title, msg_hash_to_str(MSG_PLAYLIST_MANAGER_CLEANING_PLAYLIST), + sizeof(task_title)); + strlcat(task_title, playlist_name, sizeof(task_title)); + + task->handler = task_pl_manager_clean_playlist_handler; + task->state = pl_manager; + task->title = strdup(task_title); + task->alternative_look = true; + task->progress = 0; + + /* Configure handle */ + pl_manager->playlist_path = strdup(playlist_path); + pl_manager->playlist_name = strdup(playlist_name); + pl_manager->playlist = NULL; + pl_manager->list_size = 0; + pl_manager->list_index = 0; + pl_manager->status = PL_MANAGER_BEGIN; + pl_manager->use_old_format = settings->bools.playlist_use_old_format; + pl_manager->fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; task_queue_push(task); diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index 702d5985e1..6e10f8a455 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -95,6 +95,7 @@ bool task_push_pl_entry_thumbnail_download( #endif bool task_push_pl_manager_reset_cores(const char *playlist_path); +bool task_push_pl_manager_clean_playlist(const char *playlist_path); bool task_push_image_load(const char *fullpath, bool supports_rgba, unsigned upscale_threshold,