diff --git a/Makefile.common b/Makefile.common index 65eafce53f..f611336b3b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -163,6 +163,7 @@ OBJ += frontend/frontend_driver.o \ tasks/task_file_transfer.o \ tasks/task_image.o \ tasks/task_playlist_manager.o \ + tasks/task_manual_content_scan.o \ $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.o \ $(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.o \ $(LIBRETRO_COMM_DIR)/encodings/encoding_base64.o \ @@ -258,8 +259,8 @@ OBJ += \ performance_counters.o \ verbosity.o \ midi/drivers/null_midi.o \ - $(LIBRETRO_COMM_DIR)/playlists/label_sanitization.o - + $(LIBRETRO_COMM_DIR)/playlists/label_sanitization.o \ + manual_content_scan.o ifeq ($(HAVE_AUDIOMIXER), 1) DEFINES += -DHAVE_AUDIOMIXER diff --git a/file_path_special.h b/file_path_special.h index c9e531056d..a6b13d2671 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -79,6 +79,7 @@ enum file_path_enum FILE_PATH_LPL_EXTENSION, FILE_PATH_LPL_EXTENSION_NO_DOT, FILE_PATH_RDB_EXTENSION, + FILE_PATH_RDB_EXTENSION_NO_DOT, FILE_PATH_BSV_EXTENSION, FILE_PATH_AUTO_EXTENSION, FILE_PATH_ZIP_EXTENSION, diff --git a/file_path_str.c b/file_path_str.c index 8409f66356..97e7e16887 100644 --- a/file_path_str.c +++ b/file_path_str.c @@ -130,6 +130,9 @@ const char *file_path_str(enum file_path_enum enum_idx) case FILE_PATH_RDB_EXTENSION: str = ".rdb"; break; + case FILE_PATH_RDB_EXTENSION_NO_DOT: + str = "rdb"; + break; case FILE_PATH_ZIP_EXTENSION: str = ".zip"; break; diff --git a/griffin/griffin.c b/griffin/griffin.c index 2ca9826e74..74308b86ff 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1205,6 +1205,7 @@ DATA RUNLOOP #include "../tasks/task_image.c" #include "../tasks/task_file_transfer.c" #include "../tasks/task_playlist_manager.c" +#include "../tasks/task_manual_content_scan.c" #ifdef HAVE_ZLIB #include "../tasks/task_decompress.c" #endif @@ -1634,3 +1635,8 @@ SSL PLAYLIST NAME SANITIZATION ============================================================ */ #include "../libretro-common/playlists/label_sanitization.c" + +/*============================================================ +MANUAL CONTENT SCAN +============================================================ */ +#include "../manual_content_scan.c" diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index a4a6c06989..e608b0118f 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -2105,3 +2105,25 @@ MSG_HASH(MENU_ENUM_LABEL_DRIVER_SWITCH_ENABLE, "driver_switch_enable") MSG_HASH(MENU_ENUM_LABEL_AI_SERVICE_PAUSE, "ai_service_pause") +MSG_HASH(MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST, + "deferred_manual_content_scan_list") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, + "manual_content_scan_list") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR, + "manual_content_scan_dir") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + "manual_content_scan_system_name") +MSG_HASH(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + "deferred_dropdown_box_list_manual_content_scan_system_name") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, + "manual_content_scan_system_name_custom") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME, + "manual_content_scan_core_name") +MSG_HASH(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME, + "deferred_dropdown_box_list_manual_content_scan_core_name") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_FILE_EXTS, + "manual_content_scan_file_exts") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE, + "manual_content_scan_overwrite") +MSG_HASH(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START, + "manual_content_scan_start") diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 9b84829d65..c0543cf1e6 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -10040,3 +10040,99 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_PAUSE, "Pauses core while screen is translated." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST, + "Manual Scan" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_LIST, + "Configurable scan based on content file names. Does not require content to match the database." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_DIR, + "Content Directory" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DIR, + "Selects a directory to scan for content." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + "System Name" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + "Specify a 'system name' with which to associate scanned content. Used to name to the generated playlist file and to identify playlist thumbnails." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, + "Custom System Name" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, + "Manually specify a 'system name' for scanned content. Only used when 'System Name' is set to ''." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME, + "Core" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_CORE_NAME, + "Select a default core to use when launching scanned content." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_FILE_EXTS, + "File Extensions" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_FILE_EXTS, + "Space-delimited list of file types to include in the scan. If empty, includes all files - or if a core is specified, all files supported by the core." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_OVERWRITE, + "Overwrite Existing Playlist" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_OVERWRITE, + "When enabled, any existing playlist will be deleted before scanning content. When disabled, existing playlist entries are preserved and only content currently missing from the playlist will be added." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_START, + "Start Scan" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_START, + "Scan selected content." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR, + "" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CUSTOM, + "" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT, + "" + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG, + "Invalid manual scan configuration" + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT, + "No valid content detected" + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_START, + "Scanning content: " + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS, + "Scanning: " + ) +MSG_HASH( + MSG_MANUAL_CONTENT_SCAN_END, + "Scan complete: " + ) diff --git a/libretro-common/include/string/stdstring.h b/libretro-common/include/string/stdstring.h index 1e5e0a3bd3..29e3428259 100644 --- a/libretro-common/include/string/stdstring.h +++ b/libretro-common/include/string/stdstring.h @@ -133,6 +133,10 @@ char* string_tokenize(char **str, const char *delim); /* Removes every instance of character 'c' from 'str' */ void string_remove_all_chars(char *str, char c); +/* Replaces every instance of character 'find' in 'str' + * with character 'replace' */ +void string_replace_all_chars(char *str, char find, char replace); + /* Converts string to unsigned integer. * Returns 0 if string is invalid */ unsigned string_to_unsigned(const char *str); diff --git a/libretro-common/string/stdstring.c b/libretro-common/string/stdstring.c index 51258ea424..507aa9fafd 100644 --- a/libretro-common/string/stdstring.c +++ b/libretro-common/string/stdstring.c @@ -319,6 +319,19 @@ void string_remove_all_chars(char *str, char c) *write_ptr = '\0'; } +/* Replaces every instance of character 'find' in 'str' + * with character 'replace' */ +void string_replace_all_chars(char *str, char find, char replace) +{ + char *str_ptr = str; + + if (string_is_empty(str)) + return; + + while((str_ptr = strchr(str_ptr, find)) != NULL) + *str_ptr++ = replace; +} + /* Converts string to unsigned integer. * Returns 0 if string is invalid */ unsigned string_to_unsigned(const char *str) diff --git a/manual_content_scan.c b/manual_content_scan.c new file mode 100644 index 0000000000..239897627a --- /dev/null +++ b/manual_content_scan.c @@ -0,0 +1,875 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (manual_content_scan.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "configuration.h" +#include "msg_hash.h" +#include "list_special.h" +#include "core_info.h" +#include "file_path_special.h" + +#include "manual_content_scan.h" + +/* Holds all configuration parameters associated + * with a manual content scan */ +typedef struct +{ + char content_dir[PATH_MAX_LENGTH]; + char system_name_content_dir[PATH_MAX_LENGTH]; + char system_name_database[PATH_MAX_LENGTH]; + char system_name_custom[PATH_MAX_LENGTH]; + char core_name[PATH_MAX_LENGTH]; + char core_path[PATH_MAX_LENGTH]; + char file_exts_core[PATH_MAX_LENGTH]; + char file_exts_custom[PATH_MAX_LENGTH]; + enum manual_content_scan_system_name_type system_name_type; + enum manual_content_scan_core_type core_type; + bool overwrite_playlist; +} scan_settings_t; + +/* Static settings object + * > Provides easy access to settings parameters + * when creating associated menu entries + * > We are handling this in almost exactly the same + * way as the regular global 'static settings_t *configuration_settings;' + * object in retroarch.c. This means it is not inherently thread safe, + * but this should not be an issue (i.e. regular configuration_settings + * are not thread safe, but we only access them when pushing a + * task, not in the task thread itself, so all is well) */ +static scan_settings_t scan_settings = { + "", /* content_dir */ + "", /* system_name_content_dir */ + "", /* system_name_database */ + "", /* system_name_custom */ + "", /* core_name */ + "", /* core_path */ + "", /* file_exts_core */ + "", /* file_exts_custom */ + MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR, /* system_name_type */ + MANUAL_CONTENT_SCAN_CORE_DETECT, /* core_type */ + false /* overwrite_playlist */ +}; + +/*****************/ +/* Configuration */ +/*****************/ + +/* Pointer access */ + +/* Returns a pointer to the internal + * 'system_name_custom' string */ +char *manual_content_scan_get_system_name_custom_ptr(void) +{ + return scan_settings.system_name_custom; +} + +/* Returns size of the internal + * 'system_name_custom' string */ +size_t manual_content_scan_get_system_name_custom_size(void) +{ + return sizeof(scan_settings.system_name_custom); +} + +/* Returns a pointer to the internal + * 'file_exts_custom' string */ +char *manual_content_scan_get_file_exts_custom_ptr(void) +{ + return scan_settings.file_exts_custom; +} + +/* Returns size of the internal + * 'file_exts_custom' string */ +size_t manual_content_scan_get_file_exts_custom_size(void) +{ + return sizeof(scan_settings.file_exts_custom); +} + +/* Returns a pointer to the internal + * 'overwrite_playlist' bool */ +bool *manual_content_scan_get_overwrite_playlist_ptr(void) +{ + return &scan_settings.overwrite_playlist; +} + +/* Sanitisation */ + +/* Sanitises file extensions list string: + * > Removes period (full stop) characters + * > Converts to lower case + * > Trims leading/trailing whitespace */ +static void manual_content_scan_scrub_file_exts(char *file_exts) +{ + if (string_is_empty(file_exts)) + return; + + string_remove_all_chars(file_exts, '.'); + string_to_lower(file_exts); + string_trim_whitespace(file_exts); +} + +/* Removes invalid characters from + * 'system_name_custom' string */ +void manual_content_scan_scrub_system_name_custom(void) +{ + char *scrub_char_pointer = NULL; + + if (string_is_empty(scan_settings.system_name_custom)) + return; + + /* Scrub characters that are not cross-platform + * and/or violate the No-Intro filename standard: + * http://datomatic.no-intro.org/stuff/The%20Official%20No-Intro%20Convention%20(20071030).zip + * Replace these characters with underscores */ + while((scrub_char_pointer = strpbrk(scan_settings.system_name_custom, "&*/:`\"<>?\\|"))) + *scrub_char_pointer = '_'; +} + +/* Removes period (full stop) characters from + * 'file_exts_custom' string and converts to + * lower case */ +void manual_content_scan_scrub_file_exts_custom(void) +{ + manual_content_scan_scrub_file_exts(scan_settings.file_exts_custom); +} + +/* Menu setters */ + +/* Sets content directory for next manual scan + * operation. + * Returns true if content directory is valid. */ +bool manual_content_scan_set_menu_content_dir(const char *content_dir) +{ + const char *dir_name = NULL; + size_t len; + + /* Sanity check */ + if (string_is_empty(content_dir)) + goto error; + + if (!path_is_directory(content_dir)) + goto error; + + /* Copy directory path to settings struct */ + strlcpy( + scan_settings.content_dir, + content_dir, + sizeof(scan_settings.content_dir)); + + /* Remove trailing slash, if required */ + len = strlen(scan_settings.content_dir); + if (len > 0) + { + if (scan_settings.content_dir[len - 1] == path_default_slash_c()) + scan_settings.content_dir[len - 1] = '\0'; + } + else + goto error; + + /* Handle case where path was a single slash... */ + if (string_is_empty(scan_settings.content_dir)) + goto error; + + /* Get directory name (used as system name + * when scan_settings.system_name_type == + * MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) */ + dir_name = path_basename(scan_settings.content_dir); + + if (string_is_empty(dir_name)) + goto error; + + /* Copy directory name to settings struct */ + strlcpy( + scan_settings.system_name_content_dir, + dir_name, + sizeof(scan_settings.system_name_content_dir)); + + return true; + +error: + /* Directory is invalid - reset internal + * content directory and associated 'directory' + * system name */ + scan_settings.content_dir[0] = '\0'; + scan_settings.system_name_content_dir[0] = '\0'; + return false; +} + +/* Sets system name for the next manual scan + * operation. + * Returns true if system name is valid. + * NOTE: + * > Only sets 'system_name_type' and 'system_name_database' + * > 'system_name_content_dir' and 'system_name_custom' are + * (by necessity) handled elsewhere + * > This may look fishy, but it's not - it's a menu-specific + * function, and this is simply the cleanest way to handle + * the setting... */ +bool manual_content_scan_set_menu_system_name( + enum manual_content_scan_system_name_type system_name_type, + const char *system_name) +{ + /* Sanity check */ + if (system_name_type > MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE) + goto error; + + /* Cache system name 'type' */ + scan_settings.system_name_type = system_name_type; + + /* Check if we are using a non-database name */ + if ((scan_settings.system_name_type == MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) || + (scan_settings.system_name_type == MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM)) + scan_settings.system_name_database[0] = '\0'; + else + { + /* We are using a database name... */ + if (string_is_empty(system_name)) + goto error; + + /* Copy database name to settings struct */ + strlcpy( + scan_settings.system_name_database, + system_name, + sizeof(scan_settings.system_name_database)); + } + + return true; + +error: + /* Input parameters are invalid - reset internal + * 'system_name_type' and 'system_name_database' */ + scan_settings.system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR; + scan_settings.system_name_database[0] = '\0'; + return false; +} + +/* Sets core name for the next manual scan + * operation (+ core path and other associated + * parameters). + * Returns true if core name is valid. */ +bool manual_content_scan_set_menu_core_name( + enum manual_content_scan_core_type core_type, + const char *core_name) +{ + /* Sanity check */ + if (core_type > MANUAL_CONTENT_SCAN_CORE_SET) + goto error; + + /* Cache core 'type' */ + scan_settings.core_type = core_type; + + /* Check if we are using core autodetection */ + if (scan_settings.core_type == MANUAL_CONTENT_SCAN_CORE_DETECT) + { + scan_settings.core_name[0] = '\0'; + scan_settings.core_path[0] = '\0'; + scan_settings.file_exts_core[0] = '\0'; + } + else + { + core_info_list_t *core_info_list = NULL; + core_info_t *core_info = NULL; + bool core_found = false; + size_t i; + + /* We are using a manually set core... */ + if (string_is_empty(core_name)) + goto error; + + /* Get core list */ + core_info_get_list(&core_info_list); + + if (!core_info_list) + goto error; + + /* Search for the specified core name */ + for (i = 0; i < core_info_list->count; i++) + { + core_info = NULL; + core_info = core_info_get(core_info_list, i); + + if (core_info) + { + if (string_is_equal(core_info->display_name, core_name)) + { + /* Core has been found */ + core_found = true; + + /* Copy core path to settings struct */ + if (string_is_empty(core_info->path)) + goto error; + + strlcpy( + scan_settings.core_path, + core_info->path, + sizeof(scan_settings.core_path)); + + /* Copy core name to settings struct */ + strlcpy( + scan_settings.core_name, + core_info->display_name, + sizeof(scan_settings.core_name)); + + /* Copy supported extensions to settings + * struct, if required */ + if (!string_is_empty(core_info->supported_extensions)) + { + strlcpy( + scan_settings.file_exts_core, + core_info->supported_extensions, + sizeof(scan_settings.file_exts_core)); + + /* Core info extensions are delimited by + * vertical bars. For internal consistency, + * replace them with spaces */ + string_replace_all_chars(scan_settings.file_exts_core, '|', ' '); + + /* Apply standard scrubbing/clean-up + * (should not be required, but must handle the + * case where a core info file is incorrectly + * formatted) */ + manual_content_scan_scrub_file_exts(scan_settings.file_exts_core); + } + else + scan_settings.file_exts_core[0] = '\0'; + + break; + } + } + } + + /* Sanity check */ + if (!core_found) + goto error; + } + + return true; + +error: + /* Input parameters are invalid - reset internal + * core values */ + scan_settings.core_type = MANUAL_CONTENT_SCAN_CORE_DETECT; + scan_settings.core_name[0] = '\0'; + scan_settings.core_path[0] = '\0'; + scan_settings.file_exts_core[0] = '\0'; + return false; +} + +/* Menu getters */ + +/* Fetches content directory for next manual scan + * operation. + * Returns true if content directory is valid. */ +bool manual_content_scan_get_menu_content_dir(const char **content_dir) +{ + if (!content_dir) + return false; + + if (string_is_empty(scan_settings.content_dir)) + return false; + + *content_dir = scan_settings.content_dir; + + return true; +} + +/* Fetches system name for the next manual scan operation. + * Returns true if system name is valid. + * NOTE: This corresponds to the 'System Name' value + * displayed in menus - this is not identical to the + * actual system name used when generating the playlist */ +bool manual_content_scan_get_menu_system_name(const char **system_name) +{ + if (!system_name) + return false; + + switch (scan_settings.system_name_type) + { + case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR: + *system_name = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR); + return true; + case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM: + *system_name = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CUSTOM); + return true; + case MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE: + if (string_is_empty(scan_settings.system_name_database)) + return false; + else + { + *system_name = scan_settings.system_name_database; + return true; + } + default: + break; + } + + return false; +} + +/* Fetches core name for the next manual scan operation. + * Returns true if core name is valid. */ +bool manual_content_scan_get_menu_core_name(const char **core_name) +{ + if (!core_name) + return false; + + switch (scan_settings.core_type) + { + case MANUAL_CONTENT_SCAN_CORE_DETECT: + *core_name = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT); + return true; + case MANUAL_CONTENT_SCAN_CORE_SET: + if (string_is_empty(scan_settings.core_name)) + return false; + else + { + *core_name = scan_settings.core_name; + return true; + } + default: + break; + } + + return false; +} + +/* Menu utility functions */ + +/* Creates a list of all possible 'system name' menu + * strings, for use in 'menu_displaylist' drop-down + * lists and 'menu_cbs_left/right' + * > Returns NULL in the event of failure + * > Returned string list must be free()'d */ +struct string_list *manual_content_scan_get_menu_system_name_list(void) +{ + settings_t *settings = config_get_ptr(); + struct string_list *name_list = string_list_new(); + union string_list_elem_attr attr; + + /* Sanity check */ + if (!name_list) + goto error; + + attr.i = 0; + + /* Add 'use content directory' entry */ + if (!string_list_append(name_list, msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR), attr)) + goto error; + + /* Add 'use custom' entry */ + if (!string_list_append(name_list, msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CUSTOM), attr)) + goto error; + +#ifdef HAVE_LIBRETRODB + + /* If platform has database support, get names + * of all installed database files */ + if (settings) + { + /* Note: dir_list_new_special() is well behaved - the + * returned string list will only include database + * files (i.e. don't have to check for directories, + * or verify file extensions) */ + struct string_list *rdb_list = dir_list_new_special( + settings->paths.path_content_database, + DIR_LIST_DATABASES, NULL); + + if (rdb_list && rdb_list->size) + { + unsigned i; + + /* Ensure database list is in alphabetical order */ + dir_list_sort(rdb_list, true); + + /* Loop over database files */ + for (i = 0; i < rdb_list->size; i++) + { + const char *rdb_path = rdb_list->elems[i].data; + const char *rdb_file = NULL; + char rdb_name[PATH_MAX_LENGTH]; + + rdb_name[0] = '\0'; + + /* Sanity check */ + if (string_is_empty(rdb_path)) + continue; + + rdb_file = path_basename(rdb_path); + + if (string_is_empty(rdb_file)) + continue; + + /* Remove file extension */ + strlcpy(rdb_name, rdb_file, sizeof(rdb_name)); + path_remove_extension(rdb_name); + + if (string_is_empty(rdb_name)) + continue; + + /* Add database name to list */ + if (!string_list_append(name_list, rdb_name, attr)) + goto error; + } + } + + /* Clean up */ + string_list_free(rdb_list); + } + +#endif + + return name_list; + +error: + if (name_list) + string_list_free(name_list); + return NULL; +} + +/* Creates a list of all possible 'core name' menu + * strings, for use in 'menu_displaylist' drop-down + * lists and 'menu_cbs_left/right' + * > Returns NULL in the event of failure + * > Returned string list must be free()'d */ +struct string_list *manual_content_scan_get_menu_core_name_list(void) +{ + struct string_list *name_list = string_list_new(); + core_info_list_t *core_info_list = NULL; + union string_list_elem_attr attr; + + /* Sanity check */ + if (!name_list) + goto error; + + attr.i = 0; + + /* Add 'DETECT' entry */ + if (!string_list_append(name_list, msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT), attr)) + goto error; + + /* Get core list */ + core_info_get_list(&core_info_list); + + if (core_info_list) + { + core_info_t *core_info = NULL; + size_t i; + + /* Sort cores alphabetically */ + core_info_qsort(core_info_list, CORE_INFO_LIST_SORT_DISPLAY_NAME); + + /* Loop through cores */ + for (i = 0; i < core_info_list->count; i++) + { + core_info = NULL; + core_info = core_info_get(core_info_list, i); + + if (core_info) + { + if (string_is_empty(core_info->display_name)) + continue; + + /* Add core name to list */ + if (!string_list_append(name_list, core_info->display_name, attr)) + goto error; + } + } + } + + return name_list; + +error: + if (name_list) + string_list_free(name_list); + return NULL; +} + +/****************/ +/* Task Helpers */ +/****************/ + +/* Parses current manual content scan settings, + * and extracts all information required to configure + * a manual content scan task. + * Returns false if current settings are invalid. */ +bool manual_content_scan_get_task_config(manual_content_scan_task_config_t *task_config) +{ + settings_t *settings = config_get_ptr(); + + if (!task_config || !settings) + return false; + + /* Ensure all 'task_config' strings are + * correctly initialised */ + task_config->playlist_file[0] = '\0'; + task_config->content_dir[0] = '\0'; + task_config->system_name[0] = '\0'; + task_config->database_name[0] = '\0'; + task_config->core_name[0] = '\0'; + task_config->core_path[0] = '\0'; + task_config->file_exts[0] = '\0'; + + /* Get content directory */ + if (string_is_empty(scan_settings.content_dir)) + return false; + + if (!path_is_directory(scan_settings.content_dir)) + return false; + + strlcpy( + task_config->content_dir, + scan_settings.content_dir, + sizeof(task_config->content_dir)); + + /* Get system name */ + switch (scan_settings.system_name_type) + { + case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR: + if (string_is_empty(scan_settings.system_name_content_dir)) + return false; + + strlcpy( + task_config->system_name, + scan_settings.system_name_content_dir, + sizeof(task_config->system_name)); + + break; + case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM: + if (string_is_empty(scan_settings.system_name_custom)) + return false; + + strlcpy( + task_config->system_name, + scan_settings.system_name_custom, + sizeof(task_config->system_name)); + + break; + case MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE: + if (string_is_empty(scan_settings.system_name_database)) + return false; + + strlcpy( + task_config->system_name, + scan_settings.system_name_database, + sizeof(task_config->system_name)); + + break; + default: + return false; + } + + /* Now we have a valid system name, can generate + * a 'database' name... */ + strlcpy( + task_config->database_name, + task_config->system_name, + sizeof(task_config->database_name)); + + strlcat( + task_config->database_name, + file_path_str(FILE_PATH_LPL_EXTENSION), + sizeof(task_config->database_name)); + + /* ...which can in turn be used to generate the + * playlist path */ + if (string_is_empty(settings->paths.directory_playlist)) + return false; + + fill_pathname_join( + task_config->playlist_file, + settings->paths.directory_playlist, + task_config->database_name, + sizeof(task_config->playlist_file)); + + if (string_is_empty(task_config->playlist_file)) + return false; + + /* Get core name and path */ + switch (scan_settings.core_type) + { + case MANUAL_CONTENT_SCAN_CORE_DETECT: + task_config->core_set = false; + break; + case MANUAL_CONTENT_SCAN_CORE_SET: + task_config->core_set = true; + + if (string_is_empty(scan_settings.core_name)) + return false; + if (string_is_empty(scan_settings.core_path)) + return false; + + strlcpy( + task_config->core_name, + scan_settings.core_name, + sizeof(task_config->core_name)); + + strlcpy( + task_config->core_path, + scan_settings.core_path, + sizeof(task_config->core_path)); + + break; + default: + return false; + } + + /* Get file extensions list + * > Note that compressed files are included by + * default, regardless of extension filter + * (since these can always be handled by the + * frontend) */ + task_config->include_compressed_content = true; + + if (!string_is_empty(scan_settings.file_exts_custom)) + { + strlcpy( + task_config->file_exts, + scan_settings.file_exts_custom, + sizeof(task_config->file_exts)); + + /* User has explicitly specified which file + * types are allowed - have to exclude compressed + * content when calling dir_list_new() */ + task_config->include_compressed_content = false; + } + else if (scan_settings.core_type == MANUAL_CONTENT_SCAN_CORE_SET) + { + if (!string_is_empty(scan_settings.file_exts_core)) + strlcpy( + task_config->file_exts, + scan_settings.file_exts_core, + sizeof(task_config->file_exts)); + } + + /* Our extension lists are space delimited + * > dir_list_new() expects vertical bar + * delimiters, so find and replace */ + if (!string_is_empty(task_config->file_exts)) + string_replace_all_chars(task_config->file_exts, ' ', '|'); + + /* Copy 'overwrite playlist' setting */ + task_config->overwrite_playlist = scan_settings.overwrite_playlist; + + return true; +} + +/* Creates a list of all valid content in the specified + * content directory + * > Returns NULL in the event of failure + * > Returned string list must be free()'d */ +struct string_list *manual_content_scan_get_content_list(manual_content_scan_task_config_t *task_config) +{ + struct string_list *dir_list = NULL; + bool filter_exts; + + /* Sanity check */ + if (!task_config) + goto error; + + if (string_is_empty(task_config->content_dir)) + goto error; + + /* Get directory listing + * > Exclude directories and hidden files + * > Scan recursively */ + dir_list = dir_list_new( + task_config->content_dir, + string_is_empty(task_config->file_exts) ? NULL : task_config->file_exts, + false, /* include_dirs */ + false, /* include_hidden */ + task_config->include_compressed_content, + true /* recursive */ + ); + + /* Sanity check */ + if (!dir_list) + goto error; + + if (dir_list->size < 1) + goto error; + + /* Ensure list is in alphabetical order + * > Not strictly required, but task status + * messages will be unintuitive if we leave + * the order 'random' */ + dir_list_sort(dir_list, true); + + return dir_list; + +error: + if (dir_list) + string_list_free(dir_list); + return NULL; +} + +/* Adds specified content to playlist, if not already + * present */ +void manual_content_scan_add_content_to_playlist( + manual_content_scan_task_config_t *task_config, + playlist_t *playlist, const char *content_path) +{ + /* Sanity check */ + if (!task_config || !playlist || string_is_empty(content_path)) + return; + + if (!path_is_valid(content_path)) + return; + + /* Check whether content is already included + * in playlist */ + if (!playlist_entry_exists(playlist, content_path)) + { + struct playlist_entry entry = {0}; + char label[PATH_MAX_LENGTH]; + + label[0] = '\0'; + + /* Get entry label */ + fill_short_pathname_representation( + label, content_path, sizeof(label)); + + if (string_is_empty(label)) + return; + + /* Configure playlist entry + * > The push function reads our entry as const, + * so these casts are safe */ + entry.path = (char*)content_path; + entry.label = label; + entry.core_path = (char*)"DETECT"; + entry.core_name = (char*)"DETECT"; + entry.crc32 = (char*)"00000000|crc"; + entry.db_name = task_config->database_name; + + /* Add entry to playlist */ + playlist_push(playlist, &entry); + } +} diff --git a/manual_content_scan.h b/manual_content_scan.h new file mode 100644 index 0000000000..648879a707 --- /dev/null +++ b/manual_content_scan.h @@ -0,0 +1,201 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (manual_content_scan.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __MANUAL_CONTENT_SCAN_H +#define __MANUAL_CONTENT_SCAN_H + +#include +#include + +#include + +#include + +#include "playlist.h" + +RETRO_BEGIN_DECLS + +/* Defines all possible system name types + * > Use content directory name + * > Use custom name + * > Use database name */ +enum manual_content_scan_system_name_type +{ + MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR = 0, + MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, + MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE +}; + +/* Defines all possible core name types + * > Autodetect core (DETECT) + * > Use manually set core */ +enum manual_content_scan_core_type +{ + MANUAL_CONTENT_SCAN_CORE_DETECT = 0, + MANUAL_CONTENT_SCAN_CORE_SET +}; + +/* Holds all configuration parameters required + * for a manual content scan task */ +typedef struct +{ + char playlist_file[PATH_MAX_LENGTH]; + char content_dir[PATH_MAX_LENGTH]; + char system_name[PATH_MAX_LENGTH]; + char database_name[PATH_MAX_LENGTH]; + char core_name[PATH_MAX_LENGTH]; + char core_path[PATH_MAX_LENGTH]; + char file_exts[PATH_MAX_LENGTH]; + bool core_set; + bool overwrite_playlist; + bool include_compressed_content; +} manual_content_scan_task_config_t; + +/*****************/ +/* Configuration */ +/*****************/ + +/* Pointer access + * > This is a little ugly, but it allows us to + * make use of standard 'menu_settings' code + * for several config parameters (rather than + * implementing unnecessary custom menu entries) */ + +/* Returns a pointer to the internal + * 'system_name_custom' string */ +char *manual_content_scan_get_system_name_custom_ptr(void); + +/* Returns size of the internal + * 'system_name_custom' string */ +size_t manual_content_scan_get_system_name_custom_size(void); + +/* Returns a pointer to the internal + * 'file_exts_custom' string */ +char *manual_content_scan_get_file_exts_custom_ptr(void); + +/* Returns size of the internal + * 'file_exts_custom' string */ +size_t manual_content_scan_get_file_exts_custom_size(void); + +/* Returns a pointer to the internal + * 'overwrite_playlist' bool */ +bool *manual_content_scan_get_overwrite_playlist_ptr(void); + +/* Sanitisation */ + +/* Removes invalid characters from + * 'system_name_custom' string */ +void manual_content_scan_scrub_system_name_custom(void); + +/* Removes period (full stop) characters from + * 'file_exts_custom' string and converts to + * lower case */ +void manual_content_scan_scrub_file_exts_custom(void); + +/* Menu setters */ + +/* Sets content directory for next manual scan + * operation. + * Returns true if content directory is valid. */ +bool manual_content_scan_set_menu_content_dir(const char *content_dir); + +/* Sets system name for the next manual scan + * operation. + * Returns true if system name is valid. + * NOTE: + * > Only sets 'system_name_type' and 'system_name_database' + * > 'system_name_content_dir' and 'system_name_custom' are + * (by necessity) handled elsewhere + * > This may look fishy, but it's not - it's a menu-specific + * function, and this is simply the cleanest way to handle + * the setting... */ +bool manual_content_scan_set_menu_system_name( + enum manual_content_scan_system_name_type system_name_type, + const char *system_name); + +/* Sets core name for the next manual scan + * operation (+ core path and other associated + * parameters). + * Returns true if core name is valid. */ +bool manual_content_scan_set_menu_core_name( + enum manual_content_scan_core_type core_type, + const char *core_name); + +/* Menu getters */ + +/* Fetches content directory for next manual scan + * operation. + * Returns true if content directory is valid. */ +bool manual_content_scan_get_menu_content_dir(const char **content_dir); + +/* Fetches system name for the next manual scan operation. + * Returns true if system name is valid. + * NOTE: This corresponds to the 'System Name' value + * displayed in menus - this is not identical to the + * actual system name used when generating the playlist */ +bool manual_content_scan_get_menu_system_name(const char **system_name); + +/* Fetches core name for the next manual scan operation. + * Returns true if core name is valid. */ +bool manual_content_scan_get_menu_core_name(const char **core_name); + +/* Menu utility functions */ + +/* Creates a list of all possible 'system name' menu + * strings, for use in 'menu_displaylist' drop-down + * lists and 'menu_cbs_left/right' + * > Returns NULL in the event of failure + * > Returned string list must be free()'d */ +struct string_list *manual_content_scan_get_menu_system_name_list(void); + +/* Creates a list of all possible 'core name' menu + * strings, for use in 'menu_displaylist' drop-down + * lists and 'menu_cbs_left/right' + * > Returns NULL in the event of failure + * > Returned string list must be free()'d */ +struct string_list *manual_content_scan_get_menu_core_name_list(void); + +/****************/ +/* Task Helpers */ +/****************/ + +/* Parses current manual content scan settings, + * and extracts all information required to configure + * a manual content scan task. + * Returns false if current settings are invalid. */ +bool manual_content_scan_get_task_config(manual_content_scan_task_config_t *task_config); + +/* Creates a list of all valid content in the specified + * content directory + * > Returns NULL in the event of failure + * > Returned string list must be free()'d */ +struct string_list *manual_content_scan_get_content_list(manual_content_scan_task_config_t *task_config); + +/* Adds specified content to playlist, if not already + * present */ +void manual_content_scan_add_content_to_playlist( + manual_content_scan_task_config_t *task_config, + playlist_t *playlist, const char *content_path); + +RETRO_END_DECLS + +#endif diff --git a/menu/cbs/menu_cbs_deferred_push.c b/menu/cbs/menu_cbs_deferred_push.c index da7fbb14d9..f9b7f977bf 100644 --- a/menu/cbs/menu_cbs_deferred_push.c +++ b/menu/cbs/menu_cbs_deferred_push.c @@ -217,6 +217,8 @@ generic_deferred_push(deferred_push_switch_gpu_profile, DISPLAYLIST_ generic_deferred_push(deferred_push_switch_backlight_control, DISPLAYLIST_SWITCH_BACKLIGHT_CONTROL) #endif +generic_deferred_push(deferred_push_manual_content_scan_list, DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST) + static int deferred_push_cursor_manager_list_deferred( menu_displaylist_info_t *info) { @@ -636,6 +638,8 @@ generic_deferred_push_clear_general(deferred_push_dropdown_box_list_playlist_def generic_deferred_push_clear_general(deferred_push_dropdown_box_list_playlist_label_display_mode, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LABEL_DISPLAY_MODE) generic_deferred_push_clear_general(deferred_push_dropdown_box_list_playlist_right_thumbnail_mode, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE) generic_deferred_push_clear_general(deferred_push_dropdown_box_list_playlist_left_thumbnail_mode, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE) +generic_deferred_push_clear_general(deferred_push_dropdown_box_list_manual_content_scan_system_name, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME) +generic_deferred_push_clear_general(deferred_push_dropdown_box_list_manual_content_scan_core_name, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_CORE_NAME) static int menu_cbs_init_bind_deferred_push_compare_label( menu_file_list_cbs_t *cbs, @@ -691,6 +695,16 @@ static int menu_cbs_init_bind_deferred_push_compare_label( BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_dropdown_box_list_playlist_left_thumbnail_mode); return 0; } + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME))) + { + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_dropdown_box_list_manual_content_scan_system_name); + return 0; + } + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME))) + { + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_dropdown_box_list_manual_content_scan_core_name); + return 0; + } else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_BROWSE_URL_LIST))) { BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_browse_url_list); @@ -1439,6 +1453,9 @@ static int menu_cbs_init_bind_deferred_push_compare_label( case MENU_ENUM_LABEL_FAVORITES: BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_detect_core_list); break; + case MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST: + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_list); + break; default: return -1; } @@ -1655,6 +1672,9 @@ static int menu_cbs_init_bind_deferred_push_compare_label( case MENU_LABEL_FAVORITES: BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_detect_core_list); break; + case MENU_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST: + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_manual_content_scan_list); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index 934b44ba37..6239bff489 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -43,6 +43,7 @@ #include "../../verbosity.h" #include "../../wifi/wifi_driver.h" #include "../../playlist.h" +#include "../../manual_content_scan.h" #ifdef HAVE_NETWORKING #include "../../network/netplay/netplay.h" @@ -1292,6 +1293,66 @@ static void menu_action_setting_disp_set_label_achievement_information( strlcpy(s2, path, len2); } +static void menu_action_setting_disp_set_label_manual_content_scan_dir(file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + const char *content_dir = NULL; + + *s = '\0'; + *w = 19; + + strlcpy(s2, path, len2); + + if (!manual_content_scan_get_menu_content_dir(&content_dir)) + return; + + strlcpy(s, content_dir, len); +} + +static void menu_action_setting_disp_set_label_manual_content_scan_system_name(file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + const char *system_name = NULL; + + *s = '\0'; + *w = 19; + + strlcpy(s2, path, len2); + + if (!manual_content_scan_get_menu_system_name(&system_name)) + return; + + strlcpy(s, system_name, len); +} + +static void menu_action_setting_disp_set_label_manual_content_scan_core_name(file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + const char *core_name = NULL; + + *s = '\0'; + *w = 19; + + strlcpy(s2, path, len2); + + if (!manual_content_scan_get_menu_core_name(&core_name)) + return; + + strlcpy(s, core_name, len); +} + static void menu_action_setting_disp_set_label_no_items( file_list_t* list, unsigned *w, unsigned type, unsigned i, @@ -1502,6 +1563,18 @@ static int menu_cbs_init_bind_get_string_representation_compare_label( BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_playlist_left_thumbnail_mode); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_manual_content_scan_dir); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_manual_content_scan_system_name); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_manual_content_scan_core_name); + break; default: return - 1; } diff --git a/menu/cbs/menu_cbs_left.c b/menu/cbs/menu_cbs_left.c index f8451ff12b..a160a4d494 100644 --- a/menu/cbs/menu_cbs_left.c +++ b/menu/cbs/menu_cbs_left.c @@ -41,6 +41,7 @@ #include "../../retroarch.h" #include "../../network/netplay/netplay.h" #include "../../playlist.h" +#include "../../manual_content_scan.h" #ifndef BIND_ACTION_LEFT #define BIND_ACTION_LEFT(cbs, name) \ @@ -515,6 +516,116 @@ static int playlist_left_thumbnail_mode_left(unsigned type, const char *label, return 0; } +static int manual_content_scan_system_name_left(unsigned type, const char *label, + bool wraparound) +{ + struct string_list *system_name_list = + manual_content_scan_get_menu_system_name_list(); + const char *current_system_name = NULL; + enum manual_content_scan_system_name_type next_system_name_type = + MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE; + const char *next_system_name = NULL; + unsigned current_index = 0; + unsigned next_index = 0; + unsigned i; + + if (!system_name_list) + return -1; + + /* Get currently selected system name */ + if (manual_content_scan_get_menu_system_name(¤t_system_name)) + { + /* Get index of currently selected system name */ + for (i = 0; i < system_name_list->size; i++) + { + const char *system_name = system_name_list->elems[i].data; + + if (string_is_equal(current_system_name, system_name)) + { + current_index = i; + break; + } + } + + /* Decrement index */ + if (current_index > 0) + next_index = current_index - 1; + else if (wraparound && (system_name_list->size > 1)) + next_index = system_name_list->size - 1; + } + + /* Get new system name parameters */ + if (next_index == (unsigned)MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) + next_system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR; + else if (next_index == (unsigned)MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM) + next_system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM; + + next_system_name = system_name_list->elems[next_index].data; + + /* Set system name */ + manual_content_scan_set_menu_system_name( + next_system_name_type, next_system_name); + + /* Clean up */ + string_list_free(system_name_list); + + return 0; +} + +static int manual_content_scan_core_name_left(unsigned type, const char *label, + bool wraparound) +{ + struct string_list *core_name_list = + manual_content_scan_get_menu_core_name_list(); + const char *current_core_name = NULL; + enum manual_content_scan_core_type next_core_type = + MANUAL_CONTENT_SCAN_CORE_SET; + const char *next_core_name = NULL; + unsigned current_index = 0; + unsigned next_index = 0; + unsigned i; + + if (!core_name_list) + return -1; + + /* Get currently selected core name */ + if (manual_content_scan_get_menu_core_name(¤t_core_name)) + { + /* Get index of currently selected core name */ + for (i = 0; i < core_name_list->size; i++) + { + const char *core_name = core_name_list->elems[i].data; + + if (string_is_equal(current_core_name, core_name)) + { + current_index = i; + break; + } + } + + /* Decrement index */ + if (current_index > 0) + next_index = current_index - 1; + else if (wraparound && (core_name_list->size > 1)) + next_index = core_name_list->size - 1; + } + + /* Get new core name parameters */ + if (next_index == (unsigned)MANUAL_CONTENT_SCAN_CORE_DETECT) + next_core_type = MANUAL_CONTENT_SCAN_CORE_DETECT; + + next_core_name = core_name_list->elems[next_index].data; + + /* Set core name */ + manual_content_scan_set_menu_core_name( + next_core_type, next_core_name); + + /* Clean up */ + string_list_free(core_name_list); + + return 0; +} + static int core_setting_left(unsigned type, const char *label, bool wraparound) { @@ -766,6 +877,12 @@ static int menu_cbs_init_bind_left_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE: BIND_ACTION_LEFT(cbs, playlist_left_thumbnail_mode_left); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_LEFT(cbs, manual_content_scan_system_name_left); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_LEFT(cbs, manual_content_scan_core_name_left); + break; default: return -1; } @@ -861,6 +978,7 @@ static int menu_cbs_init_bind_left_compare_type(menu_file_list_cbs_t *cbs, case FILE_TYPE_DOWNLOAD_THUMBNAIL_CONTENT: case FILE_TYPE_DOWNLOAD_URL: case FILE_TYPE_SCAN_DIRECTORY: + case FILE_TYPE_MANUAL_SCAN_DIRECTORY: case FILE_TYPE_FONT: case MENU_SETTING_GROUP: case MENU_SETTINGS_CORE_INFO_NONE: diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 233bf7ca56..43c0e327fa 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -75,6 +75,7 @@ #include "../../lakka.h" #include "../../wifi/wifi_driver.h" #include "../../gfx/video_display_server.h" +#include "../../manual_content_scan.h" #include @@ -182,6 +183,10 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl) return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE; case ACTION_OK_DL_DROPDOWN_BOX_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE: return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE; + case ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME; + case ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME: + return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME; case ACTION_OK_DL_MIXER_STREAM_SETTINGS_LIST: return MENU_ENUM_LABEL_DEFERRED_MIXER_STREAM_SETTINGS_LIST; case ACTION_OK_DL_ACCOUNTS_LIST: @@ -306,6 +311,8 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl) return MENU_ENUM_LABEL_DEFERRED_VIDEO_SHADER_PRESET_SAVE_LIST; case ACTION_OK_DL_SHADER_PRESET_REMOVE: return MENU_ENUM_LABEL_DEFERRED_VIDEO_SHADER_PRESET_REMOVE_LIST; + case ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST: + return MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST; default: break; } @@ -440,6 +447,24 @@ int generic_action_ok_displaylist_push(const char *path, info.enum_idx = MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE; dl_type = DISPLAYLIST_GENERIC; break; + case ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + info.type = type; + info.directory_ptr = idx; + info_path = path; + info_label = msg_hash_to_str( + MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME); + info.enum_idx = MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME; + dl_type = DISPLAYLIST_GENERIC; + break; + case ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME: + info.type = type; + info.directory_ptr = idx; + info_path = path; + info_label = msg_hash_to_str( + MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME); + info.enum_idx = MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME; + dl_type = DISPLAYLIST_GENERIC; + break; case ACTION_OK_DL_USER_BINDS_LIST: info.type = type; info.directory_ptr = idx; @@ -600,6 +625,14 @@ int generic_action_ok_displaylist_push(const char *path, info_label = label; dl_type = DISPLAYLIST_FILE_BROWSER_SCAN_DIR; break; + case ACTION_OK_DL_MANUAL_SCAN_DIR_LIST: + filebrowser_set_type(FILEBROWSER_MANUAL_SCAN_DIR); + info.type = FILE_TYPE_DIRECTORY; + info.directory_ptr = idx; + info_path = new_path; + info_label = label; + dl_type = DISPLAYLIST_FILE_BROWSER_SELECT_DIR; + break; case ACTION_OK_DL_REMAP_FILE: filebrowser_clear_type(); info.type = type; @@ -1019,6 +1052,7 @@ int generic_action_ok_displaylist_push(const char *path, case ACTION_OK_DL_SHADER_PRESET_REMOVE: case ACTION_OK_DL_SHADER_PRESET_SAVE: case ACTION_OK_DL_CDROM_INFO_LIST: + case ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST: action_ok_dl_lbl(action_ok_dl_to_enum(action_type), DISPLAYLIST_GENERIC); break; case ACTION_OK_DL_CDROM_INFO_DETAIL_LIST: @@ -2989,6 +3023,47 @@ static int action_ok_path_scan_directory(const char *path, } #endif +static int action_ok_path_manual_scan_directory(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char *flush_char = msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST); + unsigned flush_type = 0; + const char *menu_path = NULL; + char content_dir[PATH_MAX_LENGTH]; + + content_dir[0] = '\0'; + + /* 'Reset' file browser */ + filebrowser_clear_type(); + + /* Get user-selected scan directory */ + menu_entries_get_last_stack(&menu_path, + NULL, NULL, NULL, NULL); + + if (!string_is_empty(menu_path)) + strlcpy(content_dir, menu_path, sizeof(content_dir)); + +#ifdef HAVE_COCOATOUCH + { + /* For iOS, set the path using realpath because the path name + * can start with /private and this ensures the path starts with it. + * This will allow the path to be properly substituted when + * fill_pathname_expand_special() is called. */ + char real_content_dir[PATH_MAX_LENGTH] = {0}; + realpath(content_dir, real_content_dir); + strlcpy(content_dir, real_content_dir, sizeof(content_dir)); + } +#endif + + /* Update manual content scan settings */ + manual_content_scan_set_menu_content_dir(content_dir); + + /* Return to 'manual content scan' menu */ + menu_entries_flush_stack(flush_char, flush_type); + + return 0; +} + static int action_ok_core_deferred_set(const char *new_core_path, const char *content_label, unsigned type, size_t idx, size_t entry_idx) { @@ -4671,6 +4746,7 @@ default_action_ok_func(action_ok_push_load_disc_list, ACTION_OK_DL_LOAD_DISC_LIS default_action_ok_func(action_ok_open_archive, ACTION_OK_DL_OPEN_ARCHIVE) default_action_ok_func(action_ok_rgui_menu_theme_preset, ACTION_OK_DL_RGUI_MENU_THEME_PRESET) default_action_ok_func(action_ok_pl_thumbnails_updater_list, ACTION_OK_DL_PL_THUMBNAILS_UPDATER_LIST) +default_action_ok_func(action_ok_push_manual_content_scan_list, ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST) static int action_ok_open_uwp_permission_settings(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -5161,6 +5237,17 @@ int action_ok_push_filebrowser_list_file_select(const char *path, entry_idx, ACTION_OK_DL_FILE_BROWSER_SELECT_DIR); } +int action_ok_push_manual_content_scan_dir_select(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + settings_t *settings = config_get_ptr(); + + filebrowser_clear_type(); + return generic_action_ok_displaylist_push(path, + settings->paths.directory_menu_content, label, type, idx, + entry_idx, ACTION_OK_DL_MANUAL_SCAN_DIR_LIST); +} + /* TODO/FIXME */ static int action_ok_push_dropdown_setting_core_options_item_special(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -5532,6 +5619,54 @@ static int action_ok_push_dropdown_item_playlist_left_thumbnail_mode(const char return action_cancel_pop_default(NULL, NULL, 0, 0); } +static int action_ok_push_dropdown_item_manual_content_scan_system_name(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char* system_name = path; + enum manual_content_scan_system_name_type system_name_type = + MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE; + + (void)label; + (void)type; + (void)entry_idx; + + /* Get system name type (i.e. check if setting is + * 'use content directory' or 'use custom') */ + if (idx == (size_t)MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) + system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR; + else if (idx == (size_t)MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM) + system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM; + + /* Set system name */ + manual_content_scan_set_menu_system_name( + system_name_type, system_name); + + return action_cancel_pop_default(NULL, NULL, 0, 0); +} + +static int action_ok_push_dropdown_item_manual_content_scan_core_name(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char* core_name = path; + enum manual_content_scan_core_type core_type = + MANUAL_CONTENT_SCAN_CORE_SET; + + (void)label; + (void)type; + (void)entry_idx; + + /* Get core type (i.e. check if setting is + * DETECT/Unspecified) */ + if (idx == (size_t)MANUAL_CONTENT_SCAN_CORE_DETECT) + core_type = MANUAL_CONTENT_SCAN_CORE_DETECT; + + /* Set core name */ + manual_content_scan_set_menu_core_name( + core_type, core_name); + + return action_cancel_pop_default(NULL, NULL, 0, 0); +} + static int action_ok_push_default(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { @@ -5750,6 +5885,33 @@ static int action_ok_playlist_left_thumbnail_mode(const char *path, return 0; } +static int action_ok_manual_content_scan_system_name(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + generic_action_ok_displaylist_push( + NULL, + NULL, NULL, 0, idx, 0, + ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME); + return 0; +} + +static int action_ok_manual_content_scan_core_name(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + generic_action_ok_displaylist_push( + NULL, + NULL, NULL, 0, idx, 0, + ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME); + return 0; +} + +static int action_ok_manual_content_scan_start(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + task_push_manual_content_scan(); + return 0; +} + static int action_ok_netplay_enable_host(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { @@ -6698,6 +6860,21 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_ACHIEVEMENT_RESUME: BIND_ACTION_OK(cbs, action_ok_cheevos_toggle_hardcore_mode); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST: + BIND_ACTION_OK(cbs, action_ok_push_manual_content_scan_list); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR: + BIND_ACTION_OK(cbs, action_ok_push_manual_content_scan_dir_select); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_system_name); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_core_name); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_start); + break; default: return -1; } @@ -6826,6 +7003,12 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE: BIND_ACTION_OK(cbs, action_ok_playlist_left_thumbnail_mode); break; + case MENU_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_system_name); + break; + case MENU_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_OK(cbs, action_ok_manual_content_scan_core_name); + break; default: return -1; } @@ -6958,6 +7141,12 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_DROPDOWN_ITEM_PLAYLIST_LEFT_THUMBNAIL_MODE: BIND_ACTION_OK(cbs, action_ok_push_dropdown_item_playlist_left_thumbnail_mode); break; + case MENU_SETTING_DROPDOWN_ITEM_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_OK(cbs, action_ok_push_dropdown_item_manual_content_scan_system_name); + break; + case MENU_SETTING_DROPDOWN_ITEM_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_OK(cbs, action_ok_push_dropdown_item_manual_content_scan_core_name); + break; case MENU_SETTING_ACTION_CORE_DISK_OPTIONS: BIND_ACTION_OK(cbs, action_ok_push_default); break; @@ -7043,6 +7232,9 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, BIND_ACTION_OK(cbs, action_ok_path_scan_directory); break; #endif + case FILE_TYPE_MANUAL_SCAN_DIRECTORY: + BIND_ACTION_OK(cbs, action_ok_path_manual_scan_directory); + break; case FILE_TYPE_CONFIG: BIND_ACTION_OK(cbs, action_ok_config_load); break; diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c index 3354a343c7..d04f931291 100644 --- a/menu/cbs/menu_cbs_right.c +++ b/menu/cbs/menu_cbs_right.c @@ -42,6 +42,7 @@ #include "../../ui/ui_companion_driver.h" #include "../../network/netplay/netplay.h" #include "../../playlist.h" +#include "../../manual_content_scan.h" #ifndef BIND_ACTION_RIGHT #define BIND_ACTION_RIGHT(cbs, name) \ @@ -628,6 +629,134 @@ static int playlist_left_thumbnail_mode_right(unsigned type, const char *label, return 0; } +static int manual_content_scan_system_name_right(unsigned type, const char *label, + bool wraparound) +{ + struct string_list *system_name_list = + manual_content_scan_get_menu_system_name_list(); + const char *current_system_name = NULL; + enum manual_content_scan_system_name_type next_system_name_type = + MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE; + const char *next_system_name = NULL; + unsigned current_index = 0; + unsigned next_index = 0; + unsigned i; + + if (!system_name_list) + return -1; + + /* Get currently selected system name */ + if (manual_content_scan_get_menu_system_name(¤t_system_name)) + { + /* Get index of currently selected system name */ + for (i = 0; i < system_name_list->size; i++) + { + const char *system_name = system_name_list->elems[i].data; + + if (string_is_equal(current_system_name, system_name)) + { + current_index = i; + break; + } + } + + /* Increment index */ + next_index = current_index + 1; + if (next_index >= system_name_list->size) + { + if (wraparound) + next_index = 0; + else + { + if (system_name_list->size > 0) + next_index = system_name_list->size - 1; + else + next_index = 0; + } + } + } + + /* Get new system name parameters */ + if (next_index == (unsigned)MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) + next_system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR; + else if (next_index == (unsigned)MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM) + next_system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM; + + next_system_name = system_name_list->elems[next_index].data; + + /* Set system name */ + manual_content_scan_set_menu_system_name( + next_system_name_type, next_system_name); + + /* Clean up */ + string_list_free(system_name_list); + + return 0; +} + +static int manual_content_scan_core_name_right(unsigned type, const char *label, + bool wraparound) +{ + struct string_list *core_name_list = + manual_content_scan_get_menu_core_name_list(); + const char *current_core_name = NULL; + enum manual_content_scan_core_type next_core_type = + MANUAL_CONTENT_SCAN_CORE_SET; + const char *next_core_name = NULL; + unsigned current_index = 0; + unsigned next_index = 0; + unsigned i; + + if (!core_name_list) + return -1; + + /* Get currently selected core name */ + if (manual_content_scan_get_menu_core_name(¤t_core_name)) + { + /* Get index of currently selected core name */ + for (i = 0; i < core_name_list->size; i++) + { + const char *core_name = core_name_list->elems[i].data; + + if (string_is_equal(current_core_name, core_name)) + { + current_index = i; + break; + } + } + + /* Increment index */ + next_index = current_index + 1; + if (next_index >= core_name_list->size) + { + if (wraparound) + next_index = 0; + else + { + if (core_name_list->size > 0) + next_index = core_name_list->size - 1; + else + next_index = 0; + } + } + } + + /* Get new core name parameters */ + if (next_index == (unsigned)MANUAL_CONTENT_SCAN_CORE_DETECT) + next_core_type = MANUAL_CONTENT_SCAN_CORE_DETECT; + + next_core_name = core_name_list->elems[next_index].data; + + /* Set core name */ + manual_content_scan_set_menu_core_name( + next_core_type, next_core_name); + + /* Clean up */ + string_list_free(core_name_list); + + return 0; +} + int core_setting_right(unsigned type, const char *label, bool wraparound) { @@ -735,6 +864,7 @@ static int menu_cbs_init_bind_right_compare_type(menu_file_list_cbs_t *cbs, case FILE_TYPE_DOWNLOAD_THUMBNAIL_CONTENT: case FILE_TYPE_DOWNLOAD_URL: case FILE_TYPE_SCAN_DIRECTORY: + case FILE_TYPE_MANUAL_SCAN_DIRECTORY: case FILE_TYPE_FONT: case MENU_SETTING_GROUP: case MENU_SETTINGS_CORE_INFO_NONE: @@ -915,6 +1045,12 @@ static int menu_cbs_init_bind_right_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE: BIND_ACTION_RIGHT(cbs, playlist_left_thumbnail_mode_right); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_RIGHT(cbs, manual_content_scan_system_name_right); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_RIGHT(cbs, manual_content_scan_core_name_right); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_start.c b/menu/cbs/menu_cbs_start.c index 65bfb90e93..09ab6cdf07 100644 --- a/menu/cbs/menu_cbs_start.c +++ b/menu/cbs/menu_cbs_start.c @@ -38,6 +38,7 @@ #include "../../retroarch.h" #include "../../performance_counters.h" #include "../../playlist.h" +#include "../../manual_content_scan.h" #include "../../input/input_remapping.h" @@ -302,6 +303,29 @@ static int action_start_playlist_left_thumbnail_mode(unsigned type, const char * return 0; } +static int action_start_manual_content_scan_dir(unsigned type, const char *label) +{ + /* Reset content directory */ + manual_content_scan_set_menu_content_dir(""); + return 0; +} + +static int action_start_manual_content_scan_system_name(unsigned type, const char *label) +{ + /* Reset system name */ + manual_content_scan_set_menu_system_name( + MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR, ""); + return 0; +} + +static int action_start_manual_content_scan_core_name(unsigned type, const char *label) +{ + /* Reset core name */ + manual_content_scan_set_menu_core_name( + MANUAL_CONTENT_SCAN_CORE_DETECT, ""); + return 0; +} + static int action_start_video_resolution(unsigned type, const char *label) { unsigned width = 0, height = 0; @@ -405,6 +429,15 @@ static int menu_cbs_init_bind_start_compare_label(menu_file_list_cbs_t *cbs) case MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE: BIND_ACTION_START(cbs, action_start_playlist_left_thumbnail_mode); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR: + BIND_ACTION_START(cbs, action_start_manual_content_scan_dir); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_START(cbs, action_start_manual_content_scan_system_name); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_START(cbs, action_start_manual_content_scan_core_name); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index a3c6ca202a..f12feb45c3 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -714,6 +714,14 @@ default_sublabel_macro(action_bind_sublabel_thumbnails_updater_list, default_sublabel_macro(action_bind_sublabel_pl_thumbnails_updater_list, MENU_ENUM_SUBLABEL_PL_THUMBNAILS_UPDATER_LIST) default_sublabel_macro(action_bind_sublabel_help_send_debug_info, MENU_ENUM_SUBLABEL_HELP_SEND_DEBUG_INFO) default_sublabel_macro(action_bind_sublabel_rdb_entry_detail, MENU_ENUM_SUBLABEL_RDB_ENTRY_DETAIL) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_list, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_LIST) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_dir, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DIR) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_system_name, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_system_name_custom, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_core_name, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_CORE_NAME) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_file_exts, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_FILE_EXTS) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_overwrite, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_OVERWRITE) +default_sublabel_macro(action_bind_sublabel_manual_content_scan_start, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_START) static int action_bind_sublabel_systeminfo_controller_entry( file_list_t *list, @@ -3049,6 +3057,30 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_RDB_ENTRY_DETAIL: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_rdb_entry_detail); break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_list); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_dir); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_system_name); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_system_name_custom); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_core_name); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_FILE_EXTS: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_file_exts); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_overwrite); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_start); + break; default: case MSG_UNKNOWN: return -1; diff --git a/menu/cbs/menu_cbs_title.c b/menu/cbs/menu_cbs_title.c index a3ba581b9f..b8af91998d 100644 --- a/menu/cbs/menu_cbs_title.c +++ b/menu/cbs/menu_cbs_title.c @@ -65,6 +65,8 @@ static int action_get_title_action_generic(const char *path, const char *label, const char *title = msg_hash_to_str(lbl); \ if (!string_is_empty(path) && !string_is_empty(title)) \ fill_pathname_join_delim(s, title, path, ' ', len); \ + else if (!string_is_empty(title)) \ + strlcpy(s, title, len); \ return 1; \ } @@ -396,10 +398,12 @@ default_title_macro(action_get_title_goto_music, MENU_ENUM_LABEL_ default_title_macro(action_get_title_goto_video, MENU_ENUM_LABEL_VALUE_GOTO_VIDEO) default_title_macro(action_get_title_collection, MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB) default_title_macro(action_get_title_deferred_core_list, MENU_ENUM_LABEL_VALUE_SUPPORTED_CORES) - default_title_macro(action_get_title_dropdown_resolution_item, MENU_ENUM_LABEL_VALUE_SCREEN_RESOLUTION) default_title_macro(action_get_title_dropdown_playlist_default_core_item, MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_DEFAULT_CORE) default_title_macro(action_get_title_dropdown_playlist_label_display_mode_item, MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE) +default_title_macro(action_get_title_manual_content_scan_list, MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST) +default_title_macro(action_get_title_dropdown_manual_content_scan_system_name_item, MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME) +default_title_macro(action_get_title_dropdown_manual_content_scan_core_name_item, MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME) default_fill_title_macro(action_get_title_disk_image_append, MENU_ENUM_LABEL_VALUE_DISK_IMAGE_APPEND) default_fill_title_macro(action_get_title_cheat_file_load, MENU_ENUM_LABEL_VALUE_CHEAT_FILE) @@ -444,6 +448,7 @@ default_fill_title_macro(action_get_title_extraction_directory, MENU_ENUM_LABE default_fill_title_macro(action_get_title_menu, MENU_ENUM_LABEL_VALUE_MENU_SETTINGS) default_fill_title_macro(action_get_title_font_path, MENU_ENUM_LABEL_VALUE_XMB_FONT) default_fill_title_macro(action_get_title_log_dir, MENU_ENUM_LABEL_VALUE_LOG_DIR) +default_fill_title_macro(action_get_title_manual_content_scan_dir, MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_DIR) default_title_copy_macro(action_get_title_help, MENU_ENUM_LABEL_VALUE_HELP_LIST) default_title_copy_macro(action_get_title_input_settings, MENU_ENUM_LABEL_VALUE_INPUT_SETTINGS) @@ -1226,6 +1231,12 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, BIND_ACTION_GET_TITLE(cbs, action_get_title_switch_backlight_control); break; #endif + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST: + BIND_ACTION_GET_TITLE(cbs, action_get_title_manual_content_scan_list); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR: + BIND_ACTION_GET_TITLE(cbs, action_get_title_manual_content_scan_dir); + break; default: return -1; } @@ -1363,6 +1374,9 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, case MENU_LABEL_CORE_ASSETS_DIRECTORY: BIND_ACTION_GET_TITLE(cbs, action_get_title_core_assets_directory); break; + case MENU_LABEL_THUMBNAILS_DIRECTORY: + BIND_ACTION_GET_TITLE(cbs, action_get_title_thumbnail_directory); + break; case MENU_LABEL_RGUI_CONFIG_DIRECTORY: BIND_ACTION_GET_TITLE(cbs, action_get_title_config_directory); break; @@ -1543,6 +1557,12 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, BIND_ACTION_GET_TITLE(cbs, action_get_title_switch_backlight_control); break; #endif + case MENU_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST: + BIND_ACTION_GET_TITLE(cbs, action_get_title_manual_content_scan_list); + break; + case MENU_LABEL_MANUAL_CONTENT_SCAN_DIR: + BIND_ACTION_GET_TITLE(cbs, action_get_title_manual_content_scan_dir); + break; default: return -1; } @@ -1651,6 +1671,18 @@ int menu_cbs_init_bind_title(menu_file_list_cbs_t *cbs, BIND_ACTION_GET_TITLE(cbs, action_get_title_dropdown_playlist_left_thumbnail_mode_item); return 0; } + if (string_is_equal(label, + msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME))) + { + BIND_ACTION_GET_TITLE(cbs, action_get_title_dropdown_manual_content_scan_system_name_item); + return 0; + } + if (string_is_equal(label, + msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME))) + { + BIND_ACTION_GET_TITLE(cbs, action_get_title_dropdown_manual_content_scan_core_name_item); + return 0; + } if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS))) { BIND_ACTION_GET_TITLE(cbs, action_get_quick_menu_views_settings_list); @@ -1676,6 +1708,11 @@ int menu_cbs_init_bind_title(menu_file_list_cbs_t *cbs, BIND_ACTION_GET_TITLE(cbs, action_get_title_collection); return 0; } + if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST))) + { + BIND_ACTION_GET_TITLE(cbs, action_get_title_manual_content_scan_list); + return 0; + } return -1; } diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 6313f1610f..852aba24cc 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -2552,7 +2552,6 @@ enum materialui_entry_value_type materialui_get_entry_value_type( switch (entry_file_type) { case FILE_TYPE_IN_CARCHIVE: - case FILE_TYPE_COMPRESSED: case FILE_TYPE_MORE: case FILE_TYPE_CORE: case FILE_TYPE_DIRECT_LOAD: @@ -2564,6 +2563,15 @@ enum materialui_entry_value_type materialui_get_entry_value_type( case FILE_TYPE_IMAGE: case FILE_TYPE_MOVIE: break; + case FILE_TYPE_COMPRESSED: + /* Note that we have to perform a backup check here, + * since the 'manual content scan - file extensions' + * setting may have a value of 'zip' or '7z' etc, which + * means it would otherwise get incorreclty identified as + * an achive file... */ + if (entry_type != FILE_TYPE_CARCHIVE) + value_type = MUI_ENTRY_VALUE_TEXT; + break; default: value_type = MUI_ENTRY_VALUE_TEXT; break; @@ -2693,7 +2701,13 @@ static void materialui_render_menu_entry_default( switch (entry_file_type) { case FILE_TYPE_COMPRESSED: - icon_texture = mui->textures.list[MUI_TEXTURE_ARCHIVE]; + /* Note that we have to perform a backup check here, + * since the 'manual content scan - file extensions' + * setting may have a value of 'zip' or '7z' etc, which + * means it would otherwise get incorreclty identified as + * an achive file... */ + if (entry_type == FILE_TYPE_CARCHIVE) + icon_texture = mui->textures.list[MUI_TEXTURE_ARCHIVE]; break; case FILE_TYPE_IMAGE: icon_texture = mui->textures.list[MUI_TEXTURE_IMAGE]; @@ -7613,7 +7627,8 @@ static void materialui_list_insert( node->has_icon = true; } else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SCAN_DIRECTORY)) || - string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SCAN_FILE)) + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SCAN_FILE)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST)) ) { node->icon_texture_index = MUI_TEXTURE_ADD; diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c index 8f4dc74acd..6eac3a3cb8 100644 --- a/menu/drivers/ozone/ozone_texture.c +++ b/menu/drivers/ozone/ozone_texture.c @@ -250,6 +250,7 @@ menu_texture_item ozone_entries_icon_get_texture(ozone_handle_t *ozone, return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_USER]; case MENU_ENUM_LABEL_DIRECTORY_SETTINGS: case MENU_ENUM_LABEL_SCAN_DIRECTORY: + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CONTENT_DIR: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_PARENT: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index c0c8465e15..772abbe3bc 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2379,6 +2379,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, return xmb->textures.list[XMB_TEXTURE_RESUME]; case MENU_ENUM_LABEL_DIRECTORY_SETTINGS: case MENU_ENUM_LABEL_SCAN_DIRECTORY: + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CONTENT_DIR: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_PARENT: diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index 31ab3ef2f7..87edd85558 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -52,11 +52,14 @@ enum ACTION_OK_DL_DROPDOWN_BOX_LIST_PLAYLIST_LABEL_DISPLAY_MODE, ACTION_OK_DL_DROPDOWN_BOX_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE, ACTION_OK_DL_DROPDOWN_BOX_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE, + ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + ACTION_OK_DL_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME, ACTION_OK_DL_OPEN_ARCHIVE, ACTION_OK_DL_OPEN_ARCHIVE_DETECT_CORE, ACTION_OK_DL_MUSIC, ACTION_OK_DL_NETPLAY, ACTION_OK_DL_SCAN_DIR_LIST, + ACTION_OK_DL_MANUAL_SCAN_DIR_LIST, ACTION_OK_DL_HELP, ACTION_OK_DL_RPL_ENTRY, ACTION_OK_DL_RDB_ENTRY, @@ -166,7 +169,8 @@ enum ACTION_OK_DL_BROWSE_URL_START, ACTION_OK_DL_CONTENT_SETTINGS, ACTION_OK_DL_CDROM_INFO_DETAIL_LIST, - ACTION_OK_DL_RGUI_MENU_THEME_PRESET + ACTION_OK_DL_RGUI_MENU_THEME_PRESET, + ACTION_OK_DL_MANUAL_CONTENT_SCAN_LIST }; /* Function callbacks */ diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 20aaecb00e..3073f6dafa 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -98,6 +98,7 @@ #include "../tasks/tasks_internal.h" #include "../dynamic.h" #include "../runtime_file.h" +#include "../manual_content_scan.h" static char new_path_entry[4096] = {0}; static char new_lbl_entry[4096] = {0}; @@ -2334,9 +2335,9 @@ static unsigned menu_displaylist_parse_playlists( if (!horizontal) { -#ifdef HAVE_LIBRETRODB if (settings->bools.menu_content_show_add) { +#ifdef HAVE_LIBRETRODB if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_DIRECTORY), msg_hash_to_str(MENU_ENUM_LABEL_SCAN_DIRECTORY), @@ -2349,8 +2350,15 @@ static unsigned menu_displaylist_parse_playlists( MENU_ENUM_LABEL_SCAN_FILE, MENU_SETTING_ACTION, 0, 0)) count++; - } #endif + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, + MENU_SETTING_ACTION, 0, 0)) + count++; + } + if (settings->bools.menu_content_show_favorites) if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES), @@ -3667,6 +3675,64 @@ static unsigned populate_playlist_thumbnail_mode_dropdown_list( return count; } +static bool menu_displaylist_parse_manual_content_scan_list( + menu_displaylist_info_t *info) +{ + unsigned count = 0; + + /* Content directory */ + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_DIR), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_DIR, + MENU_SETTING_MANUAL_CONTENT_SCAN_DIR, 0, 0)) + count++; + + /* System name */ + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + MENU_SETTING_MANUAL_CONTENT_SCAN_SYSTEM_NAME, 0, 0)) + count++; + + /* Custom system name */ + if (menu_displaylist_parse_settings_enum(info->list, + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, PARSE_ONLY_STRING, + false) == 0) + count++; + + /* Core name */ + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME, + MENU_SETTING_MANUAL_CONTENT_SCAN_CORE_NAME, 0, 0)) + count++; + + /* File extensions */ + if (menu_displaylist_parse_settings_enum(info->list, + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_FILE_EXTS, PARSE_ONLY_STRING, + false) == 0) + count++; + + /* Overwrite playlist */ + if (menu_displaylist_parse_settings_enum(info->list, + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE, PARSE_ONLY_BOOL, + false) == 0) + count++; + + /* Start scan */ + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_START), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START, + MENU_SETTING_ACTION_MANUAL_CONTENT_SCAN_START, 0, 0)) + count++; + + return (count > 0); +} + unsigned menu_displaylist_build_list(file_list_t *list, enum menu_displaylist_ctl_state type) { unsigned i; @@ -3863,6 +3929,12 @@ unsigned menu_displaylist_build_list(file_list_t *list, enum menu_displaylist_ct MENU_SETTING_ACTION, 0, 0)) count++; #endif + if (menu_entries_append_enum(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, + MENU_SETTING_ACTION, 0, 0)) + count++; break; case DISPLAYLIST_NETWORK_INFO: #if defined(HAVE_NETWORKING) && !defined(HAVE_SOCKET_LEGACY) && (!defined(SWITCH) || defined(SWITCH) && defined(HAVE_LIBNX)) @@ -4125,6 +4197,84 @@ unsigned menu_displaylist_build_list(file_list_t *list, enum menu_displaylist_ct case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE: count = populate_playlist_thumbnail_mode_dropdown_list(list, PLAYLIST_THUMBNAIL_LEFT); break; + case DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + { + /* Get system name list */ + struct string_list *system_name_list = manual_content_scan_get_menu_system_name_list(); + + if (system_name_list) + { + const char *current_system_name = NULL; + unsigned i; + + /* Get currently selected system name */ + manual_content_scan_get_menu_system_name(¤t_system_name); + + /* Loop through names */ + for (i = 0; i < system_name_list->size; i++) + { + /* Note: manual_content_scan_get_system_name_list() + * ensures that system_name cannot be empty here */ + const char *system_name = system_name_list->elems[i].data; + + /* Add menu entry */ + if (menu_entries_append_enum(list, + system_name, + "", + MENU_ENUM_LABEL_NO_ITEMS, + MENU_SETTING_DROPDOWN_ITEM_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + i, 0)) + count++; + + /* Check whether current entry is checked */ + if (string_is_equal(current_system_name, system_name)) + menu_entries_set_checked(list, i, true); + } + + /* Clean up */ + string_list_free(system_name_list); + } + } + break; + case DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_CORE_NAME: + { + /* Get core name list */ + struct string_list *core_name_list = manual_content_scan_get_menu_core_name_list(); + + if (core_name_list) + { + const char *current_core_name = NULL; + unsigned i; + + /* Get currently selected core name */ + manual_content_scan_get_menu_core_name(¤t_core_name); + + /* Loop through names */ + for (i = 0; i < core_name_list->size; i++) + { + /* Note: manual_content_scan_get_core_name_list() + * ensures that core_name cannot be empty here */ + const char *core_name = core_name_list->elems[i].data; + + /* Add menu entry */ + if (menu_entries_append_enum(list, + core_name, + "", + MENU_ENUM_LABEL_NO_ITEMS, + MENU_SETTING_DROPDOWN_ITEM_MANUAL_CONTENT_SCAN_CORE_NAME, + i, 0)) + count++; + + /* Check whether current entry is checked */ + if (string_is_equal(current_core_name, core_name)) + menu_entries_set_checked(list, i, true); + } + + /* Clean up */ + string_list_free(core_name_list); + } + } + break; case DISPLAYLIST_PERFCOUNTERS_CORE: case DISPLAYLIST_PERFCOUNTERS_FRONTEND: { @@ -7213,6 +7363,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LABEL_DISPLAY_MODE: case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE: case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE: + case DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + case DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_CORE_NAME: case DISPLAYLIST_PERFCOUNTERS_CORE: case DISPLAYLIST_PERFCOUNTERS_FRONTEND: case DISPLAYLIST_MENU_SETTINGS_LIST: @@ -7238,6 +7390,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LABEL_DISPLAY_MODE: case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE: case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE: + case DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME: + case DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_CORE_NAME: menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ENTRIES_TO_DISPLAY), msg_hash_to_str(MENU_ENUM_LABEL_NO_ENTRIES_TO_DISPLAY), @@ -7701,6 +7855,12 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, MENU_SETTING_ACTION, 0, 0)) count++; #endif + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, + MENU_SETTING_ACTION, 0, 0)) + count++; if (count == 0) menu_entries_append_enum(info->list, @@ -8967,6 +9127,18 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); use_filebrowser = true; break; + case DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST: + menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); + + if (!menu_displaylist_parse_manual_content_scan_list(info)) + menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ENTRIES_TO_DISPLAY), + msg_hash_to_str(MENU_ENUM_LABEL_NO_ENTRIES_TO_DISPLAY), + MENU_ENUM_LABEL_NO_ENTRIES_TO_DISPLAY, + FILE_TYPE_NONE, 0, 0); + + info->need_push = true; + break; case DISPLAYLIST_DROPDOWN_LIST: { menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h index b86f9d87cf..aced47b137 100644 --- a/menu/menu_displaylist.h +++ b/menu/menu_displaylist.h @@ -60,6 +60,8 @@ enum menu_displaylist_ctl_state DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LABEL_DISPLAY_MODE, DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE, DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE, + DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + DISPLAYLIST_DROPDOWN_LIST_MANUAL_CONTENT_SCAN_CORE_NAME, DISPLAYLIST_CDROM_DETAIL_INFO, DISPLAYLIST_INFO, DISPLAYLIST_HELP, @@ -212,6 +214,7 @@ enum menu_displaylist_ctl_state #if defined(HAVE_LAKKA_SWITCH) || defined(HAVE_LIBNX) DISPLAYLIST_SWITCH_CPU_PROFILE, #endif + DISPLAYLIST_MANUAL_CONTENT_SCAN_LIST, DISPLAYLIST_PENDING_CLEAR }; diff --git a/menu/menu_driver.h b/menu/menu_driver.h index dfb1525160..57dbafc211 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -91,6 +91,8 @@ enum menu_settings_type MENU_SETTING_DROPDOWN_ITEM_PLAYLIST_LABEL_DISPLAY_MODE, MENU_SETTING_DROPDOWN_ITEM_PLAYLIST_RIGHT_THUMBNAIL_MODE, MENU_SETTING_DROPDOWN_ITEM_PLAYLIST_LEFT_THUMBNAIL_MODE, + MENU_SETTING_DROPDOWN_ITEM_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + MENU_SETTING_DROPDOWN_ITEM_MANUAL_CONTENT_SCAN_CORE_NAME, MENU_SETTING_DROPDOWN_SETTING_CORE_OPTIONS_ITEM, MENU_SETTING_DROPDOWN_SETTING_STRING_OPTIONS_ITEM, MENU_SETTING_DROPDOWN_SETTING_FLOAT_ITEM, @@ -206,6 +208,11 @@ enum menu_settings_type MENU_SET_CDROM_INFO, MENU_SETTING_ACTION_DELETE_PLAYLIST, + MENU_SETTING_MANUAL_CONTENT_SCAN_DIR, + MENU_SETTING_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + MENU_SETTING_MANUAL_CONTENT_SCAN_CORE_NAME, + MENU_SETTING_ACTION_MANUAL_CONTENT_SCAN_START, + MENU_SETTINGS_LAST }; diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 1501f197dd..9ac8d3e87c 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -95,6 +95,7 @@ #include "../managers/cheat_manager.h" #include "../verbosity.h" #include "../playlist.h" +#include "../manual_content_scan.h" #include "../tasks/tasks_internal.h" @@ -157,7 +158,8 @@ enum settings_list_type SETTINGS_LIST_USER_ACCOUNTS_TWITCH, SETTINGS_LIST_DIRECTORY, SETTINGS_LIST_PRIVACY, - SETTINGS_LIST_MIDI + SETTINGS_LIST_MIDI, + SETTINGS_LIST_MANUAL_CONTENT_SCAN }; struct bool_entry @@ -6679,6 +6681,17 @@ void general_write_handler(rarch_setting_t *setting) } } break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM: + /* Ensure that custom system name includes no + * invalid characters */ + manual_content_scan_scrub_system_name_custom(); + break; + case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_FILE_EXTS: + /* Ensure that custom file extension list includes + * no period (full stop) characters, and converts + * string to lower case */ + manual_content_scan_scrub_file_exts_custom(); + break; default: break; } @@ -16430,6 +16443,63 @@ static bool setting_append_list( (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; menu_settings_list_current_add_range(list, list_info, 0.0f, 100.0f, 1.0f, true, true); + END_SUB_GROUP(list, list_info, parent_group); + END_GROUP(list, list_info, parent_group); + break; + case SETTINGS_LIST_MANUAL_CONTENT_SCAN: + START_GROUP(list, list_info, &group_info, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST), parent_group); + + parent_group = msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST); + + START_SUB_GROUP(list, list_info, "State", + &group_info, &subgroup_info, parent_group); + + CONFIG_STRING( + list, list_info, + manual_content_scan_get_system_name_custom_ptr(), + manual_content_scan_get_system_name_custom_size(), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM, + "", + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_STRING_LINE_EDIT; + + CONFIG_STRING( + list, list_info, + manual_content_scan_get_file_exts_custom_ptr(), + manual_content_scan_get_file_exts_custom_size(), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_FILE_EXTS, + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_FILE_EXTS, + "", + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_STRING_LINE_EDIT; + + CONFIG_BOOL( + list, list_info, + manual_content_scan_get_overwrite_playlist_ptr(), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE, + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_OVERWRITE, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + END_SUB_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group); break; @@ -16566,7 +16636,8 @@ static rarch_setting_t *menu_setting_new_internal(rarch_setting_info_t *list_inf SETTINGS_LIST_USER_ACCOUNTS_TWITCH, SETTINGS_LIST_DIRECTORY, SETTINGS_LIST_PRIVACY, - SETTINGS_LIST_MIDI + SETTINGS_LIST_MIDI, + SETTINGS_LIST_MANUAL_CONTENT_SCAN }; const char *root = msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU); rarch_setting_t *list = (rarch_setting_t*)calloc( diff --git a/menu/widgets/menu_filebrowser.c b/menu/widgets/menu_filebrowser.c index c0620af240..1f165e4bd9 100644 --- a/menu/widgets/menu_filebrowser.c +++ b/menu/widgets/menu_filebrowser.c @@ -127,6 +127,14 @@ void filebrowser_parse(menu_displaylist_info_t *info, unsigned type_data) FILE_TYPE_SCAN_DIRECTORY, 0 ,0); #endif break; + case FILEBROWSER_MANUAL_SCAN_DIR: + if (info) + menu_entries_prepend(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_THIS_DIRECTORY), + msg_hash_to_str(MENU_ENUM_LABEL_SCAN_THIS_DIRECTORY), + MENU_ENUM_LABEL_SCAN_THIS_DIRECTORY, + FILE_TYPE_MANUAL_SCAN_DIRECTORY, 0 ,0); + break; case FILEBROWSER_SELECT_DIR: if (info) menu_entries_prepend(info->list, @@ -212,6 +220,8 @@ void filebrowser_parse(menu_displaylist_info_t *info, unsigned type_data) continue; if (filebrowser_types == FILEBROWSER_SCAN_DIR) continue; + if (filebrowser_types == FILEBROWSER_MANUAL_SCAN_DIR) + continue; } /* Need to preserve slash first time. */ diff --git a/menu/widgets/menu_filebrowser.h b/menu/widgets/menu_filebrowser.h index 86a5702fdf..43875a4164 100644 --- a/menu/widgets/menu_filebrowser.h +++ b/menu/widgets/menu_filebrowser.h @@ -32,6 +32,7 @@ enum filebrowser_enums FILEBROWSER_SELECT_DIR, FILEBROWSER_SCAN_DIR, FILEBROWSER_SCAN_FILE, + FILEBROWSER_MANUAL_SCAN_DIR, FILEBROWSER_SELECT_FILE, FILEBROWSER_SELECT_FILE_SUBSYSTEM, FILEBROWSER_SELECT_IMAGE, diff --git a/msg_hash.h b/msg_hash.h index 2d30c4c90d..ae3533f66f 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -151,6 +151,8 @@ enum msg_file_type * menu_cbs_init_bind_get_string_representation_compare_type() breaks... */ FILE_TYPE_DOWNLOAD_PL_THUMBNAIL_CONTENT, + FILE_TYPE_MANUAL_SCAN_DIRECTORY, + FILE_TYPE_LAST }; @@ -1245,6 +1247,8 @@ enum msg_hash_enums MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_LABEL_DISPLAY_MODE, MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_RIGHT_THUMBNAIL_MODE, MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE, + MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_SYSTEM_NAME, + MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_MANUAL_CONTENT_SCAN_CORE_NAME, MENU_ENUM_LABEL_DEFERRED_MIXER_STREAM_SETTINGS_LIST, MENU_ENUM_LABEL_DEFERRED_CONFIGURATIONS_LIST, MENU_ENUM_LABEL_DEFERRED_FAVORITES_LIST, @@ -1343,6 +1347,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_DEFERRED_ACCOUNTS_YOUTUBE_LIST, MENU_ENUM_LABEL_DEFERRED_ACCOUNTS_LIST, MENU_ENUM_LABEL_DEFERRED_INFORMATION, + MENU_ENUM_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST, MENU_LABEL(FILE_DETECT_CORE_LIST_PUSH_DIR), MENU_LABEL(DOWNLOADED_FILE_DETECT_CORE_LIST), @@ -2658,6 +2663,27 @@ enum msg_hash_enums MSG_NO_DISC_INSERTED, MENU_LABEL(DELETE_PLAYLIST), + /* Manual content scan */ + MENU_LABEL(MANUAL_CONTENT_SCAN_LIST), + MENU_LABEL(MANUAL_CONTENT_SCAN_DIR), + MENU_LABEL(MANUAL_CONTENT_SCAN_SYSTEM_NAME), + MENU_LABEL(MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM), + MENU_LABEL(MANUAL_CONTENT_SCAN_CORE_NAME), + MENU_LABEL(MANUAL_CONTENT_SCAN_FILE_EXTS), + MENU_LABEL(MANUAL_CONTENT_SCAN_OVERWRITE), + MENU_LABEL(MANUAL_CONTENT_SCAN_START), + + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR, + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CUSTOM, + + MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT, + + MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG, + MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT, + MSG_MANUAL_CONTENT_SCAN_START, + MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS, + MSG_MANUAL_CONTENT_SCAN_END, + MSG_LAST }; @@ -2715,6 +2741,7 @@ enum msg_hash_enums #define MENU_LABEL_DEFERRED_CONFIGURATIONS_LIST 0x679a1b0bU #define MENU_LABEL_DEFERRED_BROWSE_URL_START 0xcef58296U #define MENU_LABEL_DEFERRED_INFORMATION 0x3FCC9F2BU +#define MENU_LABEL_DEFERRED_MANUAL_CONTENT_SCAN_LIST 0x479546DCU /* Cheevos settings */ @@ -2910,6 +2937,11 @@ enum msg_hash_enums #define MENU_LABEL_HELP_CHANGE_VIRTUAL_GAMEPAD 0x6e66ef07U #define MENU_LABEL_HELP_AUDIO_VIDEO_TROUBLESHOOTING 0xd44d395cU +/* Manual content scan */ +#define MENU_LABEL_MANUAL_CONTENT_SCAN_DIR 0x6674149FU +#define MENU_LABEL_MANUAL_CONTENT_SCAN_SYSTEM_NAME 0xA3EC34C5U +#define MENU_LABEL_MANUAL_CONTENT_SCAN_CORE_NAME 0xD13B7849U + /* Main menu */ #define MENU_LABEL_LOAD_CONTENT_LIST 0x5745de1fU #define MENU_LABEL_LOAD_CONTENT_HISTORY 0xfe1d79e5U diff --git a/playlist.c b/playlist.c index 5688faa129..65aea43bfb 100644 --- a/playlist.c +++ b/playlist.c @@ -368,8 +368,7 @@ void playlist_get_index_by_path(playlist_t *playlist, } bool playlist_entry_exists(playlist_t *playlist, - const char *path, - const char *crc32) + const char *path) { size_t i; char real_search_path[PATH_MAX_LENGTH]; diff --git a/playlist.h b/playlist.h index 59bcd078d6..39a63c7de2 100644 --- a/playlist.h +++ b/playlist.h @@ -214,8 +214,7 @@ void playlist_get_index_by_path(playlist_t *playlist, const struct playlist_entry **entry); bool playlist_entry_exists(playlist_t *playlist, - const char *path, - const char *crc32); + const char *path); char *playlist_get_conf_path(playlist_t *playlist); diff --git a/tasks/task_database.c b/tasks/task_database.c index 1b71a34d6b..65ed332d46 100644 --- a/tasks/task_database.c +++ b/tasks/task_database.c @@ -850,7 +850,7 @@ static int database_info_list_iterate_found_match( fprintf(stderr, "entry path str: %s\n", entry_path_str); #endif - if (!playlist_entry_exists(playlist, entry_path_str, db_crc)) + if (!playlist_entry_exists(playlist, entry_path_str)) { struct playlist_entry entry; @@ -1052,8 +1052,7 @@ static int task_database_iterate_playlist_lutro( free(db_playlist_path); - if (!playlist_entry_exists(playlist, - path, "DETECT")) + if (!playlist_entry_exists(playlist, path)) { struct playlist_entry entry; char *game_title = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); diff --git a/tasks/task_manual_content_scan.c b/tasks/task_manual_content_scan.c new file mode 100644 index 0000000000..71e47bf257 --- /dev/null +++ b/tasks/task_manual_content_scan.c @@ -0,0 +1,354 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2014-2017 - Jean-AndrĂ© Santoni + * Copyright (C) 2016-2019 - Brad Parker + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tasks_internal.h" + +#include "../retroarch.h" +#include "../msg_hash.h" +#include "../playlist.h" +#include "../manual_content_scan.h" + +#ifdef RARCH_INTERNAL +#ifdef HAVE_MENU +#include "../menu/menu_driver.h" +#endif +#endif + +enum manual_scan_status +{ + MANUAL_SCAN_BEGIN = 0, + MANUAL_SCAN_ITERATE_CONTENT, + MANUAL_SCAN_END +}; + +typedef struct manual_scan_handle +{ + manual_content_scan_task_config_t *task_config; + playlist_t *playlist; + struct string_list *content_list; + size_t list_size; + size_t list_index; + enum manual_scan_status status; +} manual_scan_handle_t; + +/* Frees task handle + all constituent objects */ +static void free_manual_content_scan_handle(manual_scan_handle_t *manual_scan) +{ + if (!manual_scan) + return; + + if (manual_scan->task_config) + { + free(manual_scan->task_config); + manual_scan->task_config = NULL; + } + + if (manual_scan->playlist) + { + playlist_free(manual_scan->playlist); + manual_scan->playlist = NULL; + } + + if (manual_scan->content_list) + { + string_list_free(manual_scan->content_list); + manual_scan->content_list = NULL; + } + + free(manual_scan); + manual_scan = NULL; +} + +static void task_manual_content_scan_handler(retro_task_t *task) +{ + manual_scan_handle_t *manual_scan = NULL; + + if (!task) + goto task_finished; + + manual_scan = (manual_scan_handle_t*)task->state; + + if (!manual_scan) + goto task_finished; + + if (task_get_cancelled(task)) + goto task_finished; + + switch (manual_scan->status) + { + case MANUAL_SCAN_BEGIN: + { + /* Get content list */ + manual_scan->content_list = manual_content_scan_get_content_list( + manual_scan->task_config); + + if (!manual_scan->content_list) + { + runloop_msg_queue_push( + msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT), + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + goto task_finished; + } + + manual_scan->list_size = manual_scan->content_list->size; + + /* Open playlist */ + manual_scan->playlist = playlist_init( + manual_scan->task_config->playlist_file, COLLECTION_SIZE); + + if (!manual_scan->playlist) + goto task_finished; + + /* Reset playlist, if required */ + if (manual_scan->task_config->overwrite_playlist) + playlist_clear(manual_scan->playlist); + + /* Set default core, if required */ + if (manual_scan->task_config->core_set) + { + playlist_set_default_core_path( + manual_scan->playlist, manual_scan->task_config->core_path); + playlist_set_default_core_name( + manual_scan->playlist, manual_scan->task_config->core_name); + } + + /* All good - can start iterating */ + manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT; + } + break; + case MANUAL_SCAN_ITERATE_CONTENT: + { + const char *content_path = + manual_scan->content_list->elems[manual_scan->list_index].data; + + if (!string_is_empty(content_path)) + { + const char *content_file = path_basename(content_path); + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Update progress display */ + task_free_title(task); + + strlcpy( + task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS), + sizeof(task_title)); + + if (!string_is_empty(content_file)) + strlcat(task_title, content_file, sizeof(task_title)); + + task_set_title(task, strdup(task_title)); + task_set_progress(task, (manual_scan->list_index * 100) / manual_scan->list_size); + + /* Add content to playlist */ + manual_content_scan_add_content_to_playlist( + manual_scan->task_config, manual_scan->playlist, + content_path); + } + + /* Increment content index */ + manual_scan->list_index++; + if (manual_scan->list_index >= manual_scan->list_size) + manual_scan->status = MANUAL_SCAN_END; + } + break; + case MANUAL_SCAN_END: + { + playlist_t *cached_playlist = playlist_get_cached(); + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Ensure playlist is alphabetically sorted */ + playlist_qsort(manual_scan->playlist); + + /* Save playlist changes to disk */ + playlist_write_file(manual_scan->playlist); + + /* 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( + manual_scan->task_config->playlist_file, + playlist_get_conf_path(cached_playlist))) + { + playlist_free_cached(); + playlist_init_cached( + manual_scan->task_config->playlist_file, COLLECTION_SIZE); + } + } + + /* Update progress display */ + task_free_title(task); + + strlcpy( + task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_END), + sizeof(task_title)); + strlcat(task_title, manual_scan->task_config->system_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_manual_content_scan_handle(manual_scan); +} + +static bool task_manual_content_scan_finder(retro_task_t *task, void *user_data) +{ + manual_scan_handle_t *manual_scan = NULL; + + if (!task || !user_data) + return false; + + if (task->handler != task_manual_content_scan_handler) + return false; + + manual_scan = (manual_scan_handle_t*)task->state; + if (!manual_scan) + return false; + + return string_is_equal( + (const char*)user_data, manual_scan->task_config->playlist_file); +} + +static void cb_task_manual_content_scan_refresh_menu( + retro_task_t *task, void *task_data, + void *user_data, const char *err) +{ +#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) + menu_ctx_environment_t menu_environ; + menu_environ.type = MENU_ENVIRON_RESET_HORIZONTAL_LIST; + menu_environ.data = NULL; + + menu_driver_ctl(RARCH_MENU_CTL_ENVIRONMENT, &menu_environ); +#endif +} + +bool task_push_manual_content_scan(void) +{ + task_finder_data_t find_data; + char task_title[PATH_MAX_LENGTH]; + retro_task_t *task = NULL; + manual_scan_handle_t *manual_scan = (manual_scan_handle_t*) + calloc(1, sizeof(manual_scan_handle_t)); + + task_title[0] = '\0'; + + /* Sanity check */ + if (!manual_scan) + goto error; + + /* Configure handle */ + manual_scan->task_config = NULL; + manual_scan->playlist = NULL; + manual_scan->content_list = NULL; + manual_scan->list_size = 0; + manual_scan->list_index = 0; + manual_scan->status = MANUAL_SCAN_BEGIN; + + /* > Get current manual content scan configuration */ + manual_scan->task_config = (manual_content_scan_task_config_t*) + calloc(1, sizeof(manual_content_scan_task_config_t)); + + if (!manual_scan->task_config) + goto error; + + if (!manual_content_scan_get_task_config(manual_scan->task_config)) + { + runloop_msg_queue_push( + msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG), + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + goto error; + } + + /* Concurrent scanning of content to the same + * playlist is not allowed */ + find_data.func = task_manual_content_scan_finder; + find_data.userdata = (void*)manual_scan->task_config->playlist_file; + + if (task_queue_find(&find_data)) + goto error; + + /* Create task */ + task = task_init(); + + if (!task) + goto error; + + /* > Get task title */ + strlcpy( + task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_START), + sizeof(task_title)); + strlcat(task_title, manual_scan->task_config->system_name, + sizeof(task_title)); + + /* > Configure task */ + task->handler = task_manual_content_scan_handler; + task->state = manual_scan; + task->title = strdup(task_title); + task->alternative_look = true; + task->progress = 0; + task->callback = cb_task_manual_content_scan_refresh_menu; + + /* > Push task */ + task_queue_push(task); + + return true; + +error: + + /* Clean up task */ + if (task) + { + free(task); + task = NULL; + } + + /* Clean up handle */ + free_manual_content_scan_handle(manual_scan); + manual_scan = NULL; + + return false; +} diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index 7669ee96d9..201975c08b 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -98,6 +98,8 @@ bool task_push_dbscan( retro_task_callback_t cb); #endif +bool task_push_manual_content_scan(void); + #ifdef HAVE_OVERLAY bool task_push_overlay_load_default( retro_task_callback_t cb,