RetroArch/tasks/task_core_backup.c
2020-10-10 18:02:02 +02:00

1097 lines
35 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-2020 - 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 <streams/interface_stream.h>
#include <streams/file_stream.h>
#include <streams/rzip_stream.h>
#include "../retroarch.h"
#include "../paths.h"
#include "../command.h"
#include "../msg_hash.h"
#include "../verbosity.h"
#include "../core_info.h"
#include "../core_backup.h"
#if defined(ANDROID)
#include "../play_feature_delivery/play_feature_delivery.h"
#endif
#define CORE_BACKUP_CHUNK_SIZE 4096
enum core_backup_status
{
CORE_BACKUP_BEGIN = 0,
CORE_BACKUP_CHECK_CRC,
CORE_BACKUP_PRE_ITERATE,
CORE_BACKUP_ITERATE,
CORE_BACKUP_CHECK_HISTORY,
CORE_BACKUP_PRUNE_HISTORY,
CORE_BACKUP_END,
CORE_RESTORE_GET_CORE_CRC,
CORE_RESTORE_GET_BACKUP_CRC,
CORE_RESTORE_CHECK_CRC,
CORE_RESTORE_PRE_ITERATE,
CORE_RESTORE_ITERATE,
CORE_RESTORE_END
};
typedef struct core_backup_handle
{
int64_t core_file_size;
int64_t backup_file_size;
int64_t file_data_read;
char *dir_core_assets;
char *core_path;
char *core_name;
char *backup_path;
intfstream_t *core_file;
intfstream_t *backup_file;
core_backup_list_t *backup_list;
size_t auto_backup_history_size;
size_t num_auto_backups_to_remove;
size_t backup_index;
uint32_t core_crc;
uint32_t backup_crc;
enum core_backup_type backup_type;
enum core_backup_mode backup_mode;
enum core_backup_status status;
bool crc_match;
bool success;
} core_backup_handle_t;
/*********************/
/* Utility functions */
/*********************/
static void free_core_backup_handle(core_backup_handle_t *backup_handle)
{
if (!backup_handle)
return;
if (backup_handle->dir_core_assets)
{
free(backup_handle->dir_core_assets);
backup_handle->dir_core_assets = NULL;
}
if (backup_handle->core_path)
{
free(backup_handle->core_path);
backup_handle->core_path = NULL;
}
if (backup_handle->core_name)
{
free(backup_handle->core_name);
backup_handle->core_name = NULL;
}
if (backup_handle->backup_path)
{
free(backup_handle->backup_path);
backup_handle->backup_path = NULL;
}
if (backup_handle->core_file)
{
intfstream_close(backup_handle->core_file);
free(backup_handle->core_file);
backup_handle->core_file = NULL;
}
if (backup_handle->backup_file)
{
intfstream_close(backup_handle->backup_file);
free(backup_handle->backup_file);
backup_handle->backup_file = NULL;
}
if (backup_handle->backup_list)
{
core_backup_list_free(backup_handle->backup_list);
backup_handle->backup_list = NULL;
}
free(backup_handle);
backup_handle = NULL;
}
/* Forward declarations, required for task_core_backup_finder() */
static void task_core_backup_handler(retro_task_t *task);
static void task_core_restore_handler(retro_task_t *task);
static bool task_core_backup_finder(retro_task_t *task, void *user_data)
{
core_backup_handle_t *backup_handle = NULL;
const char *core_filename_a = NULL;
const char *core_filename_b = NULL;
if (!task || !user_data)
return false;
if ((task->handler != task_core_backup_handler) &&
(task->handler != task_core_restore_handler))
return false;
backup_handle = (core_backup_handle_t*)task->state;
if (!backup_handle)
return false;
if (string_is_empty(backup_handle->core_path))
return false;
core_filename_a = path_basename((const char*)user_data);
core_filename_b = path_basename(backup_handle->core_path);
if (string_is_empty(core_filename_a) ||
string_is_empty(core_filename_b))
return false;
return string_is_equal(core_filename_a, core_filename_b);
}
/***************/
/* Core Backup */
/***************/
static void task_core_backup_handler(retro_task_t *task)
{
core_backup_handle_t *backup_handle = NULL;
if (!task)
goto task_finished;
backup_handle = (core_backup_handle_t*)task->state;
if (!backup_handle)
goto task_finished;
if (task_get_cancelled(task))
goto task_finished;
switch (backup_handle->status)
{
case CORE_BACKUP_BEGIN:
/* Get current list of backups */
backup_handle->backup_list = core_backup_list_init(
backup_handle->core_path, backup_handle->dir_core_assets);
/* Open core file */
backup_handle->core_file = intfstream_open_file(
backup_handle->core_path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!backup_handle->core_file)
{
RARCH_ERR("[core backup] Failed to open core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
/* Get core file size */
backup_handle->core_file_size = intfstream_get_size(backup_handle->core_file);
if (backup_handle->core_file_size <= 0)
{
RARCH_ERR("[core backup] Core file is empty/invalid: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
/* Go to CRC checking phase */
backup_handle->status = CORE_BACKUP_CHECK_CRC;
break;
case CORE_BACKUP_CHECK_CRC:
/* Check whether we need to calculate CRC value */
if (backup_handle->core_crc == 0)
{
if (!intfstream_get_crc(backup_handle->core_file,
&backup_handle->core_crc))
{
RARCH_ERR("[core backup] Failed to determine CRC of core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
}
/* Check whether a backup with this CRC already
* exists */
if (backup_handle->backup_list)
{
const core_backup_list_entry_t *entry = NULL;
if (core_backup_list_get_crc(
backup_handle->backup_list,
backup_handle->core_crc,
backup_handle->backup_mode,
&entry))
{
RARCH_LOG("[core backup] Current version of core is already backed up: %s\n",
entry->backup_path);
backup_handle->crc_match = true;
backup_handle->success = true;
backup_handle->status = CORE_BACKUP_END;
break;
}
}
/* Go to pre-iteration phase */
backup_handle->status = CORE_BACKUP_PRE_ITERATE;
break;
case CORE_BACKUP_PRE_ITERATE:
{
char task_title[PATH_MAX_LENGTH];
char backup_path[PATH_MAX_LENGTH];
task_title[0] = '\0';
backup_path[0] = '\0';
/* Get backup path */
if (!core_backup_get_backup_path(
backup_handle->core_path,
backup_handle->core_crc,
backup_handle->backup_mode,
backup_handle->dir_core_assets,
backup_path, sizeof(backup_path)))
{
RARCH_ERR("[core backup] Failed to generate backup path for core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
backup_handle->backup_path = strdup(backup_path);
/* Open backup file */
#if defined(HAVE_ZLIB)
backup_handle->backup_file = intfstream_open_rzip_file(
backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_WRITE);
#else
backup_handle->backup_file = intfstream_open_file(
backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
#endif
if (!backup_handle->backup_file)
{
RARCH_ERR("[core backup] Failed to open core backup file: %s\n",
backup_handle->backup_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
/* Update task title */
task_free_title(task);
strlcpy(task_title, msg_hash_to_str(MSG_BACKING_UP_CORE),
sizeof(task_title));
strlcat(task_title, backup_handle->core_name, sizeof(task_title));
task_set_title(task, strdup(task_title));
/* Go to iteration phase */
backup_handle->status = CORE_BACKUP_ITERATE;
}
break;
case CORE_BACKUP_ITERATE:
{
int64_t data_written = 0;
uint8_t buffer[CORE_BACKUP_CHUNK_SIZE];
/* Read a single chunk from the core file */
int64_t data_read = intfstream_read(
backup_handle->core_file, buffer, sizeof(buffer));
if (data_read < 0)
{
RARCH_ERR("[core backup] Failed to read from core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
backup_handle->file_data_read += data_read;
/* Check whether we have reached the end of the file */
if (data_read == 0)
{
/* Close core file */
intfstream_close(backup_handle->core_file);
free(backup_handle->core_file);
backup_handle->core_file = NULL;
/* Close backup file */
intfstream_flush(backup_handle->backup_file);
intfstream_close(backup_handle->backup_file);
free(backup_handle->backup_file);
backup_handle->backup_file = NULL;
backup_handle->success = true;
/* If this is an automatic backup, check whether
* any old backup files should be deleted.
* In all other cases, backup is complete */
backup_handle->status = (backup_handle->backup_mode ==
CORE_BACKUP_MODE_AUTO) ?
CORE_BACKUP_CHECK_HISTORY : CORE_BACKUP_END;
break;
}
/* Write chunk to backup file */
data_written = intfstream_write(backup_handle->backup_file, buffer, data_read);
if (data_written != data_read)
{
RARCH_ERR("[core backup] Failed to write to core backup file: %s\n",
backup_handle->backup_path);
backup_handle->status = CORE_BACKUP_END;
break;
}
/* Update progress display */
task_set_progress(task,
(int8_t)((backup_handle->file_data_read * 100) / backup_handle->core_file_size));
}
break;
case CORE_BACKUP_CHECK_HISTORY:
{
size_t num_backups;
/* Sanity check: We can only reach this stage
* when performing automatic backups, but verify
* that this is true (i.e. don't inadvertently
* delete backups of the wrong type if someone
* edits the CORE_BACKUP_ITERATE case statement...) */
if (backup_handle->backup_mode != CORE_BACKUP_MODE_AUTO)
{
backup_handle->status = CORE_BACKUP_END;
break;
}
/* This is an automated backup
* > Fetch number of existing auto backups
* in the list
* > If we reach this stage then a new backup
* has been created successfully -> increment
* count by one */
num_backups = core_backup_list_get_num_backups(
backup_handle->backup_list,
CORE_BACKUP_MODE_AUTO);
num_backups++;
/* Check whether we have exceeded the backup
* history size limit */
if (num_backups > backup_handle->auto_backup_history_size)
{
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Get number of old backups to remove */
backup_handle->num_auto_backups_to_remove = num_backups -
backup_handle->auto_backup_history_size;
/* Update task title */
task_free_title(task);
strlcpy(task_title, msg_hash_to_str(MSG_PRUNING_CORE_BACKUP_HISTORY),
sizeof(task_title));
strlcat(task_title, backup_handle->core_name, sizeof(task_title));
task_set_title(task, strdup(task_title));
/* Go to history clean-up phase */
backup_handle->status = CORE_BACKUP_PRUNE_HISTORY;
}
/* No backups to remove - task is complete */
else
backup_handle->status = CORE_BACKUP_END;
}
break;
case CORE_BACKUP_PRUNE_HISTORY:
{
size_t list_size = core_backup_list_size(backup_handle->backup_list);
/* The core backup list is automatically sorted
* by timestamp - simply loop from start to end
* and delete automatic backups until the required
* number have been removed */
while ((backup_handle->backup_index < list_size) &&
(backup_handle->num_auto_backups_to_remove > 0))
{
const core_backup_list_entry_t *entry = NULL;
if (core_backup_list_get_index(
backup_handle->backup_list,
backup_handle->backup_index,
&entry) &&
entry &&
(entry->backup_mode == CORE_BACKUP_MODE_AUTO))
{
/* Delete backup file (if it exists) */
if (path_is_valid(entry->backup_path))
filestream_delete(entry->backup_path);
backup_handle->num_auto_backups_to_remove--;
backup_handle->backup_index++;
/* Break here - only remove one file per
* iteration of the task */
break;
}
backup_handle->backup_index++;
}
/* Check whether all old backups have been
* removed (also handle error conditions by
* ensuring we quit if end of backup list is
* reached...) */
if ((backup_handle->num_auto_backups_to_remove < 1) ||
(backup_handle->backup_index >= list_size))
backup_handle->status = CORE_BACKUP_END;
}
break;
case CORE_BACKUP_END:
{
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Set final task title */
task_free_title(task);
if (backup_handle->success)
{
if (backup_handle->crc_match)
strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_ALREADY_EXISTS),
sizeof(task_title));
else
strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_COMPLETE),
sizeof(task_title));
}
else
strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_FAILED),
sizeof(task_title));
strlcat(task_title, backup_handle->core_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_backup_handle(backup_handle);
}
/* Note 1: If CRC is set to 0, CRC of core_path file will
* be calculated automatically
* Note 2: If core_display_name is set to NULL, display
* name will be determined automatically
* > core_display_name *must* be set to a non-empty
* string if task_push_core_backup() is *not* called
* on the main thread */
void *task_push_core_backup(
const char *core_path, const char *core_display_name,
uint32_t crc, enum core_backup_mode backup_mode,
size_t auto_backup_history_size,
const char *dir_core_assets, bool mute)
{
task_finder_data_t find_data;
const char *core_name = NULL;
retro_task_t *task = NULL;
core_backup_handle_t *backup_handle = NULL;
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Sanity check */
if (string_is_empty(core_path) ||
!path_is_valid(core_path))
goto error;
/* Concurrent backup/restore tasks for the same core
* are not allowed */
find_data.func = task_core_backup_finder;
find_data.userdata = (void*)core_path;
if (task_queue_find(&find_data))
goto error;
/* Get core name */
if (!string_is_empty(core_display_name))
core_name = core_display_name;
else
{
core_info_ctx_find_t core_info;
core_info.inf = NULL;
core_info.path = core_path;
/* If core is found, use display name */
if (core_info_find(&core_info) &&
core_info.inf->display_name)
core_name = core_info.inf->display_name;
else
{
/* If not, use core file name */
core_name = path_basename(core_path);
if (string_is_empty(core_name))
goto error;
}
}
/* Configure handle */
backup_handle = (core_backup_handle_t*)calloc(1, sizeof(core_backup_handle_t));
if (!backup_handle)
goto error;
backup_handle->dir_core_assets = string_is_empty(dir_core_assets) ? NULL : strdup(dir_core_assets);
backup_handle->core_path = strdup(core_path);
backup_handle->core_name = strdup(core_name);
backup_handle->backup_path = NULL;
backup_handle->backup_type = CORE_BACKUP_TYPE_ARCHIVE;
backup_handle->backup_mode = backup_mode;
backup_handle->auto_backup_history_size = auto_backup_history_size;
backup_handle->num_auto_backups_to_remove = 0;
backup_handle->backup_index = 0;
backup_handle->core_file_size = 0;
backup_handle->backup_file_size = 0;
backup_handle->file_data_read = 0;
backup_handle->core_crc = crc;
backup_handle->backup_crc = 0;
backup_handle->crc_match = false;
backup_handle->success = false;
backup_handle->core_file = NULL;
backup_handle->backup_file = NULL;
backup_handle->backup_list = NULL;
backup_handle->status = CORE_BACKUP_BEGIN;
/* Create task */
task = task_init();
if (!task)
goto error;
/* Get initial task title */
strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_SCANNING_CORE),
sizeof(task_title));
strlcat(task_title, backup_handle->core_name, sizeof(task_title));
/* Configure task */
task->handler = task_core_backup_handler;
task->state = backup_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_backup_handle(backup_handle);
return NULL;
}
/****************/
/* Core Restore */
/****************/
static void cb_task_core_restore(
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 task_core_restore_handler(retro_task_t *task)
{
core_backup_handle_t *backup_handle = NULL;
if (!task)
goto task_finished;
backup_handle = (core_backup_handle_t*)task->state;
if (!backup_handle)
goto task_finished;
if (task_get_cancelled(task))
goto task_finished;
switch (backup_handle->status)
{
case CORE_RESTORE_GET_CORE_CRC:
{
/* If core file already exists, get its current
* CRC value */
if (path_is_valid(backup_handle->core_path))
{
/* Open core file for reading */
backup_handle->core_file = intfstream_open_file(
backup_handle->core_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!backup_handle->core_file)
{
RARCH_ERR("[core restore] Failed to open core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Get CRC value */
if (!intfstream_get_crc(backup_handle->core_file,
&backup_handle->core_crc))
{
RARCH_ERR("[core restore] Failed to determine CRC of core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Close core file */
intfstream_close(backup_handle->core_file);
free(backup_handle->core_file);
backup_handle->core_file = NULL;
}
/* Go to next CRC gathering phase */
backup_handle->status = CORE_RESTORE_GET_BACKUP_CRC;
}
break;
case CORE_RESTORE_GET_BACKUP_CRC:
/* Get CRC value of backup file */
if (!core_backup_get_backup_crc(
backup_handle->backup_path, &backup_handle->backup_crc))
{
RARCH_ERR("[core restore] Failed to determine CRC of core backup file: %s\n",
backup_handle->backup_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Go to CRC comparison phase */
backup_handle->status = CORE_RESTORE_CHECK_CRC;
break;
case CORE_RESTORE_CHECK_CRC:
/* Check whether current core matches backup CRC */
if (backup_handle->core_crc == backup_handle->backup_crc)
{
RARCH_LOG("[core restore] Selected backup core file is already installed: %s\n",
backup_handle->backup_path);
backup_handle->crc_match = true;
backup_handle->success = true;
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Go to pre-iteration phase */
backup_handle->status = CORE_RESTORE_PRE_ITERATE;
break;
case CORE_RESTORE_PRE_ITERATE:
{
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Open backup file */
#if defined(HAVE_ZLIB)
backup_handle->backup_file = intfstream_open_rzip_file(
backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_READ);
#else
backup_handle->backup_file = intfstream_open_file(
backup_handle->backup_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
#endif
if (!backup_handle->backup_file)
{
RARCH_ERR("[core restore] Failed to open core backup file: %s\n",
backup_handle->backup_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Get backup file size */
backup_handle->backup_file_size = intfstream_get_size(backup_handle->backup_file);
if (backup_handle->backup_file_size <= 0)
{
RARCH_ERR("[core restore] Core backup file is empty/invalid: %s\n",
backup_handle->backup_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
#if defined(ANDROID)
/* If this is a Play Store build and the
* core is currently installed via
* play feature delivery, must delete
* the existing core before attempting
* to write any data */
if (play_feature_delivery_enabled())
{
const char *core_filename = path_basename(
backup_handle->core_path);
if (play_feature_delivery_core_installed(core_filename) &&
!play_feature_delivery_delete(core_filename))
{
RARCH_ERR("[core restore] Failed to delete existing play feature delivery core: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
}
#endif
/* Open core file for writing */
backup_handle->core_file = intfstream_open_file(
backup_handle->core_path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!backup_handle->core_file)
{
RARCH_ERR("[core restore] Failed to open core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Update task title */
task_free_title(task);
strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
msg_hash_to_str(MSG_RESTORING_CORE) :
msg_hash_to_str(MSG_INSTALLING_CORE),
sizeof(task_title));
strlcat(task_title, backup_handle->core_name, sizeof(task_title));
task_set_title(task, strdup(task_title));
/* Go to iteration phase */
backup_handle->status = CORE_RESTORE_ITERATE;
}
break;
case CORE_RESTORE_ITERATE:
{
int64_t data_read = 0;
int64_t data_written = 0;
uint8_t buffer[CORE_BACKUP_CHUNK_SIZE];
/* Read a single chunk from the backup file */
data_read = intfstream_read(backup_handle->backup_file, buffer, sizeof(buffer));
if (data_read < 0)
{
RARCH_ERR("[core restore] Failed to read from core backup file: %s\n",
backup_handle->backup_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
backup_handle->file_data_read += data_read;
/* Check whether we have reached the end of the file */
if (data_read == 0)
{
/* Close backup file */
intfstream_close(backup_handle->backup_file);
free(backup_handle->backup_file);
backup_handle->backup_file = NULL;
/* Close core file */
intfstream_flush(backup_handle->core_file);
intfstream_close(backup_handle->core_file);
free(backup_handle->core_file);
backup_handle->core_file = NULL;
backup_handle->success = true;
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Write chunk to core file */
data_written = intfstream_write(backup_handle->core_file, buffer, data_read);
if (data_written != data_read)
{
RARCH_ERR("[core restore] Failed to write to core file: %s\n",
backup_handle->core_path);
backup_handle->status = CORE_RESTORE_END;
break;
}
/* Update progress display */
task_set_progress(task,
(int8_t)((backup_handle->file_data_read * 100) / backup_handle->backup_file_size));
}
break;
case CORE_RESTORE_END:
{
char task_title[PATH_MAX_LENGTH];
task_title[0] = '\0';
/* Set final task title */
task_free_title(task);
if (backup_handle->success)
{
if (backup_handle->crc_match)
strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
msg_hash_to_str(MSG_CORE_RESTORATION_ALREADY_INSTALLED) :
msg_hash_to_str(MSG_CORE_INSTALLATION_ALREADY_INSTALLED),
sizeof(task_title));
else
strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
msg_hash_to_str(MSG_CORE_RESTORATION_COMPLETE) :
msg_hash_to_str(MSG_CORE_INSTALLATION_COMPLETE),
sizeof(task_title));
}
else
strlcpy(task_title, (backup_handle->backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
msg_hash_to_str(MSG_CORE_RESTORATION_FAILED) :
msg_hash_to_str(MSG_CORE_INSTALLATION_FAILED),
sizeof(task_title));
strlcat(task_title, backup_handle->core_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_backup_handle(backup_handle);
}
bool task_push_core_restore(const char *backup_path, const char *dir_libretro,
bool *core_loaded)
{
task_finder_data_t find_data;
core_info_ctx_find_t core_info;
enum core_backup_type backup_type;
const char *core_name = NULL;
retro_task_t *task = NULL;
core_backup_handle_t *backup_handle = NULL;
char core_path[PATH_MAX_LENGTH];
char task_title[PATH_MAX_LENGTH];
core_path[0] = '\0';
task_title[0] = '\0';
/* Sanity check */
if (string_is_empty(backup_path) ||
!path_is_valid(backup_path) ||
string_is_empty(dir_libretro) ||
!core_loaded)
goto error;
/* Ensure core directory is valid */
if (!path_is_directory(dir_libretro))
{
if (!path_mkdir(dir_libretro))
{
RARCH_ERR("[core restore] Failed to create core directory: %s\n", dir_libretro);
goto error;
}
}
/* Get core path */
backup_type = core_backup_get_core_path(
backup_path, dir_libretro, core_path, sizeof(core_path));
if (backup_type == CORE_BACKUP_TYPE_INVALID)
{
const char *backup_filename = path_basename(backup_path);
char msg[PATH_MAX_LENGTH];
msg[0] = '\0';
strlcpy(msg, msg_hash_to_str(MSG_CORE_RESTORATION_INVALID_CONTENT), sizeof(msg));
strlcat(msg, backup_filename ? backup_filename : "", sizeof(msg));
RARCH_ERR("[core restore] Invalid core file selected: %s\n", backup_path);
runloop_msg_queue_push(msg, 1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
goto error;
}
/* Get core name */
core_info.inf = NULL;
core_info.path = core_path;
/* If core is found, use display name */
if (core_info_find(&core_info) &&
core_info.inf->display_name)
core_name = core_info.inf->display_name;
else
{
/* If not, use core file name */
core_name = path_basename(core_path);
if (string_is_empty(core_name))
goto error;
}
/* Check whether core is locked */
if (core_info_get_core_lock(core_path, true))
{
char msg[PATH_MAX_LENGTH];
msg[0] = '\0';
strlcpy(msg,
(backup_type == CORE_BACKUP_TYPE_ARCHIVE) ?
msg_hash_to_str(MSG_CORE_RESTORATION_DISABLED) :
msg_hash_to_str(MSG_CORE_INSTALLATION_DISABLED),
sizeof(msg));
strlcat(msg, core_name, sizeof(msg));
RARCH_ERR("[core restore] Restoration disabled - core is locked: %s\n", core_path);
runloop_msg_queue_push(msg, 1, 100, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
goto error;
}
/* Concurrent backup/restore tasks for the same core
* are not allowed */
find_data.func = task_core_backup_finder;
find_data.userdata = (void*)core_path;
if (task_queue_find(&find_data))
goto error;
/* Configure handle */
backup_handle = (core_backup_handle_t*)calloc(1, sizeof(core_backup_handle_t));
if (!backup_handle)
goto error;
backup_handle->dir_core_assets = NULL;
backup_handle->core_path = strdup(core_path);
backup_handle->core_name = strdup(core_name);
backup_handle->backup_path = strdup(backup_path);
backup_handle->backup_type = backup_type;
backup_handle->backup_mode = CORE_BACKUP_MODE_MANUAL;
backup_handle->auto_backup_history_size = 0;
backup_handle->num_auto_backups_to_remove = 0;
backup_handle->backup_index = 0;
backup_handle->core_file_size = 0;
backup_handle->backup_file_size = 0;
backup_handle->file_data_read = 0;
backup_handle->core_crc = 0;
backup_handle->backup_crc = 0;
backup_handle->crc_match = false;
backup_handle->success = false;
backup_handle->core_file = NULL;
backup_handle->backup_file = NULL;
backup_handle->backup_list = NULL;
backup_handle->status = CORE_RESTORE_GET_CORE_CRC;
/* Create task */
task = task_init();
if (!task)
goto error;
/* Get initial task title */
strlcpy(task_title, msg_hash_to_str(MSG_CORE_BACKUP_SCANNING_CORE),
sizeof(task_title));
strlcat(task_title, backup_handle->core_name, sizeof(task_title));
/* Configure task */
task->handler = task_core_restore_handler;
task->state = backup_handle;
task->title = strdup(task_title);
task->alternative_look = true;
task->progress = 0;
task->callback = cb_task_core_restore;
/* If core to be restored is currently loaded, must
* unload it before pushing the task */
if (rarch_ctl(RARCH_CTL_IS_CORE_LOADED, (void*)core_path))
{
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
*core_loaded = true;
}
else
*core_loaded = false;
/* Push task */
task_queue_push(task);
return true;
error:
/* Clean up task */
if (task)
{
free(task);
task = NULL;
}
/* Clean up handle */
free_core_backup_handle(backup_handle);
return false;
}