RetroArch/tasks/task_core_updater.c

1474 lines
47 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <boolean.h>
#include <string/stdstring.h>
#include <file/file_path.h>
#include <net/net_http.h>
#include <streams/interface_stream.h>
#include <streams/file_stream.h>
#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;
bool http_task_success;
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_START_BACKUP,
CORE_UPDATER_DOWNLOAD_WAIT_BACKUP,
CORE_UPDATER_DOWNLOAD_START_TRANSFER,
CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER,
CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS,
CORE_UPDATER_DOWNLOAD_END
};
typedef struct core_updater_download_handle
{
bool auto_backup;
size_t auto_backup_history_size;
char *path_dir_libretro;
char *path_dir_core_assets;
char *remote_filename;
char *remote_core_path;
char *local_download_path;
char *local_core_path;
char *display_name;
uint32_t local_crc;
uint32_t remote_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;
bool backup_enabled;
retro_task_t *backup_task;
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
{
bool auto_backup;
size_t auto_backup_history_size;
char *path_dir_libretro;
char *path_dir_core_assets;
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;
unsigned num_updated;
unsigned num_locked;
enum update_installed_cores_status status;
} update_installed_cores_handle_t;
/*********************/
/* Utility functions */
/*********************/
/* Returns CRC32 of specified core file */
static uint32_t task_core_updater_get_core_crc(const char *core_path)
{
if (string_is_empty(core_path))
return 0;
if (path_is_valid(core_path))
{
/* Open core file */
intfstream_t *core_file = intfstream_open_file(
core_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (core_file)
{
uint32_t crc = 0;
/* Get crc value */
bool success = intfstream_get_crc(core_file, &crc);
/* Close core file */
intfstream_close(core_file);
free(core_file);
core_file = NULL;
if (success)
return crc;
}
}
return 0;
}
/*************************/
/* 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;
bool success = data && string_is_empty(err);
if (!transf)
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;
list_handle->http_task_success = success;
finish:
/* Log any error messages */
if (!success)
RARCH_ERR("[core updater] Download of core list '%s' failed: %s\n",
(transf ? transf->path: "unknown"),
(err ? err : "unknown"));
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];
const char *net_buildbot_url =
settings->paths.network_buildbot_url;
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(net_buildbot_url))
goto task_finished;
fill_pathname_join(
buildbot_url,
net_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_file(
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_file()
* callback to trigger */
if (list_handle->http_task_complete)
list_handle->status = CORE_UPDATER_LIST_END;
}
break;
case CORE_UPDATER_LIST_END:
{
settings_t *settings = config_get_ptr();
/* Check whether HTTP task was successful */
if (list_handle->http_task_success)
{
/* Parse HTTP transfer data */
if (list_handle->http_data)
core_updater_list_parse_network_data(
list_handle->core_list,
settings->paths.directory_libretro,
settings->paths.path_libretro_info,
settings->paths.network_buildbot_url,
list_handle->http_data->data,
list_handle->http_data->len);
}
else
{
/* Notify user of error via task title */
task_free_title(task);
task_set_title(task, strdup(msg_hash_to_str(MSG_CORE_LIST_FAILED)));
}
/* Enable menu refresh, if required */
#if defined(RARCH_INTERNAL) && defined(HAVE_MENU)
if (list_handle->refresh_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_task_success = 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_task_core_updater_download(
retro_task_t *task, void *task_data,
void *user_data, const char *err)
{
/* Reload core info files
* > This must be done on the main thread */
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
}
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("[core updater] %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("[core updater] 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->path_dir_libretro)
free(download_handle->path_dir_libretro);
if (download_handle->path_dir_core_assets)
free(download_handle->path_dir_core_assets);
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:
{
/* Get CRC of existing core, if required */
if (download_handle->local_crc == 0)
download_handle->local_crc = task_core_updater_get_core_crc(
download_handle->local_core_path);
/* Check whether existing core and remote core
* have the same CRC */
download_handle->crc_match = (download_handle->local_crc != 0) &&
(download_handle->local_crc == download_handle->remote_crc);
/* If CRC matches, end task immediately */
if (download_handle->crc_match)
{
download_handle->status = CORE_UPDATER_DOWNLOAD_END;
break;
}
/* If automatic backups are enabled and core is
* already installed, trigger a backup - otherwise,
* initialise download */
download_handle->backup_enabled = download_handle->auto_backup &&
path_is_valid(download_handle->local_core_path);
download_handle->status = download_handle->backup_enabled ?
CORE_UPDATER_DOWNLOAD_START_BACKUP :
CORE_UPDATER_DOWNLOAD_START_TRANSFER;
}
break;
case CORE_UPDATER_DOWNLOAD_START_BACKUP:
{
/* Request core backup */
download_handle->backup_task = (retro_task_t*)task_push_core_backup(
download_handle->local_core_path,
download_handle->display_name,
download_handle->local_crc, CORE_BACKUP_MODE_AUTO,
download_handle->auto_backup_history_size,
download_handle->path_dir_core_assets, true);
if (download_handle->backup_task)
{
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_BACKING_UP_CORE),
sizeof(task_title));
strlcat(task_title, download_handle->display_name, sizeof(task_title));
task_set_title(task, strdup(task_title));
/* Start waiting for backup to complete */
download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_BACKUP;
}
else
{
/* This cannot realistically happen...
* > If it does, just log an error and initialise
* download */
RARCH_ERR("[core updater] Failed to backup core: %s\n",
download_handle->local_core_path);
download_handle->backup_enabled = false;
download_handle->status = CORE_UPDATER_DOWNLOAD_START_TRANSFER;
}
}
break;
case CORE_UPDATER_DOWNLOAD_WAIT_BACKUP:
{
bool backup_complete = false;
/* > If task is running, check 'is finished'
* status
* > If task is NULL, then it is finished
* by definition */
if (download_handle->backup_task)
{
backup_complete = task_get_finished(download_handle->backup_task);
/* If backup task is running, copy current
* progress value to *this* task */
if (!backup_complete)
{
/* Backup accounts for first third of
* task progress */
int8_t progress = task_get_progress(download_handle->backup_task);
task_set_progress(task, (int8_t)(((float)progress * (1.0f / 3.0f)) + 0.5f));
}
}
else
backup_complete = true;
/* If backup is complete, initialise download */
if (backup_complete)
{
download_handle->backup_task = NULL;
download_handle->status = CORE_UPDATER_DOWNLOAD_START_TRANSFER;
}
}
break;
case CORE_UPDATER_DOWNLOAD_START_TRANSFER:
{
file_transfer_t *transf = NULL;
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* 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_file(
download_handle->remote_core_path, true, NULL,
cb_http_task_core_updater_download, transf);
/* 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));
/* Start waiting for HTTP transfer to complete */
download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER;
}
break;
case CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER:
{
/* 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)
{
/* > If backups are enabled, download accounts
* for second third of task progress
* > Otherwise, download accounts for first half
* of task progress */
int8_t progress = task_get_progress(download_handle->http_task);
if (download_handle->backup_enabled)
progress = (int8_t)(((float)progress * (1.0f / 3.0f)) + (100.0f / 3.0f) + 0.5f);
else
progress = progress >> 1;
task_set_progress(task, progress);
}
}
/* Wait for task_push_http_transfer_file()
* callback to trigger */
if (download_handle->http_task_complete)
{
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));
/* Start waiting for file to be extracted */
download_handle->status = CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS;
}
}
break;
case CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS:
{
/* 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)
{
/* > If backups are enabled, decompression accounts
* for last third of task progress
* > Otherwise, decompression accounts for second
* half of task progress */
int8_t progress = task_get_progress(download_handle->decompress_task);
if (download_handle->backup_enabled)
progress = (int8_t)(((float)progress * (1.0f / 3.0f)) + (200.0f / 3.0f) + 0.5f);
else
progress = 50 + (progress >> 1);
task_set_progress(task, progress);
}
}
/* 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';
/* 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, uint32_t crc, bool mute,
bool auto_backup, size_t auto_backup_history_size,
const char *path_dir_libretro,
const char *path_dir_core_assets)
{
task_finder_data_t find_data;
char task_title[PATH_MAX_LENGTH];
char local_download_path[PATH_MAX_LENGTH];
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) ||
!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) ||
string_is_empty(list_entry->local_core_path) ||
string_is_empty(list_entry->display_name))
goto error;
/* Check whether core is locked
* > Have to set validate_path to 'false' here,
* since this may not run on the main thread
* > Validation is not required anyway, since core
* updater list provides 'sane' core paths */
if (core_info_get_core_lock(list_entry->local_core_path, false))
{
RARCH_ERR("[core updater] Update disabled - core is locked: %s\n",
list_entry->local_core_path);
/* If task is not muted, generate notification */
if (!mute)
{
char msg[PATH_MAX_LENGTH];
msg[0] = '\0';
strlcpy(msg, msg_hash_to_str(MSG_CORE_UPDATE_DISABLED), sizeof(msg));
strlcat(msg, list_entry->display_name, sizeof(msg));
runloop_msg_queue_push(msg, 1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
goto error;
}
/* Get local file download path */
if (string_is_empty(path_dir_libretro))
goto error;
fill_pathname_join(
local_download_path,
path_dir_libretro,
list_entry->remote_filename,
sizeof(local_download_path));
/* Configure handle */
download_handle->auto_backup = auto_backup;
download_handle->auto_backup_history_size = auto_backup_history_size;
download_handle->path_dir_libretro = strdup(path_dir_libretro);
download_handle->path_dir_core_assets = string_is_empty(path_dir_core_assets) ? NULL : strdup(path_dir_core_assets);
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->local_crc = crc;
download_handle->remote_crc = list_entry->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->backup_enabled = false;
download_handle->backup_task = NULL;
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;
task->callback = cb_task_core_updater_download;
/* 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;
if (update_installed_handle->path_dir_libretro)
free(update_installed_handle->path_dir_libretro);
if (update_installed_handle->path_dir_core_assets)
free(update_installed_handle->path_dir_core_assets);
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;
uint32_t local_crc;
/* 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 whether core is locked
* > Have to set validate_path to 'false' here,
* since this does not run on the main thread
* > Validation is not required anyway, since core
* updater list provides 'sane' core paths */
if (core_info_get_core_lock(list_entry->local_core_path, false))
{
RARCH_LOG("[core updater] Skipping locked core: %s\n",
list_entry->display_name);
/* Core update is disabled
* > Just increment 'locked cores' counter and
* return to UPDATE_INSTALLED_CORES_ITERATE state */
update_installed_handle->num_locked++;
update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE;
break;
}
/* Get CRC of existing core */
local_crc = task_core_updater_get_core_crc(
list_entry->local_core_path);
/* Check whether existing core and remote core
* have the same CRC
* > If CRC matches, then core is already the most
* recent version - just return to
* UPDATE_INSTALLED_CORES_ITERATE state */
if ((local_crc != 0) && (local_crc == list_entry->crc))
{
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,
local_crc, true,
update_installed_handle->auto_backup,
update_installed_handle->auto_backup_history_size,
update_installed_handle->path_dir_libretro,
update_installed_handle->path_dir_core_assets);
/* 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));
/* Increment 'updated cores' counter */
update_installed_handle->num_updated++;
/* 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);
/* > Check whether core list was fetched
* successfully */
if (update_installed_handle->list_size > 0)
{
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* > Generate final status message based on number
* of cores that were updated/locked */
if (update_installed_handle->num_updated > 0)
{
if (update_installed_handle->num_locked > 0)
snprintf(
task_title, sizeof(task_title), "%s [%s%u, %s%u]",
msg_hash_to_str(MSG_ALL_CORES_UPDATED),
msg_hash_to_str(MSG_NUM_CORES_UPDATED),
update_installed_handle->num_updated,
msg_hash_to_str(MSG_NUM_CORES_LOCKED),
update_installed_handle->num_locked);
else
snprintf(
task_title, sizeof(task_title), "%s [%s%u]",
msg_hash_to_str(MSG_ALL_CORES_UPDATED),
msg_hash_to_str(MSG_NUM_CORES_UPDATED),
update_installed_handle->num_updated);
}
else if (update_installed_handle->num_locked > 0)
snprintf(
task_title, sizeof(task_title), "%s [%s%u]",
msg_hash_to_str(MSG_ALL_CORES_UPDATED),
msg_hash_to_str(MSG_NUM_CORES_LOCKED),
update_installed_handle->num_locked);
else
strlcpy(task_title, msg_hash_to_str(MSG_ALL_CORES_UPDATED),
sizeof(task_title));
task_set_title(task, strdup(task_title));
}
else
task_set_title(task, 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(
bool auto_backup, size_t auto_backup_history_size,
const char *path_dir_libretro,
const char *path_dir_core_assets)
{
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 ||
string_is_empty(path_dir_libretro))
goto error;
/* Configure handle */
update_installed_handle->auto_backup = auto_backup;
update_installed_handle->auto_backup_history_size = auto_backup_history_size;
update_installed_handle->path_dir_libretro = strdup(path_dir_libretro);
update_installed_handle->path_dir_core_assets = string_is_empty(path_dir_core_assets) ?
NULL : strdup(path_dir_core_assets);
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->num_updated = 0;
update_installed_handle->num_locked = 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;
find_data.userdata = NULL;
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);
}