Merge pull request #12963 from jdgleaver/playlist-refresh

(Playlist Manager) Add 'Refresh Playlist' option
This commit is contained in:
Autechre 2021-09-09 18:44:42 +02:00 committed by GitHub
commit 27bd9868d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1096 additions and 128 deletions

View File

@ -2314,6 +2314,10 @@ MSG_HASH(
MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST,
"playlist_manager_clean_playlist"
)
MSG_HASH(
MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
"playlist_manager_refresh_playlist"
)
MSG_HASH(
MENU_ENUM_LABEL_PLAYLIST_SETTINGS_BEGIN,
"playlist_settings_begin"
@ -5006,6 +5010,10 @@ MSG_HASH(
MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_OVERWRITE,
"manual_content_scan_overwrite"
)
MSG_HASH(
MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
"manual_content_scan_validate_entries"
)
MSG_HASH(
MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START,
"manual_content_scan_start"

View File

@ -5559,6 +5559,14 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST,
"Validate core associations and remove invalid and duplicate entries."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
"Refresh Playlist"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
"Add new content and remove invalid entries by repeating the 'Manual Scan' operation last used to create or edit the playlist."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_DELETE_PLAYLIST,
"Delete Playlist"
@ -6151,6 +6159,14 @@ 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_VALIDATE_ENTRIES,
"Validate Existing Entries"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
"When enabled, entries in any existing playlist will be verified before before scanning new content. Entries referring to missing content and/or files with invalid extensions will be removed."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_START,
"Start Scan"
@ -10893,6 +10909,30 @@ MSG_HASH(
MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED,
"Playlist cleaned: "
)
MSG_HASH(
MSG_PLAYLIST_MANAGER_REFRESH_MISSING_CONFIG,
"Refresh failed - playlist contains no valid scan record: "
)
MSG_HASH(
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CONTENT_DIR,
"Refresh failed - invalid/missing content directory: "
)
MSG_HASH(
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_SYSTEM_NAME,
"Refresh failed - invalid/missing system name: "
)
MSG_HASH(
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CORE,
"Refresh failed - invalid core: "
)
MSG_HASH(
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_DAT_FILE,
"Refresh failed - invalid/missing arcade DAT file: "
)
MSG_HASH(
MSG_PLAYLIST_MANAGER_REFRESH_DAT_FILE_TOO_LARGE,
"Refresh failed - arcade DAT file too large (insufficient memory): "
)
MSG_HASH(
MSG_ADDED_TO_FAVORITES,
"Added to favorites"
@ -12085,6 +12125,10 @@ MSG_HASH(
MSG_MANUAL_CONTENT_SCAN_START,
"Scanning content: "
)
MSG_HASH(
MSG_MANUAL_CONTENT_SCAN_PLAYLIST_CLEANUP,
"Checking current entries: "
)
MSG_HASH(
MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS,
"Scanning: "

View File

@ -56,6 +56,7 @@ typedef struct
bool search_archives;
bool filter_dat_content;
bool overwrite_playlist;
bool validate_entries;
} scan_settings_t;
/* TODO/FIXME - static public global variables */
@ -83,7 +84,8 @@ static scan_settings_t scan_settings = {
true, /* search_recursively */
false, /* search_archives */
false, /* filter_dat_content */
false /* overwrite_playlist */
false, /* overwrite_playlist */
false /* validate_entries */
};
/*****************/
@ -176,6 +178,13 @@ bool *manual_content_scan_get_overwrite_playlist_ptr(void)
return &scan_settings.overwrite_playlist;
}
/* Returns a pointer to the internal
* 'validate_entries' bool */
bool *manual_content_scan_get_validate_entries_ptr(void)
{
return &scan_settings.validate_entries;
}
/* Sanitisation */
/* Sanitises file extensions list string:
@ -493,6 +502,234 @@ error:
return false;
}
/* Sets all parameters for the next manual scan
* operation according the to recorded values in
* the specified playlist.
* Returns MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK
* if playlist contains a valid scan record. */
enum manual_content_scan_playlist_refresh_status
manual_content_scan_set_menu_from_playlist(playlist_t *playlist,
const char *path_content_database, bool show_hidden_files)
{
const char *playlist_path = NULL;
const char *playlist_file = NULL;
const char *content_dir = NULL;
const char *core_name = NULL;
const char *file_exts = NULL;
const char *dat_file_path = NULL;
bool search_recursively = false;
bool search_archives = false;
bool filter_dat_content = false;
#ifdef HAVE_LIBRETRODB
struct string_list *rdb_list = NULL;
#endif
enum manual_content_scan_system_name_type
system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR;
enum manual_content_scan_core_type
core_type = MANUAL_CONTENT_SCAN_CORE_DETECT;
enum manual_content_scan_playlist_refresh_status
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK;
char system_name[PATH_MAX_LENGTH];
system_name[0] = '\0';
if (!playlist_scan_refresh_enabled(playlist))
{
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG;
goto end;
}
/* Read scan parameters from playlist */
playlist_path = playlist_get_conf_path(playlist);
content_dir = playlist_get_scan_content_dir(playlist);
core_name = playlist_get_default_core_name(playlist);
file_exts = playlist_get_scan_file_exts(playlist);
dat_file_path = playlist_get_scan_dat_file_path(playlist);
search_recursively = playlist_get_scan_search_recursively(playlist);
search_archives = playlist_get_scan_search_archives(playlist);
filter_dat_content = playlist_get_scan_filter_dat_content(playlist);
/* Determine system name (playlist basename
* without extension) */
if (string_is_empty(playlist_path))
{
/* Cannot happen, but would constitute a
* 'system name' error */
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
goto end;
}
if ((playlist_file = path_basename(playlist_path)))
{
strlcpy(system_name, playlist_file, sizeof(system_name));
path_remove_extension(system_name);
}
if (string_is_empty(system_name))
{
/* Cannot happen, but would constitute a
* 'system name' error */
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
goto end;
}
/* Set content directory */
if (!manual_content_scan_set_menu_content_dir(content_dir))
{
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR;
goto end;
}
/* Set system name */
#ifdef HAVE_LIBRETRODB
/* > If platform has database support, get names
* of all installed database files */
rdb_list = dir_list_new_special(
path_content_database,
DIR_LIST_DATABASES, NULL, show_hidden_files);
if (rdb_list && rdb_list->size)
{
size_t i;
/* 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;
/* Check whether playlist system name
* matches current database file */
if (string_is_equal(system_name, rdb_name))
{
system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE;
break;
}
}
}
string_list_free(rdb_list);
#endif
/* > If system name does not match a database
* file, then check whether it matches the
* content directory name */
if (system_name_type !=
MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE)
{
/* system_name_type is set to
* MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR
* by default - so if a match is found just
* reset 'custom name' field */
if (string_is_equal(system_name,
scan_settings.system_name_content_dir))
scan_settings.system_name_custom[0] = '\0';
else
{
/* Playlist is using a custom system name */
system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM;
strlcpy(scan_settings.system_name_custom, system_name,
sizeof(scan_settings.system_name_custom));
}
}
if (!manual_content_scan_set_menu_system_name(
system_name_type, system_name))
{
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
goto end;
}
/* Set core path/name */
if (!string_is_empty(core_name) &&
!string_is_equal(core_name, FILE_PATH_DETECT))
core_type = MANUAL_CONTENT_SCAN_CORE_SET;
if (!manual_content_scan_set_menu_core_name(
core_type, core_name))
{
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE;
goto end;
}
/* Set custom file extensions */
if (string_is_empty(file_exts))
scan_settings.file_exts_custom[0] = '\0';
else
{
strlcpy(scan_settings.file_exts_custom, file_exts,
sizeof(scan_settings.file_exts_custom));
/* File extensions read from playlist should
* be correctly formatted, with '|' characters
* as delimiters
* > For menu purposes, must replace these
* delimiters with space characters
* > Additionally scrub the resultant string,
* to handle the case where a user has
* 'corrupted' it by manually tampering with
* the playlist file */
string_replace_all_chars(scan_settings.file_exts_custom, '|', ' ');
manual_content_scan_scrub_file_exts(scan_settings.file_exts_custom);
}
/* Set DAT file path */
if (string_is_empty(dat_file_path))
scan_settings.dat_file_path[0] = '\0';
else
{
strlcpy(scan_settings.dat_file_path, dat_file_path,
sizeof(scan_settings.dat_file_path));
switch (manual_content_scan_validate_dat_file_path())
{
case MANUAL_CONTENT_SCAN_DAT_FILE_INVALID:
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE;
goto end;
case MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE:
playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE;
goto end;
default:
/* No action required */
break;
}
}
/* Set remaining boolean parameters */
scan_settings.search_recursively = search_recursively;
scan_settings.search_archives = search_archives;
scan_settings.filter_dat_content = filter_dat_content;
/* When refreshing a playlist:
* > We never overwrite the existing file
* > We always validate entries in the
* existing file */
scan_settings.overwrite_playlist = false;
scan_settings.validate_entries = true;
end:
return playlist_status;
}
/* Menu getters */
/* Fetches content directory for next manual scan
@ -858,11 +1095,15 @@ bool manual_content_scan_get_task_config(
}
/* Get file extensions list */
task_config->file_exts_custom_set = false;
if (!string_is_empty(scan_settings.file_exts_custom))
{
task_config->file_exts_custom_set = true;
strlcpy(
task_config->file_exts,
scan_settings.file_exts_custom,
sizeof(task_config->file_exts));
}
else if (scan_settings.core_type == MANUAL_CONTENT_SCAN_CORE_SET)
if (!string_is_empty(scan_settings.file_exts_core))
strlcpy(
@ -890,15 +1131,14 @@ bool manual_content_scan_get_task_config(
/* Copy 'search recursively' setting */
task_config->search_recursively = scan_settings.search_recursively;
/* Copy 'search inside archives' setting */
task_config->search_archives = scan_settings.search_archives;
task_config->search_archives = scan_settings.search_archives;
/* Copy 'DAT file filter' setting */
task_config->filter_dat_content = scan_settings.filter_dat_content;
/* Copy 'overwrite playlist' setting */
task_config->overwrite_playlist = scan_settings.overwrite_playlist;
/* Copy 'validate_entries' setting */
task_config->validate_entries = scan_settings.validate_entries;
return true;
}
@ -907,7 +1147,8 @@ bool manual_content_scan_get_task_config(
* 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 *manual_content_scan_get_content_list(
manual_content_scan_task_config_t *task_config)
{
struct string_list *dir_list = NULL;
bool filter_exts;
@ -1155,8 +1396,8 @@ void manual_content_scan_add_content_to_playlist(
* so these casts are safe */
entry.path = (char*)playlist_content_path;
entry.label = label;
entry.core_path = (char*)"DETECT";
entry.core_name = (char*)"DETECT";
entry.core_path = (char*)FILE_PATH_DETECT;
entry.core_name = (char*)FILE_PATH_DETECT;
entry.crc32 = (char*)"00000000|crc";
entry.db_name = task_config->database_name;

View File

@ -65,6 +65,19 @@ enum manual_content_scan_dat_file_path_status
MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE
};
/* Defines all possible return values for
* manual_content_scan_set_menu_from_playlist() */
enum manual_content_scan_playlist_refresh_status
{
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK = 0,
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG,
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR,
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME,
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE,
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE,
MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE
};
/* Holds all configuration parameters required
* for a manual content scan task */
typedef struct
@ -78,10 +91,12 @@ typedef struct
char file_exts[PATH_MAX_LENGTH];
char dat_file_path[PATH_MAX_LENGTH];
bool core_set;
bool file_exts_custom_set;
bool search_recursively;
bool search_archives;
bool filter_dat_content;
bool overwrite_playlist;
bool validate_entries;
} manual_content_scan_task_config_t;
/*****************/
@ -142,6 +157,10 @@ bool *manual_content_scan_get_filter_dat_content_ptr(void);
* 'overwrite_playlist' bool */
bool *manual_content_scan_get_overwrite_playlist_ptr(void);
/* Returns a pointer to the internal
* 'validate_entries' bool */
bool *manual_content_scan_get_validate_entries_ptr(void);
/* Sanitisation */
/* Removes invalid characters from
@ -187,6 +206,15 @@ bool manual_content_scan_set_menu_core_name(
enum manual_content_scan_core_type core_type,
const char *core_name);
/* Sets all parameters for the next manual scan
* operation according the to recorded values in
* the specified playlist.
* Returns MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK
* if playlist contains a valid scan record. */
enum manual_content_scan_playlist_refresh_status
manual_content_scan_set_menu_from_playlist(playlist_t *playlist,
const char *path_content_database, bool show_hidden_files);
/* Menu getters */
/* Fetches content directory for next manual scan
@ -239,7 +267,8 @@ bool manual_content_scan_get_task_config(
* 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 *manual_content_scan_get_content_list(
manual_content_scan_task_config_t *task_config);
/* Adds specified content to playlist, if not already
* present */

View File

@ -7023,7 +7023,9 @@ static int action_ok_manual_content_scan_start(const char *path,
playlist_config.old_format = settings->bools.playlist_use_old_format;
playlist_config.compress = settings->bools.playlist_compression;
playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
playlist_config_set_base_content_directory(&playlist_config,
settings->bools.playlist_portable_paths ?
settings->paths.directory_menu_content : NULL);
task_push_manual_content_scan(&playlist_config, directory_playlist);
return 0;
@ -7460,6 +7462,120 @@ static int action_ok_playlist_clean(const char *path,
return 0;
}
static int action_ok_playlist_refresh(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx)
{
playlist_config_t *playlist_config = NULL;
playlist_t *playlist = playlist_get_cached();
settings_t *settings = config_get_ptr();
bool scan_record_valid = false;
const char *msg_prefix = NULL;
const char *msg_subject = NULL;
const char *log_text = NULL;
char system_name[256];
system_name[0] = '\0';
if (!playlist || !settings)
return -1;
playlist_config = playlist_get_config(playlist);
if (!playlist_config || string_is_empty(playlist_config->path))
return -1;
/* Configure manual scan using playlist record */
switch (manual_content_scan_set_menu_from_playlist(playlist,
settings->paths.path_content_database,
settings->bools.show_hidden_files))
{
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK:
scan_record_valid = true;
break;
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR:
msg_prefix = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CONTENT_DIR);
msg_subject = playlist_get_scan_content_dir(playlist);
log_text = "[Playlist Refresh]: Invalid content directory: %s\n";
break;
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME:
{
const char *playlist_file = NULL;
if ((playlist_file = path_basename(playlist_config->path)))
{
strlcpy(system_name, playlist_file, sizeof(system_name));
path_remove_extension(system_name);
}
msg_prefix = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_SYSTEM_NAME);
msg_subject = system_name;
log_text = "[Playlist Refresh]: Invalid system name: %s\n";
}
break;
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE:
msg_prefix = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CORE);
msg_subject = playlist_get_default_core_name(playlist);
log_text = "[Playlist Refresh]: Invalid core name: %s\n";
break;
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE:
msg_prefix = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_INVALID_DAT_FILE);
msg_subject = playlist_get_scan_dat_file_path(playlist);
log_text = "[Playlist Refresh]: Invalid arcade dat file: %s\n";
break;
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE:
msg_prefix = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_DAT_FILE_TOO_LARGE);
msg_subject = playlist_get_scan_dat_file_path(playlist);
log_text = "[Playlist Refresh]: Arcade dat file too large: %s\n";
break;
case MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG:
default:
msg_prefix = msg_hash_to_str(MSG_PLAYLIST_MANAGER_REFRESH_MISSING_CONFIG);
msg_subject = path_basename(playlist_config->path);
log_text = "[Playlist Refresh]: No scan record found: %s\n";
break;
}
/* Log errors in the event of an invalid
* scan record */
if (!scan_record_valid)
{
char msg[PATH_MAX_LENGTH];
msg[0] = '\0';
if (string_is_empty(msg_subject))
msg_subject = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE);
snprintf(msg, sizeof(msg), "%s%s", msg_prefix, msg_subject);
RARCH_ERR(log_text, msg_subject);
runloop_msg_queue_push(msg, 1, 150, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
/* Even though this is a failure condition, we
* return 0 here to suppress any refreshing of
* the menu (this can appear ugly, depending
* on the active menu driver...) */
return 0;
}
/* Perform manual scan
* > Since we are refreshing the playlist,
* additionally ensure that all pertinent
* 'playlist_config' parameters are synchronised
* with the current settings struct */
playlist_config->capacity = COLLECTION_SIZE;
playlist_config->old_format = settings->bools.playlist_use_old_format;
playlist_config->compress = settings->bools.playlist_compression;
playlist_config->fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
playlist_config_set_base_content_directory(playlist_config,
settings->bools.playlist_portable_paths ?
settings->paths.directory_menu_content : NULL);
task_push_manual_content_scan(playlist_config,
settings->paths.directory_playlist);
return 0;
}
static int is_rdb_entry(enum msg_hash_enums enum_idx)
{
switch (enum_idx)
@ -7740,6 +7856,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_PLAYLIST_MANAGER_SETTINGS, action_ok_push_playlist_manager_settings},
{MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES, action_ok_playlist_reset_cores},
{MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST, action_ok_playlist_clean},
{MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST, action_ok_playlist_refresh},
{MENU_ENUM_LABEL_RECORDING_SETTINGS, action_ok_push_recording_settings_list},
{MENU_ENUM_LABEL_INPUT_HOTKEY_BINDS, action_ok_push_input_hotkey_binds_list},
{MENU_ENUM_LABEL_ACCOUNTS_RETRO_ACHIEVEMENTS, action_ok_push_accounts_cheevos_list},

View File

@ -168,6 +168,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_reset_cores, MENU_
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_label_display_mode, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_sort_mode, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_SORT_MODE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_clean_playlist, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_manager_refresh_playlist, MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_delete_playlist, MENU_ENUM_SUBLABEL_DELETE_PLAYLIST)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_network_settings_list, MENU_ENUM_SUBLABEL_NETWORK_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_network_on_demand_thumbnails, MENU_ENUM_SUBLABEL_NETWORK_ON_DEMAND_THUMBNAILS)
@ -985,6 +986,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_search_archives,
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_dat_file, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_dat_file_filter, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_DAT_FILE_FILTER)
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_validate_entries, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_manual_content_scan_start, MENU_ENUM_SUBLABEL_MANUAL_CONTENT_SCAN_START)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_create_backup, MENU_ENUM_SUBLABEL_CORE_CREATE_BACKUP)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_restore_backup_list, MENU_ENUM_SUBLABEL_CORE_RESTORE_BACKUP_LIST)
@ -3960,6 +3962,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_manager_clean_playlist);
break;
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_manager_refresh_playlist);
break;
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE:
{
const char *menu_ident = menu_driver_ident();
@ -4400,6 +4405,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
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_VALIDATE_ENTRIES:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_validate_entries);
break;
case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_START:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_manual_content_scan_start);
break;

View File

@ -10453,7 +10453,8 @@ static void materialui_list_insert(
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_OVERLAYS)) ||
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_CG_SHADERS)) ||
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_GLSL_SHADERS)) ||
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_SLANG_SHADERS))
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_SLANG_SHADERS)) ||
string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST))
)
{
node->icon_texture_index = MUI_TEXTURE_UPDATER;

View File

@ -1743,6 +1743,7 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone,
case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL:
case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD];
case MENU_ENUM_LABEL_SHUTDOWN:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SHUTDOWN];

View File

@ -2601,6 +2601,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL:
case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
return xmb->textures.list[XMB_TEXTURE_RELOAD];
case MENU_ENUM_LABEL_RENAME_ENTRY:
return xmb->textures.list[XMB_TEXTURE_RENAME];

View File

@ -4035,6 +4035,14 @@ static bool menu_displaylist_parse_playlist_manager_settings(
MENU_ENUM_LABEL_PLAYLIST_MANAGER_SORT_MODE,
MENU_SETTING_PLAYLIST_MANAGER_SORT_MODE, 0, 0);
/* Refresh playlist */
if (playlist_scan_refresh_enabled(playlist))
menu_entries_append_enum(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_REFRESH_PLAYLIST),
msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST),
MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
MENU_SETTING_ACTION_PLAYLIST_MANAGER_REFRESH_PLAYLIST, 0, 0);
/* Clean playlist */
menu_entries_append_enum(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_MANAGER_CLEAN_PLAYLIST),
@ -5240,6 +5248,13 @@ static bool menu_displaylist_parse_manual_content_scan_list(
false) == 0)
count++;
/* Validate existing entries */
if (!(*manual_content_scan_get_overwrite_playlist_ptr()) &&
MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(info->list,
MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES, 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),

View File

@ -243,6 +243,7 @@ enum menu_settings_type
MENU_SETTING_ACTION_DELETE_PLAYLIST,
MENU_SETTING_ACTION_PLAYLIST_MANAGER_RESET_CORES,
MENU_SETTING_ACTION_PLAYLIST_MANAGER_CLEAN_PLAYLIST,
MENU_SETTING_ACTION_PLAYLIST_MANAGER_REFRESH_PLAYLIST,
MENU_SETTING_MANUAL_CONTENT_SCAN_DIR,
MENU_SETTING_MANUAL_CONTENT_SCAN_SYSTEM_NAME,

View File

@ -20428,6 +20428,24 @@ static bool setting_append_list(
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
(*list)[list_info->index - 1].action_ok = setting_bool_action_left_with_refresh;
(*list)[list_info->index - 1].action_left = setting_bool_action_left_with_refresh;
(*list)[list_info->index - 1].action_right = setting_bool_action_right_with_refresh;
CONFIG_BOOL(
list, list_info,
manual_content_scan_get_validate_entries_ptr(),
MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES,
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);

View File

@ -2411,6 +2411,15 @@ enum msg_hash_enums
MSG_PLAYLIST_MANAGER_CLEANING_PLAYLIST,
MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED,
MENU_LABEL(PLAYLIST_MANAGER_REFRESH_PLAYLIST),
MSG_PLAYLIST_MANAGER_REFRESH_MISSING_CONFIG,
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CONTENT_DIR,
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_SYSTEM_NAME,
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_CORE,
MSG_PLAYLIST_MANAGER_REFRESH_INVALID_DAT_FILE,
MSG_PLAYLIST_MANAGER_REFRESH_DAT_FILE_TOO_LARGE,
MENU_LABEL(CORE_UPDATER_SETTINGS),
MENU_LABEL(LAKKA_SERVICES),
MENU_LABEL(SHADER_APPLY_CHANGES),
@ -3203,6 +3212,7 @@ enum msg_hash_enums
MENU_LABEL(MANUAL_CONTENT_SCAN_DAT_FILE),
MENU_LABEL(MANUAL_CONTENT_SCAN_DAT_FILE_FILTER),
MENU_LABEL(MANUAL_CONTENT_SCAN_OVERWRITE),
MENU_LABEL(MANUAL_CONTENT_SCAN_VALIDATE_ENTRIES),
MENU_LABEL(MANUAL_CONTENT_SCAN_START),
MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR,
@ -3216,6 +3226,7 @@ enum msg_hash_enums
MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG,
MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT,
MSG_MANUAL_CONTENT_SCAN_START,
MSG_MANUAL_CONTENT_SCAN_PLAYLIST_CLEANUP,
MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS,
MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP,
MSG_MANUAL_CONTENT_SCAN_END,

View File

@ -28,6 +28,7 @@
#include <string/stdstring.h>
#include <streams/interface_stream.h>
#include <file/file_path.h>
#include <file/archive_file.h>
#include <lists/string_list.h>
#include <formats/rjson.h>
#include <array/rbuf.h>
@ -56,6 +57,19 @@
#define USING_POSIX_FILE_SYSTEM
#endif
/* Holds all configuration parameters required
* to repeat a manual content scan for a
* previously manual-scan-generated playlist */
typedef struct
{
char *content_dir;
char *file_exts;
char *dat_file_path;
bool search_recursively;
bool search_archives;
bool filter_dat_content;
} playlist_manual_scan_record_t;
struct content_playlist
{
char *default_core_path;
@ -64,7 +78,8 @@ struct content_playlist
struct playlist_entry *entries;
playlist_config_t config; /* size_t alignment */
playlist_manual_scan_record_t scan_record; /* ptr alignment */
playlist_config_t config; /* size_t alignment */
enum playlist_label_display_mode label_display_mode;
enum playlist_thumbnail_mode right_thumbnail_mode;
@ -85,6 +100,7 @@ typedef struct
enum playlist_label_display_mode *current_meta_label_display_mode_val;
enum playlist_thumbnail_mode *current_meta_thumbnail_mode_val;
enum playlist_sort_mode *current_meta_sort_mode_val;
bool *current_meta_bool_val;
playlist_t *playlist;
unsigned array_depth;
@ -1123,6 +1139,89 @@ void playlist_resolve_path(enum playlist_file_mode mode,
#endif
}
/**
* playlist_content_path_is_valid:
* @path : Content path
*
* Checks whether specified playlist content path
* refers to an existent file. Handles all playlist
* content path 'types' (i.e. can validate paths
* referencing files inside archives).
*
* Returns true if file referenced by content
* path exists on the host filesystem.
**/
bool playlist_content_path_is_valid(const char *path)
{
/* Sanity check */
if (string_is_empty(path))
return false;
/* If content is inside an archive, special
* handling is required... */
if (path_contains_compressed_file(path))
{
const char *delim = path_get_archive_delim(path);
char archive_path[PATH_MAX_LENGTH] = {0};
size_t len = 0;
struct string_list *archive_list = NULL;
const char *content_file = NULL;
bool content_found = false;
if (!delim)
return false;
/* Get path of 'parent' archive file */
len = (size_t)(1 + delim - path);
strlcpy(archive_path, path,
(len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
/* Check if archive itself exists */
if (!path_is_valid(archive_path))
return false;
/* Check if file exists inside archive */
archive_list = file_archive_get_file_list(archive_path, NULL);
if (!archive_list)
return false;
/* > Get playlist entry content file name
* (sans archive file path) */
content_file = delim;
content_file++;
if (!string_is_empty(content_file))
{
size_t i;
/* > Loop over archive file contents */
for (i = 0; i < archive_list->size; i++)
{
const char *archive_file = archive_list->elems[i].data;
if (string_is_empty(archive_file))
continue;
if (string_is_equal(content_file, archive_file))
{
content_found = true;
break;
}
}
}
/* Clean up */
string_list_free(archive_list);
return content_found;
}
/* This is a 'normal' path - just check if
* it's valid */
else
return path_is_valid(path);
}
/**
* playlist_push:
* @playlist : Playlist handle.
@ -1640,7 +1739,7 @@ void playlist_write_file(playlist_t *playlist)
rjsonwriter_add_string(writer, "version");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_string(writer, "1.4");
rjsonwriter_add_string(writer, "1.5");
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
@ -1703,6 +1802,57 @@ void playlist_write_file(playlist_t *playlist)
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
if (!string_is_empty(playlist->scan_record.content_dir))
{
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "scan_content_dir");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_string(writer, playlist->scan_record.content_dir);
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "scan_file_exts");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_string(writer, playlist->scan_record.file_exts);
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "scan_dat_file_path");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_string(writer, playlist->scan_record.dat_file_path);
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "scan_search_recursively");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_bool(writer, playlist->scan_record.search_recursively);
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "scan_search_archives");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_bool(writer, playlist->scan_record.search_archives);
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "scan_filter_dat_content");
rjsonwriter_add_colon(writer);
rjsonwriter_add_space(writer);
rjsonwriter_add_bool(writer, playlist->scan_record.filter_dat_content);
rjsonwriter_add_comma(writer);
rjsonwriter_add_newline(writer);
}
rjsonwriter_add_spaces(writer, 2);
rjsonwriter_add_string(writer, "items");
rjsonwriter_add_colon(writer);
@ -1878,6 +2028,18 @@ void playlist_free(playlist_t *playlist)
free(playlist->base_content_directory);
playlist->base_content_directory = NULL;
if (playlist->scan_record.content_dir)
free(playlist->scan_record.content_dir);
playlist->scan_record.content_dir = NULL;
if (playlist->scan_record.file_exts)
free(playlist->scan_record.file_exts);
playlist->scan_record.file_exts = NULL;
if (playlist->scan_record.dat_file_path)
free(playlist->scan_record.dat_file_path);
playlist->scan_record.dat_file_path = NULL;
if (playlist->entries)
{
for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
@ -2118,6 +2280,21 @@ static bool JSONNumberHandler(void *context, const char *pValue, size_t length)
return true;
}
static bool JSONBoolHandler(void *context, bool value)
{
JSONContext *pCtx = (JSONContext *)context;
if (!pCtx->in_items &&
(pCtx->object_depth == 1) &&
(pCtx->array_depth == 0) &&
pCtx->current_meta_bool_val)
*pCtx->current_meta_bool_val = value;
pCtx->current_meta_bool_val = NULL;
return true;
}
static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t length)
{
JSONContext *pCtx = (JSONContext *)context;
@ -2197,7 +2374,9 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le
pCtx->current_meta_label_display_mode_val = NULL;
pCtx->current_meta_thumbnail_mode_val = NULL;
pCtx->current_meta_sort_mode_val = NULL;
pCtx->current_meta_bool_val = NULL;
pCtx->in_items = false;
switch (pValue[0])
{
case 'b':
@ -2205,7 +2384,7 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le
pCtx->current_string_val = &pCtx->playlist->base_content_directory;
break;
case 'd':
if (string_is_equal(pValue, "default_core_path"))
if (string_is_equal(pValue, "default_core_path"))
pCtx->current_string_val = &pCtx->playlist->default_core_path;
else if (string_is_equal(pValue, "default_core_name"))
pCtx->current_string_val = &pCtx->playlist->default_core_name;
@ -2215,17 +2394,29 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le
pCtx->in_items = true;
break;
case 'l':
if (string_is_equal(pValue, "label_display_mode"))
if (string_is_equal(pValue, "label_display_mode"))
pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode;
else if (string_is_equal(pValue, "left_thumbnail_mode"))
pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode;
pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode;
break;
case 'r':
if (string_is_equal(pValue, "right_thumbnail_mode"))
pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode;
break;
case 's':
if (string_is_equal(pValue, "sort_mode"))
if (string_is_equal(pValue, "scan_content_dir"))
pCtx->current_string_val = &pCtx->playlist->scan_record.content_dir;
else if (string_is_equal(pValue, "scan_file_exts"))
pCtx->current_string_val = &pCtx->playlist->scan_record.file_exts;
else if (string_is_equal(pValue, "scan_dat_file_path"))
pCtx->current_string_val = &pCtx->playlist->scan_record.dat_file_path;
else if (string_is_equal(pValue, "scan_search_recursively"))
pCtx->current_meta_bool_val = &pCtx->playlist->scan_record.search_recursively;
else if (string_is_equal(pValue, "scan_search_archives"))
pCtx->current_meta_bool_val = &pCtx->playlist->scan_record.search_archives;
else if (string_is_equal(pValue, "scan_filter_dat_content"))
pCtx->current_meta_bool_val = &pCtx->playlist->scan_record.filter_dat_content;
else if (string_is_equal(pValue, "sort_mode"))
pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode;
break;
}
@ -2338,7 +2529,8 @@ static bool playlist_read_file(playlist_t *playlist)
JSONEndObjectHandler,
JSONStartArrayHandler,
JSONEndArrayHandler,
NULL, NULL) /* unused boolean/null handlers */
JSONBoolHandler,
NULL) /* Unused null handler */
!= RJSON_DONE)
{
if (context.out_of_memory)
@ -2624,6 +2816,13 @@ playlist_t *playlist_init(const playlist_config_t *config)
playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT;
playlist->scan_record.search_recursively = false;
playlist->scan_record.search_archives = false;
playlist->scan_record.filter_dat_content = false;
playlist->scan_record.content_dir = NULL;
playlist->scan_record.file_exts = NULL;
playlist->scan_record.dat_file_path = NULL;
/* Cache configuration parameters */
if (!playlist_config_copy(config, &playlist->config))
goto error;
@ -2634,7 +2833,9 @@ playlist_t *playlist_init(const playlist_config_t *config)
/* Try auto-fixing paths if enabled, and playlist
* base content directory is different */
if (config->autofix_paths && !string_is_equal(playlist->base_content_directory, config->base_content_directory))
if (config->autofix_paths &&
!string_is_equal(playlist->base_content_directory,
config->base_content_directory))
{
if (!string_is_empty(playlist->base_content_directory))
{
@ -2651,9 +2852,9 @@ playlist_t *playlist_init(const playlist_config_t *config)
/* Fix entry path */
tmp_entry_path[0] = '\0';
path_replace_base_path_and_convert_to_local_file_system(
tmp_entry_path, entry->path,
playlist->base_content_directory, playlist->config.base_content_directory,
sizeof(tmp_entry_path));
tmp_entry_path, entry->path,
playlist->base_content_directory, playlist->config.base_content_directory,
sizeof(tmp_entry_path));
free(entry->path);
entry->path = strdup(tmp_entry_path);
@ -2676,9 +2877,9 @@ playlist_t *playlist_init(const playlist_config_t *config)
tmp_entry_path[0] = '\0';
path_replace_base_path_and_convert_to_local_file_system(
tmp_entry_path, subsystem_rom_path,
playlist->base_content_directory, playlist->config.base_content_directory,
sizeof(tmp_entry_path));
tmp_entry_path, subsystem_rom_path,
playlist->base_content_directory, playlist->config.base_content_directory,
sizeof(tmp_entry_path));
string_list_append(subsystem_roms_new_paths, tmp_entry_path, attributes);
}
@ -2686,6 +2887,32 @@ playlist_t *playlist_init(const playlist_config_t *config)
entry->subsystem_roms = subsystem_roms_new_paths;
}
}
/* Fix scan record content directory */
if (!string_is_empty(playlist->scan_record.content_dir))
{
tmp_entry_path[0] = '\0';
path_replace_base_path_and_convert_to_local_file_system(
tmp_entry_path, playlist->scan_record.content_dir,
playlist->base_content_directory, playlist->config.base_content_directory,
sizeof(tmp_entry_path));
free(playlist->scan_record.content_dir);
playlist->scan_record.content_dir = strdup(tmp_entry_path);
}
/* Fix scan record arcade DAT file */
if (!string_is_empty(playlist->scan_record.dat_file_path))
{
tmp_entry_path[0] = '\0';
path_replace_base_path_and_convert_to_local_file_system(
tmp_entry_path, playlist->scan_record.dat_file_path,
playlist->base_content_directory, playlist->config.base_content_directory,
sizeof(tmp_entry_path));
free(playlist->scan_record.dat_file_path);
playlist->scan_record.dat_file_path = strdup(tmp_entry_path);
}
}
/* Update playlist base content directory*/
@ -2958,14 +3185,14 @@ void playlist_get_db_name(playlist_t *playlist, size_t idx,
}
}
char *playlist_get_default_core_path(playlist_t *playlist)
const char *playlist_get_default_core_path(playlist_t *playlist)
{
if (!playlist)
return NULL;
return playlist->default_core_path;
}
char *playlist_get_default_core_name(playlist_t *playlist)
const char *playlist_get_default_core_name(playlist_t *playlist)
{
if (!playlist)
return NULL;
@ -3001,6 +3228,55 @@ enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist)
return playlist->sort_mode;
}
const char *playlist_get_scan_content_dir(playlist_t *playlist)
{
if (!playlist)
return NULL;
return playlist->scan_record.content_dir;
}
const char *playlist_get_scan_file_exts(playlist_t *playlist)
{
if (!playlist)
return NULL;
return playlist->scan_record.file_exts;
}
const char *playlist_get_scan_dat_file_path(playlist_t *playlist)
{
if (!playlist)
return NULL;
return playlist->scan_record.dat_file_path;
}
bool playlist_get_scan_search_recursively(playlist_t *playlist)
{
if (!playlist)
return false;
return playlist->scan_record.search_recursively;
}
bool playlist_get_scan_search_archives(playlist_t *playlist)
{
if (!playlist)
return false;
return playlist->scan_record.search_archives;
}
bool playlist_get_scan_filter_dat_content(playlist_t *playlist)
{
if (!playlist)
return false;
return playlist->scan_record.filter_dat_content;
}
bool playlist_scan_refresh_enabled(playlist_t *playlist)
{
if (!playlist)
return false;
return !string_is_empty(playlist->scan_record.content_dir);
}
void playlist_set_default_core_path(playlist_t *playlist, const char *core_path)
{
char real_core_path[PATH_MAX_LENGTH];
@ -3090,6 +3366,135 @@ void playlist_set_sort_mode(playlist_t *playlist,
}
}
void playlist_set_scan_content_dir(playlist_t *playlist, const char *content_dir)
{
bool current_string_empty;
bool new_string_empty;
if (!playlist)
return;
current_string_empty = string_is_empty(playlist->scan_record.content_dir);
new_string_empty = string_is_empty(content_dir);
/* Check whether string value has changed
* (note that a NULL or empty argument will
* unset the playlist value) */
if (( current_string_empty && !new_string_empty) ||
(!current_string_empty && new_string_empty) ||
!string_is_equal(playlist->scan_record.content_dir, content_dir))
playlist->modified = true;
else
return; /* Strings are identical; do nothing */
if (playlist->scan_record.content_dir)
{
free(playlist->scan_record.content_dir);
playlist->scan_record.content_dir = NULL;
}
if (!new_string_empty)
playlist->scan_record.content_dir = strdup(content_dir);
}
void playlist_set_scan_file_exts(playlist_t *playlist, const char *file_exts)
{
bool current_string_empty;
bool new_string_empty;
if (!playlist)
return;
current_string_empty = string_is_empty(playlist->scan_record.file_exts);
new_string_empty = string_is_empty(file_exts);
/* Check whether string value has changed
* (note that a NULL or empty argument will
* unset the playlist value) */
if (( current_string_empty && !new_string_empty) ||
(!current_string_empty && new_string_empty) ||
!string_is_equal(playlist->scan_record.file_exts, file_exts))
playlist->modified = true;
else
return; /* Strings are identical; do nothing */
if (playlist->scan_record.file_exts)
{
free(playlist->scan_record.file_exts);
playlist->scan_record.file_exts = NULL;
}
if (!new_string_empty)
playlist->scan_record.file_exts = strdup(file_exts);
}
void playlist_set_scan_dat_file_path(playlist_t *playlist, const char *dat_file_path)
{
bool current_string_empty;
bool new_string_empty;
if (!playlist)
return;
current_string_empty = string_is_empty(playlist->scan_record.dat_file_path);
new_string_empty = string_is_empty(dat_file_path);
/* Check whether string value has changed
* (note that a NULL or empty argument will
* unset the playlist value) */
if (( current_string_empty && !new_string_empty) ||
(!current_string_empty && new_string_empty) ||
!string_is_equal(playlist->scan_record.dat_file_path, dat_file_path))
playlist->modified = true;
else
return; /* Strings are identical; do nothing */
if (playlist->scan_record.dat_file_path)
{
free(playlist->scan_record.dat_file_path);
playlist->scan_record.dat_file_path = NULL;
}
if (!new_string_empty)
playlist->scan_record.dat_file_path = strdup(dat_file_path);
}
void playlist_set_scan_search_recursively(playlist_t *playlist, bool search_recursively)
{
if (!playlist)
return;
if (playlist->scan_record.search_recursively != search_recursively)
{
playlist->scan_record.search_recursively = search_recursively;
playlist->modified = true;
}
}
void playlist_set_scan_search_archives(playlist_t *playlist, bool search_archives)
{
if (!playlist)
return;
if (playlist->scan_record.search_archives != search_archives)
{
playlist->scan_record.search_archives = search_archives;
playlist->modified = true;
}
}
void playlist_set_scan_filter_dat_content(playlist_t *playlist, bool filter_dat_content)
{
if (!playlist)
return;
if (playlist->scan_record.filter_dat_content != filter_dat_content)
{
playlist->scan_record.filter_dat_content = filter_dat_content;
playlist->modified = true;
}
}
/* Returns true if specified entry has a valid
* core association (i.e. a non-empty string
* other than DETECT) */

View File

@ -249,6 +249,20 @@ void playlist_delete_by_path(playlist_t *playlist,
void playlist_resolve_path(enum playlist_file_mode mode,
bool is_core, char *path, size_t len);
/**
* playlist_content_path_is_valid:
* @path : Content path
*
* Checks whether specified playlist content path
* refers to an existent file. Handles all playlist
* content path 'types' (i.e. can validate paths
* referencing files inside archives).
*
* Returns true if file referenced by content
* path exists on the host filesystem.
**/
bool playlist_content_path_is_valid(const char *path);
/**
* playlist_push:
* @playlist : Playlist handle.
@ -338,12 +352,19 @@ void playlist_get_crc32(playlist_t *playlist, size_t idx,
void playlist_get_db_name(playlist_t *playlist, size_t idx,
const char **db_name);
char *playlist_get_default_core_path(playlist_t *playlist);
char *playlist_get_default_core_name(playlist_t *playlist);
const char *playlist_get_default_core_path(playlist_t *playlist);
const char *playlist_get_default_core_name(playlist_t *playlist);
enum playlist_label_display_mode playlist_get_label_display_mode(playlist_t *playlist);
enum playlist_thumbnail_mode playlist_get_thumbnail_mode(
playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id);
enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist);
const char *playlist_get_scan_content_dir(playlist_t *playlist);
const char *playlist_get_scan_file_exts(playlist_t *playlist);
const char *playlist_get_scan_dat_file_path(playlist_t *playlist);
bool playlist_get_scan_search_recursively(playlist_t *playlist);
bool playlist_get_scan_search_archives(playlist_t *playlist);
bool playlist_get_scan_filter_dat_content(playlist_t *playlist);
bool playlist_scan_refresh_enabled(playlist_t *playlist);
void playlist_set_default_core_path(playlist_t *playlist, const char *core_path);
void playlist_set_default_core_name(playlist_t *playlist, const char *core_name);
@ -351,6 +372,12 @@ void playlist_set_label_display_mode(playlist_t *playlist, enum playlist_label_d
void playlist_set_thumbnail_mode(
playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id, enum playlist_thumbnail_mode thumbnail_mode);
void playlist_set_sort_mode(playlist_t *playlist, enum playlist_sort_mode sort_mode);
void playlist_set_scan_content_dir(playlist_t *playlist, const char *content_dir);
void playlist_set_scan_file_exts(playlist_t *playlist, const char *file_exts);
void playlist_set_scan_dat_file_path(playlist_t *playlist, const char *dat_file_path);
void playlist_set_scan_search_recursively(playlist_t *playlist, bool search_recursively);
void playlist_set_scan_search_archives(playlist_t *playlist, bool search_archives);
void playlist_set_scan_filter_dat_content(playlist_t *playlist, bool filter_dat_content);
/* Returns true if specified entry has a valid
* core association (i.e. a non-empty string

View File

@ -45,6 +45,7 @@
enum manual_scan_status
{
MANUAL_SCAN_BEGIN = 0,
MANUAL_SCAN_ITERATE_CLEAN,
MANUAL_SCAN_ITERATE_CONTENT,
MANUAL_SCAN_ITERATE_M3U,
MANUAL_SCAN_END
@ -54,12 +55,15 @@ typedef struct manual_scan_handle
{
manual_content_scan_task_config_t *task_config;
playlist_t *playlist;
struct string_list *file_exts_list;
struct string_list *content_list;
logiqx_dat_t *dat_file;
struct string_list *m3u_list;
playlist_config_t playlist_config; /* size_t alignment */
size_t list_size;
size_t list_index;
size_t playlist_size;
size_t playlist_index;
size_t content_list_size;
size_t content_list_index;
size_t m3u_index;
enum manual_scan_status status;
} manual_scan_handle_t;
@ -82,6 +86,12 @@ static void free_manual_content_scan_handle(manual_scan_handle_t *manual_scan)
manual_scan->playlist = NULL;
}
if (manual_scan->file_exts_list)
{
string_list_free(manual_scan->file_exts_list);
manual_scan->file_exts_list = NULL;
}
if (manual_scan->content_list)
{
string_list_free(manual_scan->content_list);
@ -199,6 +209,11 @@ static void task_manual_content_scan_handler(retro_task_t *task)
{
case MANUAL_SCAN_BEGIN:
{
/* Get allowed file extensions list */
if (!string_is_empty(manual_scan->task_config->file_exts))
manual_scan->file_exts_list = string_split(
manual_scan->task_config->file_exts, "|");
/* Get content list */
manual_scan->content_list = manual_content_scan_get_content_list(
manual_scan->task_config);
@ -212,7 +227,7 @@ static void task_manual_content_scan_handler(retro_task_t *task)
goto task_finished;
}
manual_scan->list_size = manual_scan->content_list->size;
manual_scan->content_list_size = manual_scan->content_list->size;
/* Load DAT file, if required */
if (!string_is_empty(manual_scan->task_config->dat_file_path))
@ -240,25 +255,118 @@ static void task_manual_content_scan_handler(retro_task_t *task)
if (manual_scan->task_config->overwrite_playlist)
playlist_clear(manual_scan->playlist);
/* Get initial playlist size */
manual_scan->playlist_size = playlist_size(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);
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;
/* Record remaining scan parameters to enable
* subsequent 'refresh playlist' operations */
playlist_set_scan_content_dir(manual_scan->playlist,
manual_scan->task_config->content_dir);
playlist_set_scan_file_exts(manual_scan->playlist,
manual_scan->task_config->file_exts_custom_set ?
manual_scan->task_config->file_exts : NULL);
playlist_set_scan_dat_file_path(manual_scan->playlist,
manual_scan->task_config->dat_file_path);
playlist_set_scan_search_recursively(manual_scan->playlist,
manual_scan->task_config->search_recursively);
playlist_set_scan_search_archives(manual_scan->playlist,
manual_scan->task_config->search_archives);
playlist_set_scan_filter_dat_content(manual_scan->playlist,
manual_scan->task_config->filter_dat_content);
/* All good - can start iterating
* > If playlist has content and 'validate
* entries' is enabled, go to clean-up phase
* > Otherwise go straight to content scan phase */
if (manual_scan->task_config->validate_entries &&
(manual_scan->playlist_size > 0))
manual_scan->status = MANUAL_SCAN_ITERATE_CLEAN;
else
manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT;
}
break;
case MANUAL_SCAN_ITERATE_CLEAN:
{
const struct playlist_entry *entry = NULL;
bool delete_entry = false;
/* Get current entry */
playlist_get_index(manual_scan->playlist,
manual_scan->playlist_index, &entry);
if (entry)
{
const char *entry_file = NULL;
const char *entry_file_ext = NULL;
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_PLAYLIST_CLEANUP),
sizeof(task_title));
if (!string_is_empty(entry->path) &&
(entry_file = path_basename(entry->path)))
strlcat(task_title, entry_file, sizeof(task_title));
task_set_title(task, strdup(task_title));
task_set_progress(task, (manual_scan->playlist_index * 100) /
manual_scan->playlist_size);
/* Check whether playlist content exists on
* the filesystem */
if (!playlist_content_path_is_valid(entry->path))
delete_entry = true;
/* If file exists, check whether it has a
* permitted file extension */
else if (manual_scan->file_exts_list &&
(entry_file_ext = path_get_extension(entry->path)) &&
!string_list_find_elem_prefix(
manual_scan->file_exts_list,
".", entry_file_ext))
delete_entry = true;
if (delete_entry)
{
/* Invalid content - delete entry */
playlist_delete_index(manual_scan->playlist,
manual_scan->playlist_index);
/* Update playlist_size */
manual_scan->playlist_size = playlist_size(manual_scan->playlist);
}
}
/* Increment entry index *if* current entry still
* exists (i.e. if entry was deleted, current index
* will already point to the *next* entry) */
if (!delete_entry)
manual_scan->playlist_index++;
if (manual_scan->playlist_index >=
manual_scan->playlist_size)
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;
int content_type =
manual_scan->content_list->elems[manual_scan->list_index].attr.i;
const char *content_path = manual_scan->content_list->elems[
manual_scan->content_list_index].data;
int content_type = manual_scan->content_list->elems[
manual_scan->content_list_index].attr.i;
if (!string_is_empty(content_path))
{
@ -270,15 +378,16 @@ static void task_manual_content_scan_handler(retro_task_t *task)
/* Update progress display */
task_free_title(task);
strlcpy(
task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS),
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);
task_set_progress(task, (manual_scan->content_list_index * 100) /
manual_scan->content_list_size);
/* Add content to playlist */
manual_content_scan_add_content_to_playlist(
@ -300,8 +409,9 @@ static void task_manual_content_scan_handler(retro_task_t *task)
}
/* Increment content index */
manual_scan->list_index++;
if (manual_scan->list_index >= manual_scan->list_size)
manual_scan->content_list_index++;
if (manual_scan->content_list_index >=
manual_scan->content_list_size)
{
/* Check whether we have any M3U files
* to process */
@ -314,8 +424,8 @@ static void task_manual_content_scan_handler(retro_task_t *task)
break;
case MANUAL_SCAN_ITERATE_M3U:
{
const char *m3u_path =
manual_scan->m3u_list->elems[manual_scan->m3u_index].data;
const char *m3u_path = manual_scan->m3u_list->elems[
manual_scan->m3u_index].data;
if (!string_is_empty(m3u_path))
{
@ -328,15 +438,16 @@ static void task_manual_content_scan_handler(retro_task_t *task)
/* Update progress display */
task_free_title(task);
strlcpy(
task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP),
strlcpy(task_title,
msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP),
sizeof(task_title));
if (!string_is_empty(m3u_name))
strlcat(task_title, m3u_name, sizeof(task_title));
task_set_title(task, strdup(task_title));
task_set_progress(task, (manual_scan->m3u_index * 100) / manual_scan->m3u_list->size);
task_set_progress(task, (manual_scan->m3u_index * 100) /
manual_scan->m3u_list->size);
/* Load M3U file */
m3u_file = m3u_file_init(m3u_path);
@ -445,10 +556,13 @@ bool task_push_manual_content_scan(
/* Configure handle */
manual_scan->task_config = NULL;
manual_scan->playlist = NULL;
manual_scan->file_exts_list = NULL;
manual_scan->content_list = NULL;
manual_scan->dat_file = NULL;
manual_scan->list_size = 0;
manual_scan->list_index = 0;
manual_scan->playlist_size = 0;
manual_scan->playlist_index = 0;
manual_scan->content_list_size = 0;
manual_scan->content_list_index = 0;
manual_scan->m3u_list = string_list_new();
manual_scan->m3u_index = 0;
manual_scan->status = MANUAL_SCAN_BEGIN;

View File

@ -23,7 +23,6 @@
#include <string/stdstring.h>
#include <lists/string_list.h>
#include <file/file_path.h>
#include <file/archive_file.h>
#include <formats/m3u_file.h>
#include "tasks_internal.h"
@ -368,78 +367,6 @@ error:
/* Clean Playlist */
/******************/
static bool pl_manager_content_exists(const char *path)
{
/* Sanity check */
if (string_is_empty(path))
return false;
/* If content is inside an archive, special
* handling is required... */
if (path_contains_compressed_file(path))
{
const char *delim = path_get_archive_delim(path);
char archive_path[PATH_MAX_LENGTH] = {0};
size_t len = 0;
struct string_list *archive_list = NULL;
const char *content_file = NULL;
bool content_found = false;
if (!delim)
return false;
/* Get path of 'parent' archive file */
len = (size_t)(1 + delim - path);
strlcpy(
archive_path, path,
(len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
/* Check if archive itself exists */
if (!path_is_valid(archive_path))
return false;
/* Check if file exists inside archive */
archive_list = file_archive_get_file_list(archive_path, NULL);
if (!archive_list)
return false;
/* > Get playlist entry content file name
* (sans archive file path) */
content_file = delim;
content_file++;
if (!string_is_empty(content_file))
{
size_t i;
/* > Loop over archive file contents */
for (i = 0; i < archive_list->size; i++)
{
const char *archive_file = archive_list->elems[i].data;
if (string_is_empty(archive_file))
continue;
if (string_is_equal(content_file, archive_file))
{
content_found = true;
break;
}
}
}
/* Clean up */
string_list_free(archive_list);
return content_found;
}
/* This is a 'normal' path - just check if
* it's valid */
else
return path_is_valid(path);
}
static void pl_manager_validate_core_association(
playlist_t *playlist, size_t entry_index,
const char *core_path, const char *core_name)
@ -562,7 +489,7 @@ static void task_pl_manager_clean_playlist_handler(retro_task_t *task)
{
/* Check whether playlist content exists on
* the filesystem */
if (!pl_manager_content_exists(entry->path))
if (!playlist_content_path_is_valid(entry->path))
{
/* Invalid content - delete entry */
playlist_delete_index(pl_manager->playlist, pl_manager->list_index);