diff --git a/Makefile.common b/Makefile.common index 5f10eaa0c3..41f9a7e01b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1760,6 +1760,7 @@ ifeq ($(HAVE_NETWORKING), 1) $(LIBRETRO_COMM_DIR)/net/net_http_parse.o \ $(LIBRETRO_COMM_DIR)/net/net_socket.o \ $(LIBRETRO_COMM_DIR)/net/net_natt.o \ + core_updater_list.o \ network/net_http_special.o \ tasks/task_http.o \ tasks/task_netplay_lan_scan.o \ @@ -1769,7 +1770,8 @@ ifeq ($(HAVE_NETWORKING), 1) ifeq ($(HAVE_MENU_COMMON), 1) OBJ += tasks/task_pl_thumbnail_download.o \ - menu/menu_networking.o + tasks/task_core_updater.o \ + menu/menu_networking.o endif ifeq ($(HAVE_SSL), 1) diff --git a/core_updater_list.c b/core_updater_list.c new file mode 100644 index 0000000000..34e3a7cd06 --- /dev/null +++ b/core_updater_list.c @@ -0,0 +1,778 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (core_updater_list.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 + +#include "configuration.h" +#include "file_path_special.h" +#include "core_info.h" + +#include "core_updater_list.h" + +/* Holds all entries in a core updater list */ +struct core_updater_list +{ + size_t size; + size_t capacity; + core_updater_list_entry_t *entries; +}; + +/* Cached ('global') core updater list */ +static core_updater_list_t *core_list_cached = NULL; + +/**************************************/ +/* Initialisation / De-Initialisation */ +/**************************************/ + +/* Frees contents of specified core updater + * list entry */ +static void core_updater_list_free_entry(core_updater_list_entry_t *entry) +{ + if (!entry) + return; + + if (entry->remote_filename) + { + free(entry->remote_filename); + entry->remote_filename = NULL; + } + + if (entry->remote_core_path) + { + free(entry->remote_core_path); + entry->remote_core_path = NULL; + } + + if (entry->local_core_path) + { + free(entry->local_core_path); + entry->local_core_path = NULL; + } + + if (entry->local_info_path) + { + free(entry->local_info_path); + entry->local_info_path = NULL; + } + + if (entry->display_name) + { + free(entry->display_name); + entry->display_name = NULL; + } +} + +/* Creates a new, empty core updater list. + * Returns a handle to a new core_updater_list_t object + * on success, otherwise returns NULL. */ +core_updater_list_t *core_updater_list_init(size_t max_size) +{ + core_updater_list_t *core_list = NULL; + core_updater_list_entry_t *entries = NULL; + + /* Sanity check */ + if (max_size < 1) + return NULL; + + /* Create core updater list */ + core_list = (core_updater_list_t*)calloc(1, sizeof(*core_list)); + + if (!core_list) + return NULL; + + /* Create entries array */ + entries = (core_updater_list_entry_t*)calloc(max_size, sizeof(*entries)); + + if (!entries) + { + free(core_list); + return NULL; + } + + /* Initial configuration */ + core_list->size = 0; + core_list->capacity = max_size; + core_list->entries = entries; + + return core_list; +} + +/* Resets (removes all entries of) specified core + * updater list */ +void core_updater_list_reset(core_updater_list_t *core_list) +{ + size_t i; + + if (!core_list) + return; + + for (i = 0; i < core_list->size; i++) + { + core_updater_list_entry_t *entry = &core_list->entries[i]; + + if (entry) + core_updater_list_free_entry(entry); + } + + core_list->size = 0; +} + +/* Frees specified core updater list */ +void core_updater_list_free(core_updater_list_t *core_list) +{ + size_t i; + + if (!core_list) + return; + + for (i = 0; i < core_list->size; i++) + { + core_updater_list_entry_t *entry = &core_list->entries[i]; + + if (entry) + core_updater_list_free_entry(entry); + } + + free(core_list->entries); + core_list->entries = NULL; + + free(core_list); +} + +/***************/ +/* Cached List */ +/***************/ + +/* Creates a new, empty cached core updater list + * (i.e. 'global' list). + * Returns false in the event of an error. */ +bool core_updater_list_init_cached(size_t max_size) +{ + /* Free any existing cached core updater list */ + if (core_list_cached) + { + core_updater_list_free(core_list_cached); + core_list_cached = NULL; + } + + core_list_cached = core_updater_list_init(max_size); + + if (!core_list_cached) + return false; + + return true; +} + +/* Fetches cached core updater list */ +core_updater_list_t *core_updater_list_get_cached(void) +{ + if (core_list_cached) + return core_list_cached; + + return NULL; +} + +/* Frees cached core updater list */ +void core_updater_list_free_cached(void) +{ + core_updater_list_free(core_list_cached); + core_list_cached = NULL; +} + +/***********/ +/* Getters */ +/***********/ + +/* Returns number of entries in core updater list */ +size_t core_updater_list_size(core_updater_list_t *core_list) +{ + if (!core_list) + return 0; + + return core_list->size; +} + +/* Returns maximum allowed number of entries in core + * updater list */ +size_t core_updater_list_capacity(core_updater_list_t *core_list) +{ + if (!core_list) + return 0; + + return core_list->capacity; +} + +/* Fetches core updater list entry corresponding + * to the specified entry index. + * Returns false if index is invalid. */ +bool core_updater_list_get_index( + core_updater_list_t *core_list, + size_t idx, + const core_updater_list_entry_t **entry) +{ + if (!core_list || !entry) + return false; + + if (idx >= core_list->size) + return false; + + if (entry) + *entry = &core_list->entries[idx]; + + return true; +} + +/* Fetches core updater list entry corresponding + * to the specified remote core filename. + * Returns false if core is not found. */ +bool core_updater_list_get_filename( + core_updater_list_t *core_list, + const char *remote_filename, + const core_updater_list_entry_t **entry) +{ + size_t i; + + if (!core_list || string_is_empty(remote_filename)) + return false; + + if (core_list->size < 1) + return false; + + /* Search for specified filename */ + for (i = 0; i < core_list->size; i++) + { + core_updater_list_entry_t *current_entry = &core_list->entries[i]; + + if (string_is_empty(current_entry->remote_filename)) + continue; + + if (string_is_equal(remote_filename, current_entry->remote_filename)) + { + if (entry) + *entry = current_entry; + + return true; + } + } + + return false; +} + +/* Fetches core updater list entry corresponding + * to the specified core. + * Returns false if core is not found. */ +bool core_updater_list_get_core( + core_updater_list_t *core_list, + const char *local_core_path, + const core_updater_list_entry_t **entry) +{ + size_t i; + char real_core_path[PATH_MAX_LENGTH]; + + real_core_path[0] = '\0'; + + if (!core_list || !entry || string_is_empty(local_core_path)) + return false; + + if (core_list->size < 1) + return false; + + /* Resolve absolute pathname of local_core_path */ + strlcpy(real_core_path, local_core_path, sizeof(real_core_path)); + path_resolve_realpath(real_core_path, sizeof(real_core_path), true); + + if (string_is_empty(real_core_path)) + return false; + + /* Search for specified core */ + for (i = 0; i < core_list->size; i++) + { + core_updater_list_entry_t *current_entry = &core_list->entries[i]; + + if (string_is_empty(current_entry->local_core_path)) + continue; + +#ifdef _WIN32 + /* Handle case-insensitive operating systems*/ + if (string_is_equal_noncase(real_core_path, current_entry->local_core_path)) + { +#else + if (string_is_equal(real_core_path, current_entry->local_core_path)) + { +#endif + if (entry) + *entry = current_entry; + + return true; + } + } + + return false; +} + +/***********/ +/* Setters */ +/***********/ + +/* Parses date string and adds contents to + * specified core updater list entry */ +static bool core_updater_list_set_date( + core_updater_list_entry_t *entry, const char *date_str) +{ + struct string_list *date_list = NULL; + + if (!entry || string_is_empty(date_str)) + goto error; + + /* Split date string into component values */ + date_list = string_split(date_str, "-"); + + if (!date_list) + goto error; + + /* Date string must have 3 values: + * [year] [month] [day] */ + if (date_list->size < 3) + goto error; + + /* Convert date string values */ + entry->date.year = string_to_unsigned(date_list->elems[0].data); + entry->date.month = string_to_unsigned(date_list->elems[1].data); + entry->date.day = string_to_unsigned(date_list->elems[2].data); + + /* Clean up */ + string_list_free(date_list); + + return true; + +error: + + if (date_list) + string_list_free(date_list); + + return false; +} + +/* Parses crc string and adds value to + * specified core updater list entry */ +static bool core_updater_list_set_crc( + core_updater_list_entry_t *entry, const char *crc_str) +{ + uint32_t crc; + + if (!entry || string_is_empty(crc_str)) + return false; + + crc = (uint32_t)string_hex_to_unsigned(crc_str); + + if (crc == 0) + return false; + + entry->crc = crc; + + return true; +} + +/* Parses core filename string and adds all + * associated paths to the specified core + * updater list entry */ +static bool core_updater_list_set_paths( + core_updater_list_entry_t *entry, const char *filename_str) +{ + settings_t *settings = config_get_ptr(); + char *last_underscore = NULL; + char *tmp_url = NULL; + char remote_core_path[PATH_MAX_LENGTH]; + char local_core_path[PATH_MAX_LENGTH]; + char local_info_path[PATH_MAX_LENGTH]; + char display_name[255]; + bool is_archive; + + remote_core_path[0] = '\0'; + local_core_path[0] = '\0'; + local_info_path[0] = '\0'; + display_name[0] = '\0'; + + if (!entry || string_is_empty(filename_str) || !settings) + return false; + + if (string_is_empty(settings->paths.directory_libretro) || + string_is_empty(settings->paths.path_libretro_info) || + string_is_empty(settings->paths.network_buildbot_url)) + return false; + + /* Check whether remote file is an archive */ + is_archive = path_is_compressed_file(filename_str); + + /* remote_filename */ + if (entry->remote_filename) + { + free(entry->remote_filename); + entry->remote_filename = NULL; + } + + entry->remote_filename = strdup(filename_str); + + /* remote_core_path */ + fill_pathname_join( + remote_core_path, + settings->paths.network_buildbot_url, + filename_str, + sizeof(remote_core_path)); + + /* > Apply proper URL encoding (messy...) */ + tmp_url = strdup(remote_core_path); + remote_core_path[0] = '\0'; + net_http_urlencode_full( + remote_core_path, tmp_url, sizeof(remote_core_path)); + if (tmp_url) + free(tmp_url); + + if (entry->remote_core_path) + { + free(entry->remote_core_path); + entry->remote_core_path = NULL; + } + + entry->remote_core_path = strdup(remote_core_path); + + /* local_core_path */ + fill_pathname_join( + local_core_path, + settings->paths.directory_libretro, + filename_str, + sizeof(local_core_path)); + + if (is_archive) + path_remove_extension(local_core_path); + + path_resolve_realpath(local_core_path, sizeof(local_core_path), true); + + if (entry->local_core_path) + { + free(entry->local_core_path); + entry->local_core_path = NULL; + } + + entry->local_core_path = strdup(local_core_path); + + /* local_info_path */ + fill_pathname_join_noext( + local_info_path, + settings->paths.path_libretro_info, + filename_str, + sizeof(local_info_path)); + + if (is_archive) + path_remove_extension(local_info_path); + + /* > Remove any non-standard core filename + * additions (i.e. info files end with + * '_libretro' but core files may have + * a platform specific addendum, + * e.g. '_android')*/ + last_underscore = (char*)strrchr(local_info_path, '_'); + + if (!string_is_empty(last_underscore)) + { + if (string_is_not_equal_fast(last_underscore, "_libretro", 9)) + *last_underscore = '\0'; + } + + /* > Add proper file extension */ + strlcat( + local_info_path, + file_path_str(FILE_PATH_CORE_INFO_EXTENSION), + sizeof(local_info_path)); + + if (entry->local_info_path) + { + free(entry->local_info_path); + entry->local_info_path = NULL; + } + + entry->local_info_path = strdup(local_info_path); + + /* display_name + * > Note: It's a bit rubbish that we have to + * read the actual core info files here... + * Would be better to cache this globally + * (at present, we only cache info for + * *installed* cores...) */ + if (path_is_valid(local_info_path)) + if (!core_info_get_display_name( + local_info_path, display_name, sizeof(display_name))) + display_name[0] = '\0'; + + /* > If info file does not exist, just use + * core filename */ + if (string_is_empty(display_name)) + strlcpy(display_name, filename_str, sizeof(display_name)); + + if (entry->display_name) + { + free(entry->display_name); + entry->display_name = NULL; + } + + entry->display_name = strdup(display_name); + + return true; +} + +/* Adds entry to the end of the specified core + * updater list + * NOTE: Entry string values are passed by + * reference - *do not free the entry passed + * to this function* */ +static bool core_updater_list_push_entry( + core_updater_list_t *core_list, core_updater_list_entry_t *entry) +{ + core_updater_list_entry_t *list_entry = NULL; + + if (!core_list || !entry) + return false; + + /* Ensure there is enough space for the new entry */ + if (core_list->capacity <= core_list->size) + return false; + + /* Get handle of new entry inside list */ + list_entry = &core_list->entries[core_list->size]; + + if (!list_entry) + return false; + + /* Ensure list entry is empty */ + core_updater_list_free_entry(list_entry); + + /* Assign paths */ + list_entry->remote_filename = entry->remote_filename; + list_entry->remote_core_path = entry->remote_core_path; + list_entry->local_core_path = entry->local_core_path; + list_entry->local_info_path = entry->local_info_path; + list_entry->display_name = entry->display_name; + + /* Copy crc */ + list_entry->crc = entry->crc; + + /* Copy date */ + memcpy(&list_entry->date, &entry->date, sizeof(core_updater_list_date_t)); + + /* Increment list size */ + core_list->size++; + + return true; +} + +/* Parses the contents of a single buildbot + * core listing and adds it to the specified + * core updater list */ +static void core_updater_list_add_entry( + core_updater_list_t *core_list, + struct string_list *network_core_entry_list) +{ + const char *date_str = NULL; + const char *crc_str = NULL; + const char *filename_str = NULL; + core_updater_list_entry_t entry = {0}; + + if (!core_list || !network_core_entry_list) + goto error; + + /* > Listings must have 3 entries: + * [date] [crc] [filename] */ + if (network_core_entry_list->size < 3) + goto error; + + /* Get handles of the individual listing strings */ + date_str = network_core_entry_list->elems[0].data; + crc_str = network_core_entry_list->elems[1].data; + filename_str = network_core_entry_list->elems[2].data; + + if (string_is_empty(date_str) || + string_is_empty(crc_str) || + string_is_empty(filename_str)) + goto error; + + /* Check whether core file is already included + * in the list (this is *not* an error condition, + * it just means we can skip the current listing) */ + if (core_updater_list_get_filename(core_list, filename_str, NULL)) + goto error; + + /* Parse individual listing strings */ + if (!core_updater_list_set_date(&entry, date_str)) + goto error; + + if (!core_updater_list_set_crc(&entry, crc_str)) + goto error; + + if (!core_updater_list_set_paths(&entry, filename_str)) + goto error; + + /* Add entry to list */ + if (!core_updater_list_push_entry(core_list, &entry)) + goto error; + + return; + +error: + /* This is not a *fatal* error - it just + * means one of the following: + * - The current line of entry text received + * from the buildbot is broken somehow + * (could be the case that the network buffer + * wasn't large enough to cache the entire + * string, so the last line was truncated) + * - The core updater list struct is at capacity + * In either case, the current entry is discarded + * and we move on to the next one + * (network transfers are fishy business, so we + * choose to ignore this sort of error - don't + * want the whole fetch to fail because of a + * trivial glitch...) */ + core_updater_list_free_entry(&entry); +} + +/* Core updater list qsort helper function */ +static int core_updater_list_qsort_func( + const core_updater_list_entry_t *a, const core_updater_list_entry_t *b) +{ + if (!a || !b) + return 0; + + if (string_is_empty(a->display_name) || string_is_empty(b->display_name)) + return 0; + + return strcasecmp(a->display_name, b->display_name); +} + +/* Sorts core updater list into alphabetical order */ +static void core_updater_list_qsort(core_updater_list_t *core_list) +{ + if (!core_list) + return; + + if (core_list->size < 2) + return; + + qsort( + core_list->entries, + core_list->size, + sizeof(core_updater_list_entry_t), + (int (*)(const void *, const void *)) + core_updater_list_qsort_func); +} + +/* Reads the contents of a buildbot core list + * network request into the specified + * core_updater_list_t object. + * Returns false in the event of an error. */ +bool core_updater_list_parse_network_data( + core_updater_list_t *core_list, const char *data, size_t len) +{ + struct string_list *network_core_list = NULL; + struct string_list *network_core_entry_list = NULL; + char *data_buf = NULL; + size_t i; + + /* Sanity check */ + if (!core_list || string_is_empty(data) || (len < 1)) + goto error; + + /* We're populating a list 'from scratch' - remove + * any existing entries */ + core_updater_list_reset(core_list); + + /* Input data string is not terminated - have + * to copy it to a temporary buffer... */ + data_buf = (char*)malloc((len + 1) * sizeof(char)); + + if (!data_buf) + goto error; + + memcpy(data_buf, data, len * sizeof(char)); + data_buf[len] = '\0'; + + /* Split network listing request into lines */ + network_core_list = string_split(data_buf, "\n"); + + if (!network_core_list) + goto error; + + if (network_core_list->size < 1) + goto error; + + /* Temporary data buffer is no longer required */ + free(data_buf); + + /* Loop over lines */ + for (i = 0; i < network_core_list->size; i++) + { + const char *line = network_core_list->elems[i].data; + + if (string_is_empty(line)) + continue; + + /* Split line into listings info components */ + network_core_entry_list = string_split(line, " "); + + /* Parse listings info and add to core updater + * list */ + core_updater_list_add_entry( + core_list, network_core_entry_list); + + /* Clean up */ + string_list_free(network_core_entry_list); + network_core_entry_list = NULL; + } + + /* Sanity check */ + if (core_list->size < 1) + goto error; + + /* Clean up */ + string_list_free(network_core_list); + + /* Sort completed list */ + core_updater_list_qsort(core_list); + + return true; + +error: + + if (network_core_list) + string_list_free(network_core_list); + + if (network_core_entry_list) + string_list_free(network_core_entry_list); + + if (data_buf) + free(data_buf); + + return false; +} diff --git a/core_updater_list.h b/core_updater_list.h new file mode 100644 index 0000000000..3d1dae807e --- /dev/null +++ b/core_updater_list.h @@ -0,0 +1,143 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (core_updater_list.h). + * --------------------------------------------------------------------------------------- + * + * 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 __CORE_UPDATER_LIST_H +#define __CORE_UPDATER_LIST_H + +#include +#include + +#include + +RETRO_BEGIN_DECLS + +/* Default maximum number of entries in + * a core updater list */ +#define CORE_UPDATER_LIST_SIZE 9999 + +/* Holds all date info for a core file + * on the buildbot */ +typedef struct +{ + unsigned year; + unsigned month; + unsigned day; +} core_updater_list_date_t; + +/* Holds all info related to a core + * file on the buildbot */ +typedef struct +{ + char *remote_filename; + char *remote_core_path; + char *local_core_path; + char *local_info_path; + char *display_name; + uint32_t crc; + core_updater_list_date_t date; +} core_updater_list_entry_t; + +/* Prevent direct access to core_updater_list_t + * members */ +typedef struct core_updater_list core_updater_list_t; + +/**************************************/ +/* Initialisation / De-Initialisation */ +/**************************************/ + +/* Creates a new, empty core updater list with a + * maximum number of 'max_size' entries. + * Returns a handle to a new core_updater_list_t object + * on success, otherwise returns NULL. */ +core_updater_list_t *core_updater_list_init(size_t max_size); + +/* Resets (removes all entries of) specified core + * updater list */ +void core_updater_list_reset(core_updater_list_t *core_list); + +/* Frees specified core updater list */ +void core_updater_list_free(core_updater_list_t *core_list); + +/***************/ +/* Cached List */ +/***************/ + +/* Creates a new, empty cached core updater list + * (i.e. 'global' list). + * Returns false in the event of an error. */ +bool core_updater_list_init_cached(size_t max_size); + +/* Fetches cached core updater list */ +core_updater_list_t *core_updater_list_get_cached(void); + +/* Frees cached core updater list */ +void core_updater_list_free_cached(void); + +/***********/ +/* Getters */ +/***********/ + +/* Returns number of entries in core updater list */ +size_t core_updater_list_size(core_updater_list_t *core_list); + +/* Returns maximum allowed number of entries in core + * updater list */ +size_t core_updater_list_capacity(core_updater_list_t *core_list); + +/* Fetches core updater list entry corresponding + * to the specified entry index. + * Returns false if index is invalid. */ +bool core_updater_list_get_index( + core_updater_list_t *core_list, + size_t idx, + const core_updater_list_entry_t **entry); + +/* Fetches core updater list entry corresponding + * to the specified remote core filename. + * Returns false if core is not found. */ +bool core_updater_list_get_filename( + core_updater_list_t *core_list, + const char *remote_filename, + const core_updater_list_entry_t **entry); + +/* Fetches core updater list entry corresponding + * to the specified core. + * Returns false if core is not found. */ +bool core_updater_list_get_core( + core_updater_list_t *core_list, + const char *local_core_path, + const core_updater_list_entry_t **entry); + +/***********/ +/* Setters */ +/***********/ + +/* Reads the contents of a buildbot core list + * network request into the specified + * core_updater_list_t object. + * Returns false in the event of an error. */ +bool core_updater_list_parse_network_data( + core_updater_list_t *core_list, const char *data, size_t len); + +RETRO_END_DECLS + +#endif diff --git a/griffin/griffin.c b/griffin/griffin.c index 2b2af0d0df..a28503296e 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1084,6 +1084,10 @@ FRONTEND #include "../core_info.c" +#if defined(HAVE_NETWORKING) +#include "../core_updater_list.c" +#endif + /*============================================================ UI ============================================================ */ @@ -1235,6 +1239,7 @@ DATA RUNLOOP #endif #if defined(HAVE_NETWORKING) && defined(HAVE_MENU) #include "../tasks/task_pl_thumbnail_download.c" +#include "../tasks/task_core_updater.c" #endif /*============================================================ diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index e90df7d9a9..51fb5b31c1 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -487,6 +487,8 @@ MSG_HASH(MENU_ENUM_LABEL_DOWNLOAD_CORE_CONTENT, "download_core_content") MSG_HASH(MENU_ENUM_LABEL_DOWNLOAD_CORE_CONTENT_DIRS, "download_core_content_dirs") +MSG_HASH(MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES, + "update_installed_cores") MSG_HASH(MENU_ENUM_LABEL_CONTENT_DIR, "content_directory") MSG_HASH(MENU_ENUM_LABEL_MENU_SCALE_FACTOR, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index a3cceb374b..143e9aa6b6 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -832,6 +832,54 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_DOWNLOAD_CORE_CONTENT, "Content Downloader" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_UPDATE_INSTALLED_CORES, + "Update Installed Cores" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_UPDATE_INSTALLED_CORES, + "Update all installed cores to the latest version available." + ) +MSG_HASH( + MSG_FETCHING_CORE_LIST, + "Fetching core list..." + ) +MSG_HASH( + MSG_CORE_LIST_FAILED, + "Failed to retrieve core list!" + ) +MSG_HASH( + MSG_LATEST_CORE_INSTALLED, + "Latest version already installed: " + ) +MSG_HASH( + MSG_UPDATING_CORE, + "Updating core: " + ) +MSG_HASH( + MSG_DOWNLOADING_CORE, + "Downloading core: " + ) +MSG_HASH( + MSG_EXTRACTING_CORE, + "Extracting core: " + ) +MSG_HASH( + MSG_CORE_INSTALLED, + "Core installed: " + ) +MSG_HASH( + MSG_SCANNING_CORES, + "Scanning cores..." + ) +MSG_HASH( + MSG_CHECKING_CORE, + "Checking core: " + ) +MSG_HASH( + MSG_ALL_CORES_UPDATED, + "All installed cores at latest version" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_MENU_SCALE_FACTOR, "Menu Scale Factor" diff --git a/libretro-common/include/string/stdstring.h b/libretro-common/include/string/stdstring.h index 29e3428259..d57256a720 100644 --- a/libretro-common/include/string/stdstring.h +++ b/libretro-common/include/string/stdstring.h @@ -141,6 +141,11 @@ void string_replace_all_chars(char *str, char find, char replace); * Returns 0 if string is invalid */ unsigned string_to_unsigned(const char *str); +/* Converts hexadecimal string to unsigned integer. + * Handles optional leading '0x'. + * Returns 0 if string is invalid */ +unsigned string_hex_to_unsigned(const char *str); + RETRO_END_DECLS #endif diff --git a/libretro-common/string/stdstring.c b/libretro-common/string/stdstring.c index 507aa9fafd..e2afef14d5 100644 --- a/libretro-common/string/stdstring.c +++ b/libretro-common/string/stdstring.c @@ -349,3 +349,36 @@ unsigned string_to_unsigned(const char *str) return (unsigned)strtoul(str, NULL, 10); } + +/* Converts hexadecimal string to unsigned integer. + * Handles optional leading '0x'. + * Returns 0 if string is invalid */ +unsigned string_hex_to_unsigned(const char *str) +{ + const char *hex_str = str; + const char *ptr = NULL; + size_t len; + + if (string_is_empty(str)) + return 0; + + /* Remove leading '0x', if required */ + len = strlen(str); + + if (len >= 2) + if ((str[0] == '0') && + ((str[1] == 'x') || (str[1] == 'X'))) + hex_str = str + 2; + + if (string_is_empty(hex_str)) + return 0; + + /* Check for valid characters */ + for (ptr = hex_str; *ptr != '\0'; ptr++) + { + if (!isxdigit(*ptr)) + return 0; + } + + return (unsigned)strtoul(hex_str, NULL, 16); +} diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index e5ab140b98..2878db9b6c 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -3797,12 +3797,11 @@ static void cb_decompressed(retro_task_t *task, switch (type_hash) { - case CB_CORE_UPDATER_DOWNLOAD: - generic_action_ok_command(CMD_EVENT_CORE_INFO_INIT); - break; case CB_UPDATE_ASSETS: generic_action_ok_command(CMD_EVENT_REINIT); break; + default: + break; } } @@ -3820,6 +3819,37 @@ static void cb_decompressed(retro_task_t *task, } #endif +static int action_ok_core_updater_list(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + core_updater_list_t *core_list = NULL; + bool refresh = true; + + /* Get cached core updater list, initialising + * it if required */ + core_list = core_updater_list_get_cached(); + + if (!core_list) + { + core_updater_list_init_cached(CORE_UPDATER_LIST_SIZE); + core_list = core_updater_list_get_cached(); + + if (!core_list) + return menu_cbs_exit(); + } + + /* Initial setup... */ + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + generic_action_ok_command(CMD_EVENT_NETWORK_INIT); + + /* Push core list update task */ + task_push_get_core_updater_list(core_list, false, true); + + return generic_action_ok_displaylist_push( + path, NULL, label, type, idx, entry_idx, + ACTION_OK_DL_CORE_UPDATER_LIST); +} + static int generic_action_ok_network(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx, enum msg_hash_enums enum_idx) @@ -3860,17 +3890,6 @@ static int generic_action_ok_network(const char *path, callback = cb_net_generic; suppress_msg = true; break; - case MENU_ENUM_LABEL_CB_CORE_UPDATER_LIST: - - if (string_is_empty(settings->paths.network_buildbot_url)) - return menu_cbs_exit(); - - fill_pathname_join(url_path, settings->paths.network_buildbot_url, - ".index-extended", sizeof(url_path)); - url_label = msg_hash_to_str(enum_idx); - type_id2 = ACTION_OK_DL_CORE_UPDATER_LIST; - callback = cb_net_generic; - break; case MENU_ENUM_LABEL_CB_THUMBNAILS_UPDATER_LIST: fill_pathname_join(url_path, "http://thumbnailpacks.libretro.com", @@ -3919,7 +3938,6 @@ static int (funcname)(const char *path, const char *label, unsigned type, size_t default_action_ok_list(action_ok_core_content_list, MENU_ENUM_LABEL_CB_CORE_CONTENT_LIST) default_action_ok_list(action_ok_core_content_dirs_list, MENU_ENUM_LABEL_CB_CORE_CONTENT_DIRS_LIST) -default_action_ok_list(action_ok_core_updater_list, MENU_ENUM_LABEL_CB_CORE_UPDATER_LIST) default_action_ok_list(action_ok_thumbnails_updater_list, MENU_ENUM_LABEL_CB_THUMBNAILS_UPDATER_LIST) default_action_ok_list(action_ok_lakka_list, MENU_ENUM_LABEL_CB_LAKKA_LIST) @@ -3952,7 +3970,7 @@ void cb_generic_download(retro_task_t *task, settings_t *settings = config_get_ptr(); http_transfer_data_t *data = (http_transfer_data_t*)task_data; - if (!data || !data->data | !transf) + if (!data || !data->data || !transf) goto finish; output_path[0] = '\0'; @@ -3965,9 +3983,6 @@ void cb_generic_download(retro_task_t *task, case MENU_ENUM_LABEL_CB_CORE_THUMBNAILS_DOWNLOAD: dir_path = settings->paths.directory_thumbnails; break; - case MENU_ENUM_LABEL_CB_CORE_UPDATER_DOWNLOAD: - dir_path = settings->paths.directory_libretro; - break; case MENU_ENUM_LABEL_CB_CORE_CONTENT_DOWNLOAD: dir_path = settings->paths.directory_core_assets; #if defined(HAVE_COMPRESSION) && defined(HAVE_ZLIB) @@ -4094,28 +4109,23 @@ void cb_generic_download(retro_task_t *task, if (path_is_compressed_file(output_path)) { - void *frontend_userdata = task->frontend_userdata; - task->frontend_userdata = NULL; + retro_task_t *decompress_task = NULL; + void *frontend_userdata = task->frontend_userdata; + task->frontend_userdata = NULL; - if (!task_push_decompress(output_path, dir_path, - NULL, NULL, NULL, - cb_decompressed, (void*)(uintptr_t) - msg_hash_calculate(msg_hash_to_str(transf->enum_idx)), - frontend_userdata)) + decompress_task = (retro_task_t*)task_push_decompress( + output_path, dir_path, + NULL, NULL, NULL, + cb_decompressed, (void*)(uintptr_t) + msg_hash_calculate(msg_hash_to_str(transf->enum_idx)), + frontend_userdata, false); + + if (!decompress_task) { err = msg_hash_to_str(MSG_DECOMPRESSION_FAILED); goto finish; } } -#else - switch (transf->enum_idx) - { - case MENU_ENUM_LABEL_CB_CORE_UPDATER_DOWNLOAD: - generic_action_ok_command(CMD_EVENT_CORE_INFO_INIT); - break; - default: - break; - } #endif finish: @@ -4249,6 +4259,36 @@ static int action_ok_core_content_download(const char *path, MENU_ENUM_LABEL_CB_CORE_CONTENT_DOWNLOAD); } +static int action_ok_core_updater_download(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ +#ifdef HAVE_NETWORKING + core_updater_list_t *core_list = core_updater_list_get_cached(); + + if (!core_list) + return menu_cbs_exit(); + + task_push_core_updater_download( + core_list, path, false, true); + +#endif + return 0; +} + +static int action_ok_update_installed_cores(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ +#ifdef HAVE_NETWORKING + /* Ensure networking is initialised */ + generic_action_ok_command(CMD_EVENT_NETWORK_INIT); + + /* Push update task */ + task_push_update_installed_cores(); + +#endif + return 0; +} + #define default_action_ok_download(funcname, _id) \ static int (funcname)(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) \ { \ @@ -4258,7 +4298,6 @@ static int (funcname)(const char *path, const char *label, unsigned type, size_t default_action_ok_download(action_ok_core_content_thumbnails, MENU_ENUM_LABEL_CB_CORE_THUMBNAILS_DOWNLOAD) default_action_ok_download(action_ok_thumbnails_updater_download, MENU_ENUM_LABEL_CB_THUMBNAILS_UPDATER_DOWNLOAD) default_action_ok_download(action_ok_download_url, MENU_ENUM_LABEL_CB_DOWNLOAD_URL) -default_action_ok_download(action_ok_core_updater_download, MENU_ENUM_LABEL_CB_CORE_UPDATER_DOWNLOAD) default_action_ok_download(action_ok_lakka_download, MENU_ENUM_LABEL_CB_LAKKA_DOWNLOAD) default_action_ok_download(action_ok_update_assets, MENU_ENUM_LABEL_CB_UPDATE_ASSETS) default_action_ok_download(action_ok_update_core_info_files, MENU_ENUM_LABEL_CB_UPDATE_CORE_INFO_FILES) @@ -6506,6 +6545,9 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CORE_UPDATER_LIST: BIND_ACTION_OK(cbs, action_ok_core_updater_list); break; + case MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES: + BIND_ACTION_OK(cbs, action_ok_update_installed_cores); + break; case MENU_ENUM_LABEL_THUMBNAILS_UPDATER_LIST: BIND_ACTION_OK(cbs, action_ok_thumbnails_updater_list); break; diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 3b6f1a3886..22e156a12e 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -290,6 +290,7 @@ default_sublabel_macro(action_bind_sublabel_video_post_filter_record, MENU_ default_sublabel_macro(action_bind_sublabel_start_core, MENU_ENUM_SUBLABEL_START_CORE) default_sublabel_macro(action_bind_sublabel_core_list, MENU_ENUM_SUBLABEL_CORE_LIST) default_sublabel_macro(action_bind_sublabel_download_core, MENU_ENUM_SUBLABEL_DOWNLOAD_CORE) +default_sublabel_macro(action_bind_sublabel_update_installed_cores, MENU_ENUM_SUBLABEL_UPDATE_INSTALLED_CORES) default_sublabel_macro(action_bind_sublabel_sideload_core_list, MENU_ENUM_SUBLABEL_SIDELOAD_CORE_LIST) default_sublabel_macro(action_bind_sublabel_load_disc, MENU_ENUM_SUBLABEL_LOAD_DISC) default_sublabel_macro(action_bind_sublabel_dump_disc, MENU_ENUM_SUBLABEL_DUMP_DISC) @@ -2423,6 +2424,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CORE_UPDATER_LIST: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_download_core); break; + case MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_update_installed_cores); + break; case MENU_ENUM_LABEL_VIDEO_POST_FILTER_RECORD: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_post_filter_record); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 75e3264100..f0491e190d 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -7615,6 +7615,7 @@ static void materialui_list_insert( else if ( string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_ONLINE_UPDATER)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_CORE_INFO_FILES)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_AUTOCONFIG_PROFILES)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_ASSETS)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_CHEATS)) || diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c index 6eac3a3cb8..e4a88733cd 100644 --- a/menu/drivers/ozone/ozone_texture.c +++ b/menu/drivers/ozone/ozone_texture.c @@ -112,6 +112,7 @@ menu_texture_item ozone_entries_icon_get_texture(ozone_handle_t *ozone, case MENU_ENUM_LABEL_SIDELOAD_CORE_LIST: case MENU_ENUM_LABEL_CORE_SETTINGS: case MENU_ENUM_LABEL_CORE_UPDATER_LIST: + case MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_CORE: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CORE: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 89690d723a..2dea7ab9d6 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2388,6 +2388,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, case MENU_ENUM_LABEL_SIDELOAD_CORE_LIST: case MENU_ENUM_LABEL_CORE_SETTINGS: case MENU_ENUM_LABEL_CORE_UPDATER_LIST: + case MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_CORE: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CORE: diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index b816be040a..b1c5f0d56b 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -46,6 +46,7 @@ #include #include "../../network/netplay/netplay.h" #include "../network/netplay/netplay_discovery.h" +#include "../core_updater_list.h" #endif #ifdef HAVE_LAKKA_SWITCH @@ -7922,9 +7923,35 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_CORES_UPDATER: menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); #ifdef HAVE_NETWORKING - count = print_buf_lines(info->list, menu->core_buf, "", - (int)menu->core_len, FILE_TYPE_DOWNLOAD_CORE, true, true); + { + core_updater_list_t *core_list = core_updater_list_get_cached(); + if (core_list) + { + size_t i; + + for (i = 0; i < core_updater_list_size(core_list); i++) + { + const core_updater_list_entry_t *entry = NULL; + + if (core_updater_list_get_index(core_list, i, &entry)) + { + if (menu_entries_append_enum(info->list, + entry->remote_filename, + "", + MENU_ENUM_LABEL_URL_ENTRY, + FILE_TYPE_DOWNLOAD_CORE, 0, 0)) + { + file_list_set_alt_at_offset( + info->list, i, entry->display_name); + + count++; + } + } + } + } + } +#endif if (count == 0) menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_ENTRIES_TO_DISPLAY), @@ -7935,7 +7962,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, info->need_push = true; info->need_refresh = true; info->need_clear = true; -#endif + break; case DISPLAYLIST_THUMBNAILS_UPDATER: #ifdef HAVE_NETWORKING @@ -8806,6 +8833,13 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, MENU_ENUM_LABEL_CORE_UPDATER_LIST, MENU_SETTING_ACTION, 0, 0)) count++; + + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UPDATE_INSTALLED_CORES), + msg_hash_to_str(MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES), + MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES, + MENU_SETTING_ACTION, 0, 0)) + count++; } #endif #endif diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 9d621fec71..0c94237d02 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -67,6 +67,7 @@ #include "../ui/ui_companion_driver.h" #include "../verbosity.h" #include "../tasks/task_powerstate.h" +#include "../core_updater_list.h" #define SCROLL_INDEX_SIZE (2 * (26 + 2) + 1) @@ -3277,7 +3278,7 @@ static bool menu_init(menu_handle_t *menu_data) task_push_decompress(settings->arrays.bundle_assets_src, settings->arrays.bundle_assets_dst, NULL, settings->arrays.bundle_assets_dst_subdir, - NULL, bundle_decompressed, NULL, NULL); + NULL, bundle_decompressed, NULL, NULL, false); #endif } @@ -3596,6 +3597,7 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) menu_shader_manager_free(); #endif + core_updater_list_free_cached(); if (menu_driver_data) { diff --git a/menu/menu_networking.c b/menu/menu_networking.c index 4d33639b60..b248d86054 100644 --- a/menu/menu_networking.c +++ b/menu/menu_networking.c @@ -47,7 +47,7 @@ unsigned print_buf_lines(file_list_t *list, char *buf, { char c; unsigned count = 0; - int i, j = 0; + int i = 0; char *line_start = buf; if (!buf || !buf_size) @@ -123,55 +123,6 @@ unsigned print_buf_lines(file_list_t *list, char *buf, } } - switch (type) - { - case FILE_TYPE_DOWNLOAD_CORE: - { - settings_t *settings = config_get_ptr(); - - if (settings) - { - char display_name[255]; - char core_path[PATH_MAX_LENGTH]; - char *last = NULL; - - display_name[0] = core_path[0] = '\0'; - - fill_pathname_join_noext( - core_path, - settings->paths.path_libretro_info, - (extended && !string_is_empty(core_pathname)) - ? core_pathname : line_start, - sizeof(core_path)); - path_remove_extension(core_path); - - last = (char*)strrchr(core_path, '_'); - - if (!string_is_empty(last)) - { - if (string_is_not_equal_fast(last, "_libretro", 9)) - *last = '\0'; - } - - strlcat(core_path, - file_path_str(FILE_PATH_CORE_INFO_EXTENSION), - sizeof(core_path)); - - if ( - path_is_valid(core_path) - && core_info_get_display_name( - core_path, display_name, sizeof(display_name))) - file_list_set_alt_at_offset(list, j, display_name); - } - } - break; - default: - case FILE_TYPE_NONE: - break; - } - - j++; - string_list_free(str_list); /* Restore the saved char */ diff --git a/menu/widgets/menu_widgets.c b/menu/widgets/menu_widgets.c index 86ab60769e..efe0a6c1b4 100644 --- a/menu/widgets/menu_widgets.c +++ b/menu/widgets/menu_widgets.c @@ -384,6 +384,7 @@ void menu_widgets_msg_queue_push( if (task) { msg_widget->msg = strdup(title); + msg_widget->msg_new = strdup(title); msg_widget->msg_len = (unsigned)strlen(title); msg_widget->task_error = task->error; @@ -449,7 +450,7 @@ void menu_widgets_msg_queue_push( msg_widget->expiration_timer_started = false; } - if (task->title != msg_widget->task_title_ptr) + if (!string_is_equal(task->title, msg_widget->msg_new)) { unsigned len = (unsigned)strlen(task->title); unsigned new_width = font_driver_get_message_width(font_regular, task->title, len, msg_queue_text_scale_factor); @@ -609,6 +610,9 @@ static void menu_widgets_msg_queue_free(menu_widget_msg_t *msg, bool touch_list) if (msg->msg) free(msg->msg); + if (msg->msg_new) + free(msg->msg_new); + /* Remove it from the list */ if (touch_list) { diff --git a/msg_hash.h b/msg_hash.h index c5f51b6a64..7412b57ae7 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1882,6 +1882,21 @@ enum msg_hash_enums MENU_LABEL(DISC_INFORMATION), MENU_LABEL(CORE_DELETE), + /* Core updater */ + MENU_LABEL(UPDATE_INSTALLED_CORES), + + MSG_FETCHING_CORE_LIST, + MSG_CORE_LIST_FAILED, + MSG_LATEST_CORE_INSTALLED, + MSG_UPDATING_CORE, + MSG_DOWNLOADING_CORE, + MSG_EXTRACTING_CORE, + MSG_CORE_INSTALLED, + MSG_SCANNING_CORES, + MSG_CHECKING_CORE, + MSG_ALL_CORES_UPDATED, + + MENU_LABEL(VIDEO_SHADER_PARAMETERS), MENU_LABEL(VIDEO_SHADER_PRESET_PARAMETERS), MENU_LABEL(DISK_OPTIONS), diff --git a/tasks/task_core_updater.c b/tasks/task_core_updater.c new file mode 100644 index 0000000000..397101c0b5 --- /dev/null +++ b/tasks/task_core_updater.c @@ -0,0 +1,1199 @@ +/* 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 + * Copyright (C) 2019 - James Leaver + * + * 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 +#include + +#include "task_file_transfer.h" +#include "tasks_internal.h" + +#include "../configuration.h" +#include "../retroarch.h" +#include "../command.h" +#include "../msg_hash.h" +#include "../verbosity.h" +#include "../core_updater_list.h" + +#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) +#include "../menu/menu_entries.h" +#endif + +/* Get core updater list */ +enum core_updater_list_status +{ + CORE_UPDATER_LIST_BEGIN = 0, + CORE_UPDATER_LIST_WAIT, + CORE_UPDATER_LIST_END +}; + +typedef struct core_updater_list_handle +{ + core_updater_list_t* core_list; + bool refresh_menu; + retro_task_t *http_task; + bool http_task_finished; + bool http_task_complete; + http_transfer_data_t *http_data; + enum core_updater_list_status status; +} core_updater_list_handle_t; + +/* Download core */ +enum core_updater_download_status +{ + CORE_UPDATER_DOWNLOAD_BEGIN = 0, + CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER, + CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS, + CORE_UPDATER_DOWNLOAD_END +}; + +typedef struct core_updater_download_handle +{ + char *remote_filename; + char *remote_core_path; + char *local_download_path; + char *local_core_path; + char *display_name; + uint32_t remote_crc; + bool check_crc; + bool crc_match; + retro_task_t *http_task; + bool http_task_finished; + bool http_task_complete; + retro_task_t *decompress_task; + bool decompress_task_finished; + bool decompress_task_complete; + enum core_updater_download_status status; +} core_updater_download_handle_t; + +/* Update installed cores */ +enum update_installed_cores_status +{ + UPDATE_INSTALLED_CORES_BEGIN = 0, + UPDATE_INSTALLED_CORES_WAIT_LIST, + UPDATE_INSTALLED_CORES_ITERATE, + UPDATE_INSTALLED_CORES_UPDATE_CORE, + UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD, + UPDATE_INSTALLED_CORES_END +}; + +typedef struct update_installed_cores_handle +{ + core_updater_list_t* core_list; + retro_task_t *list_task; + retro_task_t *download_task; + size_t list_size; + size_t list_index; + size_t installed_index; + enum update_installed_cores_status status; +} update_installed_cores_handle_t; + +/*********************/ +/* Utility functions */ +/*********************/ + +/* Returns true if local core has the same crc + * value as core on buildbot */ +static bool local_core_matches_remote_crc( + const char *local_core_path, uint32_t remote_crc) +{ + /* Sanity check */ + if (string_is_empty(local_core_path) || (remote_crc == 0)) + return false; + + if (path_is_valid(local_core_path)) + { + int64_t length = 0; + uint8_t *ret_buf = NULL; + + if (filestream_read_file( + local_core_path, (void**)&ret_buf, &length)) + { + uint32_t crc = 0; + + if (length >= 0) + crc = encoding_crc32(0, ret_buf, length); + + if (ret_buf) + free(ret_buf); + + if ((crc != 0) && (crc == remote_crc)) + return true; + } + } + + return false; +} + +/*************************/ +/* Get core updater list */ +/*************************/ + +static void cb_http_task_core_updater_get_list( + retro_task_t *task, void *task_data, + void *user_data, const char *err) +{ + http_transfer_data_t *data = (http_transfer_data_t*)task_data; + file_transfer_t *transf = (file_transfer_t*)user_data; + core_updater_list_handle_t *list_handle = NULL; + + if (!data || !transf || err) + goto finish; + + list_handle = (core_updater_list_handle_t*)transf->user_data; + + if (!list_handle) + goto finish; + + list_handle->http_data = data; + list_handle->http_task_complete = true; + +finish: + + if (transf) + free(transf); +} + +static void free_core_updater_list_handle(core_updater_list_handle_t *list_handle) +{ + if (!list_handle) + return; + + if (list_handle->http_data) + { + if (list_handle->http_data->data) + free(list_handle->http_data->data); + + free(list_handle->http_data); + } + + free(list_handle); + list_handle = NULL; +} + +static void task_core_updater_get_list_handler(retro_task_t *task) +{ + core_updater_list_handle_t *list_handle = NULL; + + if (!task) + goto task_finished; + + list_handle = (core_updater_list_handle_t*)task->state; + + if (!list_handle) + goto task_finished; + + if (task_get_cancelled(task)) + goto task_finished; + + switch (list_handle->status) + { + case CORE_UPDATER_LIST_BEGIN: + { + settings_t *settings = config_get_ptr(); + file_transfer_t *transf = NULL; + char *tmp_url = NULL; + char buildbot_url[PATH_MAX_LENGTH]; + + buildbot_url[0] = '\0'; + + /* Reset core updater list */ + core_updater_list_reset(list_handle->core_list); + + /* Get core listing URL */ + if (!settings) + goto task_finished; + + if (string_is_empty(settings->paths.network_buildbot_url)) + goto task_finished; + + fill_pathname_join( + buildbot_url, + settings->paths.network_buildbot_url, + ".index-extended", + sizeof(buildbot_url)); + + tmp_url = strdup(buildbot_url); + buildbot_url[0] = '\0'; + net_http_urlencode_full( + buildbot_url, tmp_url, sizeof(buildbot_url)); + if (tmp_url) + free(tmp_url); + + if (string_is_empty(buildbot_url)) + goto task_finished; + + /* Configure file transfer object */ + transf = (file_transfer_t*)calloc(1, sizeof(file_transfer_t)); + + if (!transf) + goto task_finished; + + /* > Seems to be required - not sure why the + * underlying code is implemented like this... */ + strlcpy(transf->path, buildbot_url, sizeof(transf->path)); + + transf->user_data = (void*)list_handle; + + /* Push HTTP transfer task */ + list_handle->http_task = (retro_task_t*)task_push_http_transfer( + buildbot_url, true, NULL, + cb_http_task_core_updater_get_list, transf); + + /* Start waiting for HTTP transfer to complete */ + list_handle->status = CORE_UPDATER_LIST_WAIT; + } + break; + case CORE_UPDATER_LIST_WAIT: + { + /* If HTTP task is NULL, then it either finished + * or an error occurred - in either case, + * just move on to the next state */ + if (!list_handle->http_task) + list_handle->http_task_complete = true; + /* Otherwise, check if HTTP task is still running */ + else if (!list_handle->http_task_finished) + { + list_handle->http_task_finished = + task_get_finished(list_handle->http_task); + + /* If HTTP task is running, copy current + * progress value to *this* task */ + if (!list_handle->http_task_finished) + task_set_progress( + task, task_get_progress(list_handle->http_task)); + } + + /* Wait for task_push_http_transfer() + * callback to trigger */ + if (list_handle->http_task_complete) + list_handle->status = CORE_UPDATER_LIST_END; + } + break; + case CORE_UPDATER_LIST_END: + { + /* Parse HTTP transfer data */ + if (list_handle->http_data) + core_updater_list_parse_network_data( + list_handle->core_list, + list_handle->http_data->data, + list_handle->http_data->len); + + /* Enable menu refresh, if required */ +#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) + menu_entries_ctl( + MENU_ENTRIES_CTL_UNSET_REFRESH, + &list_handle->refresh_menu); +#endif + } + /* fall-through */ + default: + task_set_progress(task, 100); + goto task_finished; + } + + return; + +task_finished: + + if (task) + task_set_finished(task, true); + + free_core_updater_list_handle(list_handle); +} + +static bool task_core_updater_get_list_finder(retro_task_t *task, void *user_data) +{ + core_updater_list_handle_t *list_handle = NULL; + + if (!task || !user_data) + return false; + + if (task->handler != task_core_updater_get_list_handler) + return false; + + list_handle = (core_updater_list_handle_t*)task->state; + if (!list_handle) + return false; + + return ((uintptr_t)user_data == (uintptr_t)list_handle->core_list); +} + +void *task_push_get_core_updater_list( + core_updater_list_t* core_list, bool mute, bool refresh_menu) +{ + task_finder_data_t find_data; + retro_task_t *task = NULL; + core_updater_list_handle_t *list_handle = (core_updater_list_handle_t*) + calloc(1, sizeof(core_updater_list_handle_t)); + + /* Sanity check */ + if (!core_list || !list_handle) + goto error; + + /* Configure handle */ + list_handle->core_list = core_list; + list_handle->refresh_menu = refresh_menu; + list_handle->http_task = NULL; + list_handle->http_task_finished = false; + list_handle->http_task_complete = false; + list_handle->http_data = NULL; + list_handle->status = CORE_UPDATER_LIST_BEGIN; + + /* Concurrent downloads of the buildbot core listing + * to the same core_updater_list_t object are not + * allowed */ + find_data.func = task_core_updater_get_list_finder; + find_data.userdata = (void*)core_list; + + if (task_queue_find(&find_data)) + goto error; + + /* Create task */ + task = task_init(); + + if (!task) + goto error; + + /* Configure task */ + task->handler = task_core_updater_get_list_handler; + task->state = list_handle; + task->mute = mute; + task->title = strdup(msg_hash_to_str(MSG_FETCHING_CORE_LIST)); + task->alternative_look = true; + task->progress = 0; + + /* Push task */ + task_queue_push(task); + + return task; + +error: + + /* Clean up task */ + if (task) + { + free(task); + task = NULL; + } + + /* Clean up handle */ + free_core_updater_list_handle(list_handle); + + return NULL; +} + +/*****************/ +/* Download core */ +/*****************/ + +static void cb_decompress_task_core_updater_download( + retro_task_t *task, void *task_data, + void *user_data, const char *err) +{ + decompress_task_data_t *decompress_data = + (decompress_task_data_t*)task_data; + core_updater_download_handle_t *download_handle = + (core_updater_download_handle_t*)user_data; + + /* Signal that decompression task is complete */ + if (download_handle) + download_handle->decompress_task_complete = true; + + /* Remove original archive file */ + if (decompress_data) + { + if (!string_is_empty(decompress_data->source_file)) + if (path_is_valid(decompress_data->source_file)) + filestream_delete(decompress_data->source_file); + + if (decompress_data->source_file) + free(decompress_data->source_file); + + free(decompress_data); + } + + /* Log any error messages */ + if (!string_is_empty(err)) + RARCH_ERR("%s", err); +} + +void cb_http_task_core_updater_download( + retro_task_t *task, void *task_data, + void *user_data, const char *err) +{ + http_transfer_data_t *data = (http_transfer_data_t*)task_data; + file_transfer_t *transf = (file_transfer_t*)user_data; + core_updater_download_handle_t *download_handle = NULL; + char output_dir[PATH_MAX_LENGTH]; + + output_dir[0] = '\0'; + + if (!data || !transf) + goto finish; + + if (!data->data || string_is_empty(transf->path)) + goto finish; + + download_handle = (core_updater_download_handle_t*)transf->user_data; + + if (!download_handle) + goto finish; + + /* Update download_handle task status + * NOTE: We set decompress_task_complete = true + * here to prevent any lock-ups in the event + * of errors (or lack of decompression support). + * decompress_task_complete will be set false + * if/when we actually call task_push_decompress() */ + download_handle->http_task_complete = true; + download_handle->decompress_task_complete = true; + + /* Create output directory, if required */ + strlcpy(output_dir, transf->path, sizeof(output_dir)); + path_basedir_wrapper(output_dir); + + if (!path_mkdir(output_dir)) + { + err = msg_hash_to_str(MSG_FAILED_TO_CREATE_THE_DIRECTORY); + goto finish; + } + +#ifdef HAVE_COMPRESSION + /* If core file is an archive, make sure it is + * not being decompressed already (by another task) */ + if (path_is_compressed_file(transf->path)) + { + if (task_check_decompress(transf->path)) + { + err = msg_hash_to_str(MSG_DECOMPRESSION_ALREADY_IN_PROGRESS); + goto finish; + } + } +#endif + + /* Write core file to disk */ + if (!filestream_write_file(transf->path, data->data, data->len)) + { + err = "Write failed."; + goto finish; + } + +#if defined(HAVE_COMPRESSION) && defined(HAVE_ZLIB) + /* Decompress core file, if required + * NOTE: If core is compressed and platform + * doesn't have compression support, then this + * whole thing falls apart... + * We assume that the build process is configured + * in such a way that this cannot happen... */ + if (path_is_compressed_file(transf->path)) + { + download_handle->decompress_task = (retro_task_t*)task_push_decompress( + transf->path, output_dir, + NULL, NULL, NULL, + cb_decompress_task_core_updater_download, + (void*)download_handle, + NULL, true); + + if (!download_handle->decompress_task) + { + err = msg_hash_to_str(MSG_DECOMPRESSION_FAILED); + goto finish; + } + + download_handle->decompress_task_complete = false; + } +#endif + +finish: + + /* Log any error messages */ + if (!string_is_empty(err)) + { + RARCH_ERR("Download of '%s' failed: %s\n", + (transf ? transf->path: "unknown"), err); + } + + if (data) + { + if (data->data) + free(data->data); + free(data); + } + + if (transf) + free(transf); +} + +static void free_core_updater_download_handle(core_updater_download_handle_t *download_handle) +{ + if (!download_handle) + return; + + if (download_handle->remote_filename) + free(download_handle->remote_filename); + + if (download_handle->remote_core_path) + free(download_handle->remote_core_path); + + if (download_handle->local_download_path) + free(download_handle->local_download_path); + + if (download_handle->local_core_path) + free(download_handle->local_core_path); + + if (download_handle->display_name) + free(download_handle->display_name); + + free(download_handle); + download_handle = NULL; +} + +static void task_core_updater_download_handler(retro_task_t *task) +{ + core_updater_download_handle_t *download_handle = NULL; + + if (!task) + goto task_finished; + + download_handle = (core_updater_download_handle_t*)task->state; + + if (!download_handle) + goto task_finished; + + if (task_get_cancelled(task)) + goto task_finished; + + switch (download_handle->status) + { + case CORE_UPDATER_DOWNLOAD_BEGIN: + { + file_transfer_t *transf = NULL; + + /* Check CRC of existing core, if required */ + if (download_handle->check_crc) + download_handle->crc_match = local_core_matches_remote_crc( + download_handle->local_core_path, + download_handle->remote_crc); + + /* If CRC matches, end task immediately */ + if (download_handle->crc_match) + { + download_handle->status = CORE_UPDATER_DOWNLOAD_END; + break; + } + + /* Configure file transfer object */ + transf = (file_transfer_t*)calloc(1, sizeof(file_transfer_t)); + + if (!transf) + goto task_finished; + + strlcpy( + transf->path, download_handle->local_download_path, + sizeof(transf->path)); + + transf->user_data = (void*)download_handle; + + /* Push HTTP transfer task */ + download_handle->http_task = (retro_task_t*)task_push_http_transfer( + download_handle->remote_core_path, true, NULL, + cb_http_task_core_updater_download, transf); + + /* Start waiting for HTTP transfer to complete */ + download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER; + } + break; + case CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER: + { + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Update task title */ + task_free_title(task); + + strlcpy( + task_title, msg_hash_to_str(MSG_DOWNLOADING_CORE), + sizeof(task_title)); + strlcat(task_title, download_handle->display_name, sizeof(task_title)); + + task_set_title(task, strdup(task_title)); + + /* If HTTP task is NULL, then it either finished + * or an error occurred - in either case, + * just move on to the next state */ + if (!download_handle->http_task) + download_handle->http_task_complete = true; + /* Otherwise, check if HTTP task is still running */ + else if (!download_handle->http_task_finished) + { + download_handle->http_task_finished = + task_get_finished(download_handle->http_task); + + /* If HTTP task is running, copy current + * progress value to *this* task */ + if (!download_handle->http_task_finished) + { + /* Download accounts for first half of + * task progress */ + int8_t progress = task_get_progress(download_handle->http_task); + + task_set_progress(task, progress >> 1); + } + } + + /* Wait for task_push_http_transfer() + * callback to trigger */ + if (download_handle->http_task_complete) + download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS; + } + break; + case CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS: + { + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Update task title */ + task_free_title(task); + + strlcpy( + task_title, msg_hash_to_str(MSG_EXTRACTING_CORE), + sizeof(task_title)); + strlcat(task_title, download_handle->display_name, sizeof(task_title)); + + task_set_title(task, strdup(task_title)); + + /* If decompression task is NULL, then it either + * finished or an error occurred - in either case, + * just move on to the next state */ + if (!download_handle->decompress_task) + download_handle->decompress_task_complete = true; + /* Otherwise, check if decompression task is still + * running */ + else if (!download_handle->decompress_task_finished) + { + download_handle->decompress_task_finished = + task_get_finished(download_handle->decompress_task); + + /* If decompression task is running, copy + * current progress value to *this* task */ + if (!download_handle->decompress_task_finished) + { + /* Download accounts for second half + * of task progress */ + int8_t progress = task_get_progress(download_handle->decompress_task); + + task_set_progress(task, 50 + (progress >> 1)); + } + } + + /* Wait for task_push_decompress() + * callback to trigger */ + if (download_handle->decompress_task_complete) + download_handle->status = CORE_UPDATER_DOWNLOAD_END; + } + break; + case CORE_UPDATER_DOWNLOAD_END: + { + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Reload core info files */ + command_event(CMD_EVENT_CORE_INFO_INIT, NULL); + + /* Set final task title */ + task_free_title(task); + + strlcpy( + task_title, + download_handle->crc_match ? + msg_hash_to_str(MSG_LATEST_CORE_INSTALLED) : msg_hash_to_str(MSG_CORE_INSTALLED), + sizeof(task_title)); + strlcat(task_title, download_handle->display_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_core_updater_download_handle(download_handle); +} + +static bool task_core_updater_download_finder(retro_task_t *task, void *user_data) +{ + core_updater_download_handle_t *download_handle = NULL; + + if (!task || !user_data) + return false; + + if (task->handler != task_core_updater_download_handler) + return false; + + download_handle = (core_updater_download_handle_t*)task->state; + if (!download_handle) + return false; + + return string_is_equal((const char*)user_data, download_handle->remote_filename); +} + +void *task_push_core_updater_download( + core_updater_list_t* core_list, const char *filename, bool mute, bool check_crc) +{ + task_finder_data_t find_data; + char task_title[PATH_MAX_LENGTH]; + char local_download_path[PATH_MAX_LENGTH]; + settings_t *settings = config_get_ptr(); + const core_updater_list_entry_t *list_entry = NULL; + retro_task_t *task = NULL; + core_updater_download_handle_t *download_handle = (core_updater_download_handle_t*) + calloc(1, sizeof(core_updater_download_handle_t)); + + task_title[0] = '\0'; + local_download_path[0] = '\0'; + + /* Sanity check */ + if (!core_list || + string_is_empty(filename) || + !settings || + !download_handle) + goto error; + + /* Get core updater list entry */ + if (!core_updater_list_get_filename( + core_list, filename, &list_entry)) + goto error; + + if (string_is_empty(list_entry->remote_core_path)) + goto error; + + if (string_is_empty(list_entry->local_core_path)) + goto error; + + if (string_is_empty(list_entry->display_name)) + goto error; + + /* Get local file download path */ + if (string_is_empty(settings->paths.directory_libretro)) + goto error; + + fill_pathname_join( + local_download_path, + settings->paths.directory_libretro, + list_entry->remote_filename, + sizeof(local_download_path)); + + /* Configure handle */ + download_handle->remote_filename = strdup(list_entry->remote_filename); + download_handle->remote_core_path = strdup(list_entry->remote_core_path); + download_handle->local_download_path = strdup(local_download_path); + download_handle->local_core_path = strdup(list_entry->local_core_path); + download_handle->display_name = strdup(list_entry->display_name); + download_handle->remote_crc = list_entry->crc; + download_handle->check_crc = check_crc; + download_handle->crc_match = false; + download_handle->http_task = NULL; + download_handle->http_task_finished = false; + download_handle->http_task_complete = false; + download_handle->decompress_task = NULL; + download_handle->decompress_task_finished = false; + download_handle->decompress_task_complete = false; + download_handle->status = CORE_UPDATER_DOWNLOAD_BEGIN; + + /* Concurrent downloads of the same file are not allowed */ + find_data.func = task_core_updater_download_finder; + find_data.userdata = (void*)download_handle->remote_filename; + + if (task_queue_find(&find_data)) + goto error; + + /* Create task */ + task = task_init(); + + if (!task) + goto error; + + /* Configure task */ + strlcpy( + task_title, msg_hash_to_str(MSG_UPDATING_CORE), + sizeof(task_title)); + strlcat(task_title, download_handle->display_name, sizeof(task_title)); + + task->handler = task_core_updater_download_handler; + task->state = download_handle; + task->mute = mute; + task->title = strdup(task_title); + task->alternative_look = true; + task->progress = 0; + + /* Push task */ + task_queue_push(task); + + return task; + +error: + + /* Clean up task */ + if (task) + { + free(task); + task = NULL; + } + + /* Clean up handle */ + free_core_updater_download_handle(download_handle); + + return NULL; +} + +/**************************/ +/* Update installed cores */ +/**************************/ + +static void free_update_installed_cores_handle(update_installed_cores_handle_t *update_installed_handle) +{ + if (!update_installed_handle) + return; + + core_updater_list_free(update_installed_handle->core_list); + + free(update_installed_handle); + update_installed_handle = NULL; +} + +static void task_update_installed_cores_handler(retro_task_t *task) +{ + update_installed_cores_handle_t *update_installed_handle = NULL; + + if (!task) + goto task_finished; + + update_installed_handle = (update_installed_cores_handle_t*)task->state; + + if (!update_installed_handle) + goto task_finished; + + if (task_get_cancelled(task)) + goto task_finished; + + switch (update_installed_handle->status) + { + case UPDATE_INSTALLED_CORES_BEGIN: + { + /* Request buildbot core list */ + update_installed_handle->list_task = (retro_task_t*) + task_push_get_core_updater_list( + update_installed_handle->core_list, + true, false); + + /* If push failed, go to end + * (error will message will be displayed when + * final task title is set) */ + if (!update_installed_handle->list_task) + update_installed_handle->status = UPDATE_INSTALLED_CORES_END; + else + update_installed_handle->status = UPDATE_INSTALLED_CORES_WAIT_LIST; + } + break; + case UPDATE_INSTALLED_CORES_WAIT_LIST: + { + bool list_available = false; + + /* > If task is running, check 'is finished' + * status + * > If task is NULL, then it is finished + * by definition */ + if (update_installed_handle->list_task) + list_available = task_get_finished(update_installed_handle->list_task); + else + list_available = true; + + /* If list is available, make sure it isn't empty + * (error will message will be displayed when + * final task title is set) */ + if (list_available) + { + update_installed_handle->list_size = + core_updater_list_size(update_installed_handle->core_list); + + if (update_installed_handle->list_size < 1) + update_installed_handle->status = UPDATE_INSTALLED_CORES_END; + else + update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; + } + } + break; + case UPDATE_INSTALLED_CORES_ITERATE: + { + const core_updater_list_entry_t *list_entry = NULL; + bool core_installed = false; + + /* Check whether we have reached the end + * of the list */ + if (update_installed_handle->list_index >= update_installed_handle->list_size) + { + update_installed_handle->status = UPDATE_INSTALLED_CORES_END; + break; + } + + /* Check whether current core is installed */ + if (core_updater_list_get_index( + update_installed_handle->core_list, + update_installed_handle->list_index, + &list_entry)) + { + if (path_is_valid(list_entry->local_core_path)) + { + core_installed = true; + update_installed_handle->installed_index = + update_installed_handle->list_index; + update_installed_handle->status = + UPDATE_INSTALLED_CORES_UPDATE_CORE; + } + } + + /* Update progress display */ + task_free_title(task); + + if (core_installed) + { + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + strlcpy( + task_title, msg_hash_to_str(MSG_CHECKING_CORE), + sizeof(task_title)); + strlcat(task_title, list_entry->display_name, sizeof(task_title)); + + task_set_title(task, strdup(task_title)); + } + else + task_set_title(task, strdup(msg_hash_to_str(MSG_SCANNING_CORES))); + + task_set_progress(task, + (update_installed_handle->list_index * 100) / + update_installed_handle->list_size); + + /* Increment list index */ + update_installed_handle->list_index++; + } + break; + case UPDATE_INSTALLED_CORES_UPDATE_CORE: + { + const core_updater_list_entry_t *list_entry = NULL; + bool crc_match; + + /* Get list entry + * > In the event of an error, just return + * to UPDATE_INSTALLED_CORES_ITERATE state */ + if (!core_updater_list_get_index( + update_installed_handle->core_list, + update_installed_handle->installed_index, + &list_entry)) + { + update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; + break; + } + + /* Check CRC of existing core */ + crc_match = local_core_matches_remote_crc( + list_entry->local_core_path, + list_entry->crc); + + /* If CRC matches, then core is already the most + * recent version - just return to + * UPDATE_INSTALLED_CORES_ITERATE state */ + if (crc_match) + { + update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; + break; + } + + /* Existing core is not the most recent version + * > Request download */ + update_installed_handle->download_task = (retro_task_t*) + task_push_core_updater_download( + update_installed_handle->core_list, + list_entry->remote_filename, + true, false); + + /* Again, if an error occurred, just return to + * UPDATE_INSTALLED_CORES_ITERATE state */ + if (!update_installed_handle->download_task) + update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; + else + { + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* Update task title */ + task_free_title(task); + + strlcpy( + task_title, msg_hash_to_str(MSG_UPDATING_CORE), + sizeof(task_title)); + strlcat(task_title, list_entry->display_name, sizeof(task_title)); + + task_set_title(task, strdup(task_title)); + + /* Wait for download to complete */ + update_installed_handle->status = UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD; + } + } + break; + case UPDATE_INSTALLED_CORES_WAIT_DOWNLOAD: + { + bool download_complete = false; + + /* > If task is running, check 'is finished' + * status + * > If task is NULL, then it is finished + * by definition */ + if (update_installed_handle->download_task) + download_complete = task_get_finished(update_installed_handle->download_task); + else + download_complete = true; + + /* If download is complete, return to + * UPDATE_INSTALLED_CORES_ITERATE state */ + if (download_complete) + { + update_installed_handle->download_task = NULL; + update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; + } + } + break; + case UPDATE_INSTALLED_CORES_END: + { + /* Set final task title */ + task_free_title(task); + + task_set_title(task, + (update_installed_handle->list_size > 0) ? + strdup(msg_hash_to_str(MSG_ALL_CORES_UPDATED)) : + strdup(msg_hash_to_str(MSG_CORE_LIST_FAILED))); + } + /* fall-through */ + default: + task_set_progress(task, 100); + goto task_finished; + } + + return; + +task_finished: + + if (task) + task_set_finished(task, true); + + free_update_installed_cores_handle(update_installed_handle); +} + +static bool task_update_installed_cores_finder(retro_task_t *task, void *user_data) +{ + if (!task) + return false; + + if (task->handler == task_update_installed_cores_handler) + return true; + + return false; +} + +void task_push_update_installed_cores(void) +{ + task_finder_data_t find_data; + retro_task_t *task = NULL; + update_installed_cores_handle_t *update_installed_handle = + (update_installed_cores_handle_t*) + calloc(1, sizeof(update_installed_cores_handle_t)); + + /* Sanity check */ + if (!update_installed_handle) + goto error; + + /* Configure handle */ + update_installed_handle->core_list = core_updater_list_init(CORE_UPDATER_LIST_SIZE); + update_installed_handle->list_task = NULL; + update_installed_handle->download_task = NULL; + update_installed_handle->list_size = 0; + update_installed_handle->list_index = 0; + update_installed_handle->installed_index = 0; + update_installed_handle->status = UPDATE_INSTALLED_CORES_BEGIN; + + if (!update_installed_handle->core_list) + goto error; + + /* Only one instance of this task may run at a time */ + find_data.func = task_update_installed_cores_finder; + + if (task_queue_find(&find_data)) + goto error; + + /* Create task */ + task = task_init(); + + if (!task) + goto error; + + /* Configure task */ + task->handler = task_update_installed_cores_handler; + task->state = update_installed_handle; + task->title = strdup(msg_hash_to_str(MSG_FETCHING_CORE_LIST)); + task->alternative_look = true; + task->progress = 0; + + /* Push task */ + task_queue_push(task); + + return; + +error: + + /* Clean up task */ + if (task) + { + free(task); + task = NULL; + } + + /* Clean up handle */ + free_update_installed_cores_handle(update_installed_handle); +} diff --git a/tasks/task_decompress.c b/tasks/task_decompress.c index 8c21e872df..04f34c0567 100644 --- a/tasks/task_decompress.c +++ b/tasks/task_decompress.c @@ -261,7 +261,7 @@ bool task_check_decompress(const char *source_file) return task_queue_find(&find_data); } -bool task_push_decompress( +void *task_push_decompress( const char *source_file, const char *target_dir, const char *target_file, @@ -269,7 +269,8 @@ bool task_push_decompress( const char *valid_ext, retro_task_callback_t cb, void *user_data, - void *frontend_userdata) + void *frontend_userdata, + bool mute) { char tmp[PATH_MAX_LENGTH]; const char *ext = NULL; @@ -283,7 +284,7 @@ bool task_push_decompress( RARCH_WARN( "[decompress] Empty or null source file or" " target directory arguments.\n"); - return false; + return NULL; } ext = path_get_extension(source_file); @@ -304,7 +305,7 @@ bool task_push_decompress( "[decompress] File '%s' does not exist" " or is not a compressed file.\n", source_file); - return false; + return NULL; } if (!valid_ext || !valid_ext[0]) @@ -315,7 +316,7 @@ bool task_push_decompress( RARCH_LOG( "[decompress] File '%s' already being decompressed.\n", source_file); - return false; + return NULL; } RARCH_LOG("[decompress] File '%s.\n", source_file); @@ -323,14 +324,14 @@ bool task_push_decompress( s = (decompress_state_t*)calloc(1, sizeof(*s)); if (!s) - return false; + return NULL; t = (retro_task_t*)calloc(1, sizeof(*t)); if (!t) { free(s); - return false; + return NULL; } s->source_file = strdup(source_file); @@ -365,8 +366,9 @@ bool task_push_decompress( path_basename(source_file)); t->title = strdup(tmp); + t->mute = mute; task_queue_push(t); - return true; + return t; } diff --git a/tasks/task_file_transfer.h b/tasks/task_file_transfer.h index 605b43f1f0..f663bdf216 100644 --- a/tasks/task_file_transfer.h +++ b/tasks/task_file_transfer.h @@ -74,6 +74,7 @@ typedef struct { enum msg_hash_enums enum_idx; char path[PATH_MAX_LENGTH]; + void *user_data; } file_transfer_t; RETRO_END_DECLS diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index 201975c08b..702d5985e1 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -28,6 +28,10 @@ #include "../config.h" #endif +#if defined(HAVE_NETWORKING) +#include "../core_updater_list.h" +#endif + #if defined(HAVE_NETWORKING) && defined(HAVE_MENU) /* Required for task_push_pl_entry_thumbnail_download() */ #include "../playlist.h" @@ -71,6 +75,13 @@ bool task_push_netplay_lan_scan_rooms(retro_task_callback_t cb); bool task_push_netplay_nat_traversal(void *nat_traversal_state, uint16_t port); +/* Core updater tasks */ +void *task_push_get_core_updater_list( + core_updater_list_t* core_list, bool mute, bool refresh_menu); +void *task_push_core_updater_download( + core_updater_list_t* core_list, const char *filename, bool mute, bool check_crc); +void task_push_update_installed_cores(void); + #ifdef HAVE_MENU bool task_push_pl_thumbnail_download(const char *system, const char *playlist_path); bool task_push_pl_entry_thumbnail_download( @@ -113,7 +124,7 @@ bool task_push_overlay_load_default( bool task_check_decompress(const char *source_file); -bool task_push_decompress( +void *task_push_decompress( const char *source_file, const char *target_dir, const char *target_file, @@ -121,7 +132,8 @@ bool task_push_decompress( const char *valid_ext, retro_task_callback_t cb, void *user_data, - void *frontend_userdata); + void *frontend_userdata, + bool mute); void task_file_load_handler(retro_task_t *task); diff --git a/ui/drivers/qt/ui_qt_window.cpp b/ui/drivers/qt/ui_qt_window.cpp index 16ad166d97..a83f6a690d 100644 --- a/ui/drivers/qt/ui_qt_window.cpp +++ b/ui/drivers/qt/ui_qt_window.cpp @@ -3023,6 +3023,7 @@ int MainWindow::onExtractArchive(QString path, QString extractionDir, QString te const char *dir = dirArray.constData(); struct string_list *file_list = file_archive_get_file_list(file, NULL); bool returnerr = true; + retro_task_t *decompress_task = NULL; if (!file_list || file_list->size == 0) { @@ -3079,9 +3080,12 @@ int MainWindow::onExtractArchive(QString path, QString extractionDir, QString te m_updateProgressDialog->setCancelButtonText(QString()); m_updateProgressDialog->show(); - if (!task_push_decompress(file, dir, - NULL, NULL, NULL, - cb, this, NULL)) + decompress_task = (retro_task_t*)task_push_decompress( + file, dir, + NULL, NULL, NULL, + cb, this, NULL, false); + + if (!decompress_task) { m_updateProgressDialog->cancel(); return -1;