Prevent duplicate content history entries (+ general sanitisation of playlist searching)

This commit is contained in:
jdgleaver 2019-05-02 09:56:04 +01:00
parent 7cc7a6df75
commit 05e0a6c8d2
10 changed files with 298 additions and 60 deletions

View file

@ -743,6 +743,8 @@ static const unsigned playlist_sublabel_runtime_type = PLAYLIST_RUNTIME_PER_CORE
static const bool playlist_show_sublabels = false;
static const bool playlist_fuzzy_archive_match = false;
/* Show Menu start-up screen on boot. */
static const bool default_menu_show_start_screen = true;

View file

@ -1601,6 +1601,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings,
SETTING_BOOL("content_runtime_log_aggregate", &settings->bools.content_runtime_log_aggregate, true, content_runtime_log_aggregate, false);
SETTING_BOOL("playlist_show_sublabels", &settings->bools.playlist_show_sublabels, true, playlist_show_sublabels, false);
SETTING_BOOL("playlist_sort_alphabetical", &settings->bools.playlist_sort_alphabetical, true, playlist_sort_alphabetical, false);
SETTING_BOOL("playlist_fuzzy_archive_match", &settings->bools.playlist_fuzzy_archive_match, true, playlist_fuzzy_archive_match, false);
SETTING_BOOL("quit_press_twice", &settings->bools.quit_press_twice, true, quit_press_twice, false);
SETTING_BOOL("vibrate_on_keypress", &settings->bools.vibrate_on_keypress, true, vibrate_on_keypress, false);

View file

@ -317,6 +317,7 @@ typedef struct settings
bool playlist_sort_alphabetical;
bool playlist_show_sublabels;
bool playlist_fuzzy_archive_match;
bool quit_press_twice;
bool vibrate_on_keypress;

View file

@ -1825,6 +1825,8 @@ MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_SORT_ALPHABETICAL,
"playlist_sort_alphabetical")
MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_SHOW_SUBLABELS,
"playlist_show_sublabels")
MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH,
"playlist_fuzzy_archive_match")
MSG_HASH(MENU_ENUM_LABEL_PLAYLIST_SUBLABEL_RUNTIME_TYPE,
"playlist_sublabel_runtime_type")
MSG_HASH(MENU_ENUM_LABEL_HELP_SEND_DEBUG_INFO,

View file

@ -8592,6 +8592,14 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_PLAYLIST_RUNTIME_PER_CORE,
"Per Core"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_PLAYLIST_FUZZY_ARCHIVE_MATCH,
"Fuzzy archive matching"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH,
"When searching playlists for entries associated with compressed files, match only the archive file name instead of [file name]+[content]. Enable this to avoid duplicate content history entries when loading compressed files."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_PLAYLIST_RUNTIME_AGGREGATE,
"Aggregate"

View file

@ -549,6 +549,7 @@ default_sublabel_macro(action_bind_sublabel_menu_ticker_type,
default_sublabel_macro(action_bind_sublabel_menu_ticker_speed, MENU_ENUM_SUBLABEL_MENU_TICKER_SPEED)
default_sublabel_macro(action_bind_sublabel_playlist_show_inline_core_name, MENU_ENUM_SUBLABEL_PLAYLIST_SHOW_INLINE_CORE_NAME)
default_sublabel_macro(action_bind_sublabel_playlist_sort_alphabetical, MENU_ENUM_SUBLABEL_PLAYLIST_SORT_ALPHABETICAL)
default_sublabel_macro(action_bind_sublabel_playlist_fuzzy_archive_match, MENU_ENUM_SUBLABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH)
default_sublabel_macro(action_bind_sublabel_menu_rgui_full_width_layout, MENU_ENUM_SUBLABEL_MENU_RGUI_FULL_WIDTH_LAYOUT)
default_sublabel_macro(action_bind_sublabel_menu_rgui_extended_ascii, MENU_ENUM_SUBLABEL_MENU_RGUI_EXTENDED_ASCII)
default_sublabel_macro(action_bind_sublabel_help_send_debug_info, MENU_ENUM_SUBLABEL_HELP_SEND_DEBUG_INFO)
@ -2494,6 +2495,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_PLAYLIST_SORT_ALPHABETICAL:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_sort_alphabetical);
break;
case MENU_ENUM_LABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_fuzzy_archive_match);
break;
case MENU_ENUM_LABEL_MENU_RGUI_FULL_WIDTH_LAYOUT:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_rgui_full_width_layout);
break;

View file

@ -5460,6 +5460,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type,
{MENU_ENUM_LABEL_PLAYLIST_SHOW_INLINE_CORE_NAME, PARSE_ONLY_UINT},
{MENU_ENUM_LABEL_PLAYLIST_SHOW_SUBLABELS, PARSE_ONLY_BOOL},
{MENU_ENUM_LABEL_PLAYLIST_SUBLABEL_RUNTIME_TYPE, PARSE_ONLY_UINT},
{MENU_ENUM_LABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH, PARSE_ONLY_BOOL},
};
for (i = 0; i < ARRAY_SIZE(build_list); i++)

View file

@ -12741,6 +12741,22 @@ static bool setting_append_list(
&setting_get_string_representation_uint_playlist_inline_core_display_type;
menu_settings_list_current_add_range(list, list_info, 0, PLAYLIST_INLINE_CORE_DISPLAY_LAST-1, 1, true, true);
CONFIG_BOOL(
list, list_info,
&settings->bools.playlist_fuzzy_archive_match,
MENU_ENUM_LABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH,
MENU_ENUM_LABEL_VALUE_PLAYLIST_FUZZY_ARCHIVE_MATCH,
playlist_fuzzy_archive_match,
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

@ -2322,6 +2322,7 @@ enum msg_hash_enums
MENU_LABEL(PLAYLIST_SHOW_INLINE_CORE_NAME),
MENU_LABEL(PLAYLIST_SORT_ALPHABETICAL),
MENU_LABEL(PLAYLIST_SHOW_SUBLABELS),
MENU_LABEL(PLAYLIST_FUZZY_ARCHIVE_MATCH),
MENU_LABEL(PLAYLIST_SUBLABEL_RUNTIME_TYPE),
MENU_ENUM_LABEL_VALUE_PLAYLIST_INLINE_CORE_DISPLAY_HIST_FAV,

View file

@ -74,6 +74,133 @@ typedef int (playlist_sort_fun_t)(
const struct playlist_entry *a,
const struct playlist_entry *b);
/**
* playlist_path_equal:
* @real_path : 'Real' search path, generated by path_resolve_realpath()
* @entry_path : Existing playlist entry 'path' value
*
* Returns 'true' if real_path matches entry_path
* (Taking into account relative paths, case insensitive
* filesystems, 'incomplete' archive paths)
**/
static bool playlist_path_equal(const char *real_path, const char *entry_path)
{
settings_t *settings = config_get_ptr();
bool real_path_is_compressed;
bool entry_real_path_is_compressed;
char entry_real_path[PATH_MAX_LENGTH];
entry_real_path[0] = '\0';
/* Sanity check */
if (string_is_empty(real_path) || string_is_empty(entry_path) || !settings)
return false;
/* Get entry 'real' path */
strlcpy(entry_real_path, entry_path, sizeof(entry_real_path));
path_resolve_realpath(entry_real_path, sizeof(entry_real_path));
if (string_is_empty(entry_real_path))
return false;
/* First pass comparison */
#ifdef _WIN32
/* Handle case-insensitive operating systems*/
if (string_is_equal_noncase(real_path, entry_real_path))
return true;
#else
if (string_is_equal(real_path, entry_real_path))
return true;
#endif
/* If fuzzy matching is disabled, we can give up now */
if (!settings->bools.playlist_fuzzy_archive_match)
return false;
/* If we reach this point, we have to work
* harder...
* Need to handle a rather awkward archive file
* case where:
* - playlist path contains a properly formatted
* [archive_path][delimiter][rom_file]
* - search path is just [archive_path]
* ...or vice versa.
* This pretty much always happens when a playlist
* is generated via scan content (which handles the
* archive paths correctly), but the user subsequently
* loads an archive file via the command line or some
* external launcher (where the [delimiter][rom_file]
* part is almost always omitted) */
real_path_is_compressed = path_is_compressed_file(real_path);
entry_real_path_is_compressed = path_is_compressed_file(entry_real_path);
if ((real_path_is_compressed && !entry_real_path_is_compressed) ||
(!real_path_is_compressed && entry_real_path_is_compressed))
{
const char *compressed_path_a = real_path_is_compressed ? real_path : entry_real_path;
const char *full_path = real_path_is_compressed ? entry_real_path : real_path;
const char *delim = path_get_archive_delim(full_path);
if (delim)
{
char compressed_path_b[PATH_MAX_LENGTH] = {0};
unsigned len = 1 + delim - full_path;
strlcpy(compressed_path_b, full_path,
(len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
#ifdef _WIN32
/* Handle case-insensitive operating systems*/
if (string_is_equal_noncase(compressed_path_a, compressed_path_b))
return true;
#else
if (string_is_equal(compressed_path_a, compressed_path_b))
return true;
#endif
}
}
return false;
}
/**
* playlist_core_path_equal:
* @real_core_path : 'Real' search path, generated by path_resolve_realpath()
* @entry_core_path : Existing playlist entry 'core path' value
*
* Returns 'true' if real_core_path matches entry_core_path
* (Taking into account relative paths, case insensitive
* filesystems)
**/
static bool playlist_core_path_equal(const char *real_core_path, const char *entry_core_path)
{
char entry_real_core_path[PATH_MAX_LENGTH];
entry_real_core_path[0] = '\0';
/* Sanity check */
if (string_is_empty(real_core_path) || string_is_empty(entry_core_path))
return false;
/* Get entry 'real' core path */
strlcpy(entry_real_core_path, entry_core_path, sizeof(entry_real_core_path));
path_resolve_realpath(entry_real_core_path, sizeof(entry_real_core_path));
if (string_is_empty(entry_real_core_path))
return false;
#ifdef _WIN32
/* Handle case-insensitive operating systems*/
if (string_is_equal_noncase(real_core_path, entry_real_core_path))
return true;
#else
if (string_is_equal(real_core_path, entry_real_core_path))
return true;
#endif
return false;
}
uint32_t playlist_get_size(playlist_t *playlist)
{
if (!playlist)
@ -168,13 +295,20 @@ void playlist_get_index_by_path(playlist_t *playlist,
const struct playlist_entry **entry)
{
size_t i;
char real_search_path[PATH_MAX_LENGTH];
if (!playlist || !entry)
real_search_path[0] = '\0';
if (!playlist || !entry || string_is_empty(search_path))
return;
/* Get 'real' search path */
strlcpy(real_search_path, search_path, sizeof(real_search_path));
path_resolve_realpath(real_search_path, sizeof(real_search_path));
for (i = 0; i < playlist->size; i++)
{
if (!string_is_equal(playlist->entries[i].path, search_path))
if (!playlist_path_equal(real_search_path, playlist->entries[i].path))
continue;
*entry = &playlist->entries[i];
@ -188,11 +322,19 @@ bool playlist_entry_exists(playlist_t *playlist,
const char *crc32)
{
size_t i;
if (!playlist)
char real_search_path[PATH_MAX_LENGTH];
real_search_path[0] = '\0';
if (!playlist || string_is_empty(path))
return false;
/* Get 'real' search path */
strlcpy(real_search_path, path, sizeof(real_search_path));
path_resolve_realpath(real_search_path, sizeof(real_search_path));
for (i = 0; i < playlist->size; i++)
if (string_is_equal(playlist->entries[i].path, path))
if (playlist_path_equal(real_search_path, playlist->entries[i].path))
return true;
return false;
@ -401,41 +543,52 @@ bool playlist_push_runtime(playlist_t *playlist,
unsigned last_played_hour, unsigned last_played_minute, unsigned last_played_second)
{
size_t i;
bool core_path_empty = string_is_empty(core_path);
char real_path[PATH_MAX_LENGTH];
char real_core_path[PATH_MAX_LENGTH];
if (core_path_empty)
{
RARCH_ERR("cannot push NULL or empty core name into the playlist.\n");
return false;
}
if (string_is_empty(path))
path = NULL;
real_path[0] = '\0';
real_core_path[0] = '\0';
if (!playlist)
return false;
if (string_is_empty(core_path))
{
RARCH_ERR("cannot push NULL or empty core path into the playlist.\n");
return false;
}
/* Get 'real' path */
if (!string_is_empty(path))
{
strlcpy(real_path, path, sizeof(real_path));
path_resolve_realpath(real_path, sizeof(real_path));
}
/* Get 'real' core path */
strlcpy(real_core_path, core_path, sizeof(real_core_path));
path_resolve_realpath(real_core_path, sizeof(real_core_path));
if (string_is_empty(real_core_path))
{
RARCH_ERR("cannot push NULL or empty core path into the playlist.\n");
return false;
}
for (i = 0; i < playlist->size; i++)
{
struct playlist_entry tmp;
const char *entry_path = playlist->entries[i].path;
bool equal_path =
(!path && !entry_path) ||
(path && entry_path &&
#ifdef _WIN32
/*prevent duplicates on case-insensitive operating systems*/
string_is_equal_noncase(path, entry_path)
#else
string_is_equal(path, entry_path)
#endif
);
bool equal_path =
(string_is_empty(real_path) && string_is_empty(entry_path)) ||
playlist_path_equal(real_path, entry_path);
/* Core name can have changed while still being the same core.
* Differentiate based on the core path only. */
if (!equal_path)
continue;
if (!string_is_equal(playlist->entries[i].core_path, core_path))
if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path))
continue;
/* If top entry, we don't want to push a new entry since
@ -469,10 +622,10 @@ bool playlist_push_runtime(playlist_t *playlist,
playlist->entries[0].path = NULL;
playlist->entries[0].core_path = NULL;
if (!string_is_empty(path))
playlist->entries[0].path = strdup(path);
if (!string_is_empty(core_path))
playlist->entries[0].core_path = strdup(core_path);
if (!string_is_empty(real_path))
playlist->entries[0].path = strdup(real_path);
if (!string_is_empty(real_core_path))
playlist->entries[0].core_path = strdup(real_core_path);
playlist->entries[0].runtime_hours = runtime_hours;
playlist->entries[0].runtime_minutes = runtime_minutes;
@ -503,53 +656,67 @@ bool playlist_push(playlist_t *playlist,
const struct playlist_entry *entry)
{
size_t i;
bool core_path_empty = string_is_empty(entry->core_path);
bool core_name_empty = string_is_empty(entry->core_name);
char real_path[PATH_MAX_LENGTH];
char real_core_path[PATH_MAX_LENGTH];
const char *core_name = entry->core_name;
const char *path = entry->path;
bool entry_updated = false;
if (core_path_empty || core_name_empty)
real_path[0] = '\0';
real_core_path[0] = '\0';
if (!playlist || !entry)
return false;
if (string_is_empty(entry->core_path))
{
if (core_name_empty && !core_path_empty)
{
static char base_path[255] = {0};
fill_pathname_base_noext(base_path, entry->core_path, sizeof(base_path));
core_name = base_path;
}
RARCH_ERR("cannot push NULL or empty core path into the playlist.\n");
return false;
}
if (core_path_empty || core_name_empty)
/* Get 'real' path */
if (!string_is_empty(entry->path))
{
strlcpy(real_path, entry->path, sizeof(real_path));
path_resolve_realpath(real_path, sizeof(real_path));
}
/* Get 'real' core path */
strlcpy(real_core_path, entry->core_path, sizeof(real_core_path));
path_resolve_realpath(real_core_path, sizeof(real_core_path));
if (string_is_empty(real_core_path))
{
RARCH_ERR("cannot push NULL or empty core path into the playlist.\n");
return false;
}
if (string_is_empty(core_name))
{
static char base_path[255] = {0};
fill_pathname_base_noext(base_path, real_core_path, sizeof(base_path));
core_name = base_path;
if (string_is_empty(core_name))
{
RARCH_ERR("cannot push NULL or empty core name into the playlist.\n");
return false;
}
}
if (string_is_empty(path))
path = NULL;
if (!playlist)
return false;
for (i = 0; i < playlist->size; i++)
{
struct playlist_entry tmp;
const char *entry_path = playlist->entries[i].path;
bool equal_path = (!path && !entry_path) ||
(path && entry_path &&
#ifdef _WIN32
/*prevent duplicates on case-insensitive operating systems*/
string_is_equal_noncase(path, entry_path)
#else
string_is_equal(path, entry_path)
#endif
);
bool equal_path =
(string_is_empty(real_path) && string_is_empty(entry_path)) ||
playlist_path_equal(real_path, entry_path);
/* Core name can have changed while still being the same core.
* Differentiate based on the core path only. */
if (!equal_path)
continue;
if (!string_is_equal(playlist->entries[i].core_path, entry->core_path))
if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path))
continue;
if ( !string_is_empty(entry->subsystem_ident)
@ -589,7 +756,17 @@ bool playlist_push(playlist_t *playlist,
for (j = 0; j < entry->subsystem_roms->size; j++)
{
if (!string_is_equal(entry->subsystem_roms->elems[j].data, roms->elems[j].data))
char real_rom_path[PATH_MAX_LENGTH];
real_rom_path[0] = '\0';
if (!string_is_empty(entry->subsystem_roms->elems[j].data))
{
strlcpy(real_rom_path, entry->subsystem_roms->elems[j].data, sizeof(real_rom_path));
path_resolve_realpath(real_rom_path, sizeof(real_rom_path));
}
if (!playlist_path_equal(real_rom_path, roms->elems[j].data))
{
unequal = true;
break;
@ -600,10 +777,35 @@ bool playlist_push(playlist_t *playlist,
continue;
}
/* If content was previously loaded via file browser
* or command line, certain entry values will be missing.
* If we are now loading the same content from a playlist,
* fill in any blanks */
if ((playlist->entries[i].label == NULL) && !string_is_empty(entry->label))
{
playlist->entries[i].label = strdup(entry->label);
entry_updated = true;
}
if ((playlist->entries[i].crc32 == NULL) && !string_is_empty(entry->crc32))
{
playlist->entries[i].crc32 = strdup(entry->crc32);
entry_updated = true;
}
if ((playlist->entries[i].db_name == NULL) && !string_is_empty(entry->db_name))
{
playlist->entries[i].db_name = strdup(entry->db_name);
entry_updated = true;
}
/* If top entry, we don't want to push a new entry since
* the top and the entry to be pushed are the same. */
if (i == 0)
{
if (entry_updated)
goto success;
return false;
}
/* Seen it before, bump to top. */
tmp = playlist->entries[i];
@ -646,12 +848,12 @@ bool playlist_push(playlist_t *playlist,
playlist->entries[0].last_played_hour = 0;
playlist->entries[0].last_played_minute = 0;
playlist->entries[0].last_played_second = 0;
if (!string_is_empty(entry->path))
playlist->entries[0].path = strdup(entry->path);
if (!string_is_empty(real_path))
playlist->entries[0].path = strdup(real_path);
if (!string_is_empty(entry->label))
playlist->entries[0].label = strdup(entry->label);
if (!string_is_empty(entry->core_path))
playlist->entries[0].core_path = strdup(entry->core_path);
if (!string_is_empty(real_core_path))
playlist->entries[0].core_path = strdup(real_core_path);
if (!string_is_empty(core_name))
playlist->entries[0].core_name = strdup(core_name);
if (!string_is_empty(entry->db_name))