diff --git a/Makefile b/Makefile index f405ec1..9a110e6 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ INCLUDES := include ./Libraries/FsLib/Switch/FsLib/include ./Libraries/SDLLib/SD EXEFS_SRC := exefs_src APP_TITLE := JKSV APP_AUTHOR := JK -APP_VERSION := 11.29.2025 +APP_VERSION := 12.01.2025 ROMFS := romfs ICON := icon.jpg diff --git a/include/appstates/BackupMenuState.hpp b/include/appstates/BackupMenuState.hpp index 39b6fb9..3f7595f 100644 --- a/include/appstates/BackupMenuState.hpp +++ b/include/appstates/BackupMenuState.hpp @@ -53,6 +53,7 @@ class BackupMenuState final : public BaseState void save_data_written(); // clang-format off + /// @brief Types of entries corresponding to the menu. enum class MenuEntryType : uint8_t { Null, @@ -60,25 +61,46 @@ class BackupMenuState final : public BaseState Remote }; + /// @brief The menu entry. Stores the type and the index in the respective target (remote/local) struct MenuEntry { MenuEntryType type{}; int index{}; }; + /// @brief Datastruct passed to tasks. struct DataStruct : sys::Task::DataStruct { + /// @brief Pointer to the target user. data::User *user{}; + + /// @brief Pointer to the target title. data::TitleInfo *titleInfo{}; + + /// @brief Pointer to the data for the save. const FsSaveDataInfo *saveInfo{}; - fslib::Path path{}; // This and - std::string remoteName{}; // and this and - remote::Item *remoteItem{}; // this are set when needed. + + /// @brief Pointer to the base path of the instance. Used for constructing auto backups (cause it's easier) + fslib::Path *basePath{}; + + /// @brief Path. This changes according to the context used. + fslib::Path path{}; + + /// @brief Name to use when the file is uploaded remotely. + std::string remoteName{}; + + /// @brief This is set and used when something is being targetted remotely. + remote::Item *remoteItem{}; + + /// @brief This is a pointer to the backup menu instance so refresh() and save_data_written() can be called. BackupMenuState *spawningState{}; - bool killTask = false; // Some tasks use other tasks instead of repeating code. + + /// @brief Signals whether or not the backup fuctions should kill the task when finished. This is so they can be reused. + bool killTask = false; }; // clang-format on + /// @brief This makes this easier to work with and type. using TaskData = std::shared_ptr; private: diff --git a/include/builddate.hpp b/include/builddate.hpp index f894a9c..58bb013 100644 --- a/include/builddate.hpp +++ b/include/builddate.hpp @@ -2,7 +2,7 @@ namespace builddate { - inline constexpr int MONTH = 11; - inline constexpr int DAY = 29; + inline constexpr int MONTH = 12; + inline constexpr int DAY = 1; inline constexpr int YEAR = 2025; } diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index c94d3a2..c9b0cee 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -136,21 +136,27 @@ void BackupMenuState::render() void BackupMenuState::refresh() { - const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); + // Grab pointer to remote service. remote::Storage *remote = remote::get_remote_storage(); + // Re-open and refresh directory. m_directoryListing.open(m_directoryPath); - if (!autoUpload && !m_directoryListing.is_open()) { return; } + // If neither are valid, return. + if (!remote && !m_directoryListing.is_open()) { return; } + // Lock the menu. std::lock_guard menuGuard{sm_menuMutex}; + // Clear the menu and the entry array. sm_backupMenu->reset(); m_menuEntries.clear(); + // Grab the "New" string and add it & it's NULL entry in the vector. const char *optionNew = strings::get_by_name(strings::names::BACKUPMENU_MENU, 0); sm_backupMenu->add_option(optionNew); m_menuEntries.push_back({MenuEntryType::Null, 0}); + // Remote->Local. if (remote) { const std::string_view prefix = remote->get_prefix(); @@ -218,6 +224,7 @@ void BackupMenuState::initialize_task_data() m_dataStruct->user = m_user; m_dataStruct->titleInfo = m_titleInfo; m_dataStruct->saveInfo = m_saveInfo; + m_dataStruct->basePath = &m_directoryPath; m_dataStruct->spawningState = this; } @@ -257,16 +264,27 @@ void BackupMenuState::initialize_remote_storage() void BackupMenuState::name_and_create_backup() { - static constexpr size_t SIZE_NAME_LENGTH = 0x80; + // Size of the buffer for naming backups. + static constexpr size_t SIZE_NAME_LENGTH = 0x80; + + // Zip extension because it's used in multiple spots. static constexpr const char *STRING_ZIP_EXT = ".zip"; + // Remote storage pointer. This is only used for testing if it's valid at this point. remote::Storage *remote = remote::get_remote_storage(); - const bool autoName = config::get_by_key(config::keys::AUTO_NAME_BACKUPS); - const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); - const bool exportZip = autoUpload || config::get_by_key(config::keys::EXPORT_TO_ZIP); - const bool zrHeld = input::button_held(HidNpadButton_ZR); - const bool autoNamed = (autoName || zrHeld); // This can be eval'd here. + // Config needed. + const bool autoName = config::get_by_key(config::keys::AUTO_NAME_BACKUPS); + const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); + const bool exportZip = autoUpload || config::get_by_key(config::keys::EXPORT_TO_ZIP); + + // Input. + const bool zrHeld = input::button_held(HidNpadButton_ZR); + + // Whether or not we should skip the keyboard. + const bool autoNamed = (autoName || zrHeld); // This can be eval'd here. + + // This is the buffer for naming. It's auto filled as [User] - [Date]. char name[SIZE_NAME_LENGTH + 1] = {0}; { const char *nickname = m_user->get_path_safe_nickname(); @@ -274,7 +292,7 @@ void BackupMenuState::name_and_create_backup() std::snprintf(name, SIZE_NAME_LENGTH, "%s - %s", nickname, date.c_str()); } - // Doing this like this so the strings don't linger. + // Doing this like this so the strings don't linger. Dictionary entries for the keyboard. keyboard::Dictionary dictionary{}; { // Array of dictionary strings. @@ -291,37 +309,49 @@ void BackupMenuState::name_and_create_backup() for (const std::string_view word : dictionaryStrings) { dictionary.add_word_to_list(word); } } + // Header string for the keyboard. const char *keyboardHeader = strings::get_by_name(strings::names::KEYBOARD, 0); + // Stores whether or not the input was successful. const bool named = autoNamed || keyboard::get_input(SwkbdType_QWERTY, name, keyboardHeader, name, SIZE_NAME_LENGTH, dictionary); if (!named) { return; } - m_dataStruct->killTask = true; // Need to make sure these kill the task. - const bool hasZipExt = std::strstr(name, STRING_ZIP_EXT); // This might not be the best check. - if (autoUpload && remote) + // Send the signal that the backup task should signal completion. + m_dataStruct->killTask = true; + + // Check for and append zip extension if needed. + const bool hasZipExt = std::strstr(name, STRING_ZIP_EXT); // This might not be the best check. + const bool needsZip = !hasZipExt && (autoUpload || exportZip); + if (needsZip) { std::strncat(name, STRING_ZIP_EXT, SIZE_NAME_LENGTH); } + + // This is used by both if the keep local is enabled anyway. + m_dataStruct->path = m_directoryPath / name; + + if (autoUpload && remote) // If both autoUpload and remote is valid. { - const bool keepLocal = config::get_by_key(config::keys::KEEP_LOCAL_BACKUPS); - if (!hasZipExt) { std::strncat(name, STRING_ZIP_EXT, SIZE_NAME_LENGTH); } - if (keepLocal) { m_dataStruct->path = m_directoryPath / name; } + // Set the name. m_dataStruct->remoteName = name; + // Start the process. ProgressState::create_push_fade(tasks::backup::create_new_backup_remote, m_dataStruct); } else { - fslib::Path target{m_directoryPath / name}; - if (!hasZipExt && (autoUpload || exportZip)) { target += STRING_ZIP_EXT; } // We're going to append zip either way. - - m_dataStruct->path = std::move(target); + // Start the process. ProgressState::create_push_fade(tasks::backup::create_new_backup_local, m_dataStruct); } } void BackupMenuState::confirm_overwrite() { - const int selected = sm_backupMenu->get_selected(); - const MenuEntry &entry = m_menuEntries.at(selected); - const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); + // Grab selected index and entry. + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + + // Whether or not holding is required. + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); + + // Template/format for the confirmation. const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); if (entry.type == MenuEntryType::Remote) @@ -344,63 +374,95 @@ void BackupMenuState::confirm_overwrite() void BackupMenuState::confirm_restore() { - const int selected = sm_backupMenu->get_selected(); + // Tired of typing it out. + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Grab selected. + const int selected = sm_backupMenu->get_selected(); + + // Entry reference. const MenuEntry &entry = m_menuEntries.at(selected); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_RESTORATION); + // Config needed. + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_RESTORATION); + const bool allowSystem = config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM); + + // Template for confirmation string. const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 1); - const bool isSystem = BackupMenuState::user_is_system(); - const bool allowSystem = config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM); + // Whether or not we're working with a system type. + const bool isSystem = BackupMenuState::user_is_system(); + + // Whether or not the restoration is valid under the current conditions. const bool isValidRestore = !isSystem || allowSystem; if (!isValidRestore) { - const char *popSysNotAllowed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 6); - ui::PopMessageManager::push_message(popTicks, popSysNotAllowed); + // Pop and return on trying to restore system when not enabled. + const char *popInvalid = strings::get_by_name(strings::names::BACKUPMENU_POPS, 6); + ui::PopMessageManager::push_message(POP_TICKS, popInvalid); return; } if (entry.type == MenuEntryType::Local) { - const char *popBackupEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 1); - + // Target path we're working with. const fslib::Path target{m_directoryPath / m_directoryListing[entry.index]}; + + // Check if it's a directory. Ensure it has contents either way. const bool targetIsDirectory = fslib::directory_exists(target); const bool backupIsGood = targetIsDirectory ? fs::directory_has_contents(target) : fs::zip_has_contents(target); if (!backupIsGood) { - ui::PopMessageManager::push_message(popTicks, popBackupEmpty); + const char *popEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 1); + ui::PopMessageManager::push_message(POP_TICKS, popEmpty); return; } - m_dataStruct->path = target; + // Move the target to the struct path to pass it. + m_dataStruct->path = std::move(target); + + // Construct our confirmation string. const char *targetName = m_directoryListing[entry.index].get_filename(); std::string query = stringutil::get_formatted_string(confirmTemplate, targetName); + // Begin process. ConfirmProgress::create_push_fade(query, holdRequired, tasks::backup::restore_backup_local, nullptr, m_dataStruct); } else if (entry.type == MenuEntryType::Remote) { - remote::Item *target = m_remoteListing[entry.index]; - std::string query = stringutil::get_formatted_string(confirmTemplate, target->get_name().data()); - m_dataStruct->remoteItem = target; - m_dataStruct->path = m_directoryPath + "//"; // To-do: This is a workaround. + // Pointer to target we're working with. + remote::Item *target = m_remoteListing[entry.index]; + // String for the confirmation + std::string query = stringutil::get_formatted_string(confirmTemplate, target->get_name().data()); + + // Set data needed. + m_dataStruct->remoteItem = target; + m_dataStruct->path = m_directoryPath; + + // Pass go and collect $200. ConfirmProgress::create_push_fade(query, holdRequired, tasks::backup::restore_backup_remote, nullptr, m_dataStruct); } } void BackupMenuState::confirm_delete() { - const int selected = sm_backupMenu->get_selected(); - const MenuEntry &entry = m_menuEntries.at(selected); - const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_DELETION); + // Grab the selected index and entry. + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + + // Whether or not the use desires to be forced to hold the button. + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_DELETION); + + // Template for the confirmation string. const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 2); if (entry.type == MenuEntryType::Local) { - m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; + // Target path to delete. + m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; + + // This is just the name for the confirmation. const char *targetName = m_directoryListing[entry.index].get_filename(); std::string query = stringutil::get_formatted_string(confirmTemplate, targetName); @@ -418,34 +480,49 @@ void BackupMenuState::confirm_delete() void BackupMenuState::upload_backup() { + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Grab pointer to remote. Don't continue if it's not valid. remote::Storage *remote = remote::get_remote_storage(); if (error::is_null(remote)) { return; } + // Grab index, entry. If isn't local, it can't be uploaded. const int selected = sm_backupMenu->get_selected(); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; const MenuEntry &entry = m_menuEntries[selected]; if (entry.type != BackupMenuState::MenuEntryType::Local) { return; } + // Our final target. fslib::Path target{m_directoryPath / m_directoryListing[entry.index]}; + + // If it's a directory, pop and bail. Only zip can be uploaded because it's simpler that way. const bool isDir = fslib::directory_exists(target); if (isDir) { const char *popNotZip = strings::get_by_name(strings::names::BACKUPMENU_POPS, 13); - ui::PopMessageManager::push_message(popTicks, popNotZip); + ui::PopMessageManager::push_message(POP_TICKS, popNotZip); return; } - m_dataStruct->path = std::move(target); + // Move our final path to our data struct. + m_dataStruct->path = std::move(target); + + // Grab the name of the target. If it already exists, patch. If not, new upload. const std::string_view itemName = m_dataStruct->path.get_filename(); const bool exists = remote->file_exists(itemName); if (exists) { + // This is needed, since we're patching and overwriting something. + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); + + // Get the target. remote::Item *remoteItem = remote->get_file_by_name(itemName); const char *queryFormat = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); - std::string query = stringutil::get_formatted_string(queryFormat, itemName.data()); - const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); - m_dataStruct->remoteItem = remoteItem; + const char *itemName = remoteItem->get_name().data(); + // Confirmation string. + std::string query = stringutil::get_formatted_string(queryFormat, itemName); + + // Push the confirmation. ConfirmProgress::create_push_fade(query, holdRequired, tasks::backup::patch_backup, nullptr, m_dataStruct); } else { ProgressState::create_push_fade(tasks::backup::upload_backup, m_dataStruct); } diff --git a/source/tasks/backup.cpp b/source/tasks/backup.cpp index 805b1a0..83ae310 100644 --- a/source/tasks/backup.cpp +++ b/source/tasks/backup.cpp @@ -14,7 +14,6 @@ namespace { constexpr const char *STRING_ZIP_EXT = ".zip"; - constexpr const char *PATH_JKSV_TEMP = "sdmc:/jksvTemp.zip"; // This is named this so if something fails, people know. } // Definitions at bottom. @@ -27,24 +26,42 @@ static fs::ScopedSaveMount create_scoped_mount(const FsSaveDataInfo *saveInfo); void tasks::backup::create_new_backup_local(sys::threadpool::JobData taskData) { + // Cast data to what we actually use. auto castData = std::static_pointer_cast(taskData); - sys::ProgressTask *task = static_cast(castData->task); + // Unpack to pointers and references for easier access. + // Task. + sys::ProgressTask *task = static_cast(castData->task); + + // Data. data::User *user = castData->user; data::TitleInfo *titleInfo = castData->titleInfo; const FsSaveDataInfo *saveInfo = castData->saveInfo; - const fslib::Path &target = castData->path; + + // Filesystem/path. + const fslib::Path &path = castData->path; + + // State to update. BackupMenuState *spawningState = castData->spawningState; - const bool killTask = castData->killTask; + // Whether or not to signal completion at the end. + const bool killTask = castData->killTask; + + // If anything is invalid, bail. if (error::is_null(task)) { return; } - else if (error::is_null({user, titleInfo, saveInfo})) { TASK_FINISH_RETURN(task); } + else if (error::is_null({user, titleInfo, saveInfo}) || !path.is_valid()) { TASK_FINISH_RETURN(task); } - const std::string targetString = target.string(); - const bool hasZipExt = std::strstr(targetString.c_str(), STRING_ZIP_EXT); + // Check if the path has the zip extension. Scoped so the string doesn't linger. Not the best way to detect this btw. + bool hasZipExt{}; + { + const std::string pathString = path.string(); + hasZipExt = pathString.find(STRING_ZIP_EXT) != pathString.npos; + } + + // If it has the zip extension if (hasZipExt) // At this point, this should have the zip extension appended if needed. { - fs::MiniZip zip{target}; + fs::MiniZip zip{path}; if (!zip.is_open()) { TASK_FINISH_RETURN(task); } write_meta_zip(zip, saveInfo); @@ -53,13 +70,14 @@ void tasks::backup::create_new_backup_local(sys::threadpool::JobData taskData) } else { - const bool needsDir = !fslib::directory_exists(target); - const bool createError = needsDir && error::fslib(fslib::create_directory(target)); + // Create the directory if needed. + const bool needsDir = !fslib::directory_exists(path); + const bool createError = needsDir && error::fslib(fslib::create_directory(path)); if (needsDir && createError) { TASK_FINISH_RETURN(task); } - write_meta_file(target, saveInfo); + write_meta_file(path, saveInfo); auto scopedMount = create_scoped_mount(saveInfo); - fs::copy_directory(fs::DEFAULT_SAVE_ROOT, target, task); + fs::copy_directory(fs::DEFAULT_SAVE_ROOT, path, task); } // This is like this so I can reuse this code. @@ -69,37 +87,57 @@ void tasks::backup::create_new_backup_local(sys::threadpool::JobData taskData) void tasks::backup::create_new_backup_remote(sys::threadpool::JobData taskData) { + // This is the temporary name for the backup. + static constexpr const char *BACKUP_PATH = "sdmc:/jksv_backup.zip"; + // I get tired of typing out the entire variable. + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Cast auto castData = std::static_pointer_cast(taskData); - sys::ProgressTask *task = static_cast(castData->task); + // Unpack + // Task. + sys::ProgressTask *task = static_cast(castData->task); + + // Data. data::User *user = castData->user; data::TitleInfo *titleInfo = castData->titleInfo; const FsSaveDataInfo *saveInfo = castData->saveInfo; - const fslib::Path &path = castData->path; - const std::string &remoteName = castData->remoteName; - BackupMenuState *spawningState = castData->spawningState; - const bool &killTask = castData->killTask; - const bool keepLocal = config::get_by_key(config::keys::KEEP_LOCAL_BACKUPS); - remote::Storage *remote = remote::get_remote_storage(); + // FS + const fslib::Path &path = castData->path; + + // Remote + remote::Storage *remote = remote::get_remote_storage(); + const std::string &remoteName = castData->remoteName; + + // State. + BackupMenuState *spawningState = castData->spawningState; + + // Whether or not to signal. + const bool killTask = castData->killTask; + + // Whether or not to keep and move the backup. + const bool keepLocal = config::get_by_key(config::keys::KEEP_LOCAL_BACKUPS); + + // Valid check. if (error::is_null(task)) { return; } else if (error::is_null({user, titleInfo, remote, saveInfo})) { TASK_FINISH_RETURN(task); } - { - TASK_FINISH_RETURN(task); - } - const fslib::Path zipPath{keepLocal ? path : PATH_JKSV_TEMP}; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + // This path is conditional and changes depending on whether or not the keep local option is toggled. + const fslib::Path zipPath{keepLocal ? path : BACKUP_PATH}; + // Attempt to open the ZIP. fs::MiniZip zip{zipPath}; if (!zip.is_open()) { const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); - ui::PopMessageManager::push_message(popTicks, popErrorCreating); + ui::PopMessageManager::push_message(POP_TICKS, popErrorCreating); TASK_FINISH_RETURN(task); } + // Write meta, backup write_meta_zip(zip, saveInfo); { auto scopedMount = create_scoped_mount(saveInfo); @@ -107,18 +145,21 @@ void tasks::backup::create_new_backup_remote(sys::threadpool::JobData taskData) } zip.close(); + // Scoped, update status of the task. { const char *uploadFormat = strings::get_by_name(strings::names::IO_STATUSES, 5); std::string status = stringutil::get_formatted_string(uploadFormat, remoteName.data()); task->set_status(status); } - const bool uploaded = remote->upload_file(zipPath, remoteName, task); + // Upload the file. + const bool uploaded = remote->upload_file(zipPath, remoteName, task); + // Delete if desired. const bool deleteError = uploaded && !keepLocal && error::fslib(fslib::delete_file(zipPath)); if (!uploaded || deleteError) { const char *popErrorUploading = strings::get_by_name(strings::names::BACKUPMENU_POPS, 10); - ui::PopMessageManager::push_message(popTicks, popErrorUploading); + ui::PopMessageManager::push_message(POP_TICKS, popErrorUploading); } if (spawningState) { spawningState->refresh(); } @@ -127,45 +168,73 @@ void tasks::backup::create_new_backup_remote(sys::threadpool::JobData taskData) void tasks::backup::overwrite_backup_local(sys::threadpool::JobData taskData) { + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Cast auto castData = std::static_pointer_cast(taskData); - sys::ProgressTask *task = static_cast(castData->task); - const fslib::Path &target = castData->path; - if (error::is_null(task)) { return; } + // Unpack + // Task. + sys::ProgressTask *task = static_cast(castData->task); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const bool isDirectory = fslib::directory_exists(target); - const bool dirFailed = isDirectory && error::fslib(fslib::delete_directory_recursively(target)); - const bool fileFailed = !isDirectory && error::fslib(fslib::delete_file(target)); + // FS + const fslib::Path &path = castData->path; + + // Bail if invalid. + if (error::is_null(task) || !path.is_valid()) { return; } + + // If the backup is a directory, try to delete it. If not, delete the zip. + const bool isDirectory = fslib::directory_exists(path); + const bool dirFailed = isDirectory && error::fslib(fslib::delete_directory_recursively(path)); + const bool fileFailed = !isDirectory && error::fslib(fslib::delete_file(path)); + // If deletion of the target backup failed, pop, finish. if (dirFailed && fileFailed) { - const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - ui::PopMessageManager::push_message(popTicks, popErrorDeleting); + const char *popError = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + ui::PopMessageManager::push_message(POP_TICKS, popError); TASK_FINISH_RETURN(task); } + // Ensure the new backup kills the task. castData->killTask = true; + tasks::backup::create_new_backup_local(castData); } void tasks::backup::overwrite_backup_remote(sys::threadpool::JobData taskData) { + // This is the temporary path for patch backups. + static constexpr const char *PATCH_PATH = "sdmc:/jksv_patch.zip"; + + // I get tired of typing out the full thing. + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Cast. auto castData = std::static_pointer_cast(taskData); - sys::ProgressTask *task = static_cast(castData->task); - const FsSaveDataInfo *saveInfo = castData->saveInfo; - remote::Item *target = castData->remoteItem; - remote::Storage *remote = remote::get_remote_storage(); + // Unpack. + // Task. + sys::ProgressTask *task = static_cast(castData->task); + // Data. + const FsSaveDataInfo *saveInfo = castData->saveInfo; + + // Remote. + remote::Storage *remote = remote::get_remote_storage(); + remote::Item *target = castData->remoteItem; + + // Bail if invalid. if (error::is_null(task)) { return; } else if (error::is_null({remote, target})) { TASK_FINISH_RETURN(task); } - const fslib::Path tempPath{PATH_JKSV_TEMP}; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + // This is our temporary path to work with. + const fslib::Path tempPath{PATCH_PATH}; + // Create the ZIP. fs::MiniZip zip{tempPath}; if (!zip.is_open()) { TASK_FINISH_RETURN(task); } + // Backup and close. write_meta_zip(zip, saveInfo); { auto scopedMount = create_scoped_mount(saveInfo); @@ -173,19 +242,22 @@ void tasks::backup::overwrite_backup_remote(sys::threadpool::JobData taskData) } zip.close(); + // Scoped. Update status to uploading. { const char *targetName = target->get_name().data(); const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 5); std::string status = stringutil::get_formatted_string(statusFormat, targetName); task->set_status(status); } + // Patch the backup. remote->patch_file(target, tempPath, task); + // Delete the temporary local backup. const bool deleteError = error::fslib(fslib::delete_file(tempPath)); if (deleteError) { const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - ui::PopMessageManager::push_message(popTicks, popErrorDeleting); + ui::PopMessageManager::push_message(POP_TICKS, popErrorDeleting); } task->complete(); @@ -193,44 +265,67 @@ void tasks::backup::overwrite_backup_remote(sys::threadpool::JobData taskData) void tasks::backup::restore_backup_local(sys::threadpool::JobData taskData) { + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Cast auto castData = std::static_pointer_cast(taskData); - sys::ProgressTask *task = static_cast(castData->task); + // Unpack + // Task + sys::ProgressTask *task = static_cast(castData->task); + + // Data data::User *user = castData->user; data::TitleInfo *titleInfo = castData->titleInfo; const FsSaveDataInfo *saveInfo = castData->saveInfo; - const fslib::Path &target = castData->path; + + // FS + const fslib::Path &path = castData->path; + + // State BackupMenuState *spawningState = castData->spawningState; + // Bail on invalid data. if (error::is_null(task)) { return; } else if (error::is_null({user, titleInfo, saveInfo, spawningState})) { TASK_FINISH_RETURN(task); } + // Get the journal size to work with. FsSaveDataExtraData extraData{}; const uint8_t saveType = saveInfo->save_data_type; const bool readExtra = fs::read_save_extra_data(saveInfo, extraData); const int64_t journalSize = readExtra ? extraData.journal_size : titleInfo->get_journal_size(saveType); - const std::string targetString = target.string(); - const bool autoBackup = config::get_by_key(config::keys::AUTO_BACKUP_ON_RESTORE); - const bool isDir = fslib::directory_exists(target); - const bool hasZipExt = std::strstr(targetString.c_str(), STRING_ZIP_EXT); + // Whether or not to create an auto-backup. + const bool autoBackup = config::get_by_key(config::keys::AUTO_BACKUP_ON_RESTORE); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + // Wether or not the backup is a directory. + const bool isDir = fslib::directory_exists(path); + + // Wether or not the it's a file and has the zip extension. + bool hasZipExt{}; + { + const std::string pathString = path.string(); + hasZipExt = !isDir && pathString.find(STRING_ZIP_EXT) != pathString.npos; + } + + // Create the auto-backup. if (autoBackup) { auto_backup(task, castData); } + // Wipe the current save. { auto scopedMount = create_scoped_mount(saveInfo); error::fslib(fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)); error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); } + // If it's not a folder and has the zip extension, try to restore as a ZIP. if (!isDir && hasZipExt) { - fs::MiniUnzip unzip{target}; + fs::MiniUnzip unzip{path}; if (!unzip.is_open()) { const char *popErrorOpenZip = strings::get_by_name(strings::names::EXTRASMENU_POPS, 7); - ui::PopMessageManager::push_message(popTicks, popErrorOpenZip); + ui::PopMessageManager::push_message(POP_TICKS, popErrorOpenZip); TASK_FINISH_RETURN(task); } @@ -240,16 +335,19 @@ void tasks::backup::restore_backup_local(sys::threadpool::JobData taskData) } else if (isDir) { - read_and_process_meta(target, castData, task); + // Directory restore. + read_and_process_meta(path, castData, task); auto scopedMount = create_scoped_mount(saveInfo); - fs::copy_directory_commit(target, fs::DEFAULT_SAVE_ROOT, journalSize, task); + fs::copy_directory_commit(path, fs::DEFAULT_SAVE_ROOT, journalSize, task); } else { + // Just copy the file. auto scopedMount = create_scoped_mount(saveInfo); - fs::copy_file_commit(target, fs::DEFAULT_SAVE_ROOT, journalSize, task); + fs::copy_file_commit(path, fs::DEFAULT_SAVE_ROOT, journalSize, task); } + // Signal data was written if it previously wasn't and refresh it. if (spawningState) { spawningState->save_data_written(); @@ -261,53 +359,72 @@ void tasks::backup::restore_backup_local(sys::threadpool::JobData taskData) void tasks::backup::restore_backup_remote(sys::threadpool::JobData taskData) { + // Download path. + static constexpr const char *DOWNLOAD_PATH = "sdmc:/jksv_download.zip"; + + // Tired of typing. Tired of typing this comment too. + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Cast. auto castData = std::static_pointer_cast(taskData); - sys::ProgressTask *task = static_cast(castData->task); + // Unpack. + // Task + sys::ProgressTask *task = static_cast(castData->task); + + // Data. data::User *user = castData->user; data::TitleInfo *titleInfo = castData->titleInfo; const FsSaveDataInfo *saveInfo = castData->saveInfo; + + // State. BackupMenuState *spawningState = castData->spawningState; - remote::Storage *remote = remote::get_remote_storage(); - const bool autoBackup = config::get_by_key(config::keys::AUTO_BACKUP_ON_RESTORE); + // Remote. + remote::Storage *remote = remote::get_remote_storage(); + const remote::Item *target = castData->remoteItem; + + // Whether or not an auto backup is needed. + const bool autoBackup = config::get_by_key(config::keys::AUTO_BACKUP_ON_RESTORE); + + // Invalid, bail. if (error::is_null(task)) { return; } - else if (error::is_null({user, titleInfo, saveInfo, remote})) { TASK_FINISH_RETURN(task); } + else if (error::is_null({user, titleInfo, saveInfo, remote, target})) { TASK_FINISH_RETURN(task); } - if (autoBackup) { auto_backup(task, castData); } + // Temporary file path for download. + const fslib::Path tempPath{DOWNLOAD_PATH}; + // Scoped status update. { - auto scopedMount = create_scoped_mount(saveInfo); - error::fslib(fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)); - error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); - } + const char *targetName = target->get_name().data(); + const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 4); + std::string status = stringutil::get_formatted_string(statusFormat, targetName); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - remote::Item *target = castData->remoteItem; - const fslib::Path tempPath{PATH_JKSV_TEMP}; - { - const char *name = target->get_name().data(); - const char *downloadingFormat = strings::get_by_name(strings::names::IO_STATUSES, 4); - std::string status = stringutil::get_formatted_string(downloadingFormat, name); task->set_status(status); } + // Download the file first. Only continue if it succeeds. const bool downloaded = remote->download_file(target, tempPath, task); if (!downloaded) { - const char *popErrorDownloading = strings::get_by_name(strings::names::BACKUPMENU_POPS, 9); - ui::PopMessageManager::push_message(popTicks, popErrorDownloading); + const char *popError = strings::get_by_name(strings::names::BACKUPMENU_POPS, 9); + ui::PopMessageManager::push_message(POP_TICKS, popError); TASK_FINISH_RETURN(task); } + // Attempt to open the downloaded file. It it fails, don't continue. fs::MiniUnzip backup{tempPath}; if (!backup.is_open()) { - const char *popErrorOpeningZip = strings::get_by_name(strings::names::BACKUPMENU_POPS, 3); - ui::PopMessageManager::push_message(popTicks, popErrorOpeningZip); + const char *popError = strings::get_by_name(strings::names::BACKUPMENU_POPS, 3); + ui::PopMessageManager::push_message(POP_TICKS, popError); TASK_FINISH_RETURN(task); } + // Create the autobackup. + if (autoBackup) { auto_backup(task, castData); } + + // Clear the save container. { auto scopedMount = create_scoped_mount(saveInfo); const bool deleteError = error::fslib(fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)); @@ -315,27 +432,32 @@ void tasks::backup::restore_backup_remote(sys::threadpool::JobData taskData) if (deleteError || commitError) { const char *popErrorResetting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 2); - ui::PopMessageManager::push_message(popTicks, popErrorResetting); + ui::PopMessageManager::push_message(POP_TICKS, popErrorResetting); TASK_FINISH_RETURN(task); } } + // Read the meta from the backup. read_and_process_meta(backup, castData, task); { + // Get journal size. FsSaveDataExtraData extraData{}; const bool readExtra = fs::read_save_extra_data(saveInfo, extraData); const uint8_t saveType = user->get_account_save_type(); const int64_t journalSize = readExtra ? extraData.journal_size : titleInfo->get_journal_size(saveType); - fs::ScopedSaveMount saveMount{fs::DEFAULT_SAVE_MOUNT, saveInfo}; + + // Temp mount the save, back it up. + auto scopedMount = create_scoped_mount(saveInfo); fs::copy_zip_to_directory(backup, fs::DEFAULT_SAVE_ROOT, journalSize, task); } backup.close(); + // Delete the temporary downloaded file. const bool deleteError = error::fslib(fslib::delete_file(tempPath)); if (deleteError) { const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - ui::PopMessageManager::push_message(popTicks, popErrorDeleting); + ui::PopMessageManager::push_message(POP_TICKS, popErrorDeleting); } spawningState->save_data_written(); @@ -344,16 +466,29 @@ void tasks::backup::restore_backup_remote(sys::threadpool::JobData taskData) void tasks::backup::delete_backup_local(sys::threadpool::JobData taskData) { + static constexpr int POP_TICKS = ui::PopMessageManager::DEFAULT_TICKS; + + // Cast. auto castData = std::static_pointer_cast(taskData); - sys::Task *task = castData->task; - const fslib::Path &path = castData->path; + // Unpack + // Task + sys::Task *task = castData->task; + + // FS + const fslib::Path &path = castData->path; + + // State. BackupMenuState *spawningState = castData->spawningState; + // Config + const bool trashEnabled = config::get_by_key(config::keys::ENABLE_TRASH_BIN); + + // Invalid, bail. if (error::is_null(task)) { return; } else if (error::is_null(spawningState)) { TASK_FINISH_RETURN(task); } - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + // Status. This is basically a flash most of the time. { const std::string pathString = path.string(); const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 3); @@ -361,8 +496,10 @@ void tasks::backup::delete_backup_local(sys::threadpool::JobData taskData) task->set_status(status); } - const bool trashEnabled = config::get_by_key(config::keys::ENABLE_TRASH_BIN); - const bool isDir = fslib::directory_exists(path); + // Wether or not the backup is a folder. + const bool isDir = fslib::directory_exists(path); + + // These errors are set in the conditions and checked later. bool dirError{}, fileError{}; if (trashEnabled) { @@ -379,7 +516,7 @@ void tasks::backup::delete_backup_local(sys::threadpool::JobData taskData) if (dirError || fileError) { const char *popFailed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - ui::PopMessageManager::push_message(popTicks, popFailed); + ui::PopMessageManager::push_message(POP_TICKS, popFailed); } spawningState->refresh(); @@ -388,13 +525,21 @@ void tasks::backup::delete_backup_local(sys::threadpool::JobData taskData) void tasks::backup::delete_backup_remote(sys::threadpool::JobData taskData) { + // Cast auto castData = std::static_pointer_cast(taskData); - sys::Task *task = castData->task; - remote::Item *target = castData->remoteItem; - BackupMenuState *spawningState = castData->spawningState; - remote::Storage *remote = remote::get_remote_storage(); + // Unpack + // Task + sys::Task *task = castData->task; + // Remote. + remote::Storage *remote = remote::get_remote_storage(); + remote::Item *target = castData->remoteItem; + + // State + BackupMenuState *spawningState = castData->spawningState; + + // Invalid, bail if (error::is_null(task)) { return; } else if (error::is_null({target, spawningState, remote})) { TASK_FINISH_RETURN(task); } @@ -467,19 +612,28 @@ void tasks::backup::patch_backup(sys::threadpool::JobData taskData) static void auto_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData) { - if (error::is_null(task)) { return; } - - remote::Storage *remote = remote::get_remote_storage(); + // Unpack + // Data data::User *user = taskData->user; data::TitleInfo *titleInfo = taskData->titleInfo; const FsSaveDataInfo *saveInfo = taskData->saveInfo; - fslib::Path &target = taskData->path; - if (error::is_null({user, titleInfo, saveInfo})) { return; } + // FS + fslib::Path &path = taskData->path; + const fslib::Path &basePath = *taskData->basePath; + + // Remote + remote::Storage *remote = remote::get_remote_storage(); + + // Invalid, bail. + if (error::is_null(task)) { return; } + else if (error::is_null({user, titleInfo, saveInfo}) || !path.is_valid()) { return; } + + // Check if the save actually has data to backup before continuing. { - fs::ScopedSaveMount testMount{fs::DEFAULT_SAVE_MOUNT, saveInfo, false}; + auto scopedMount = create_scoped_mount(saveInfo); const bool hasData = fs::directory_has_contents(fs::DEFAULT_SAVE_ROOT); - if (!testMount.is_open() || !hasData) { return; } + if (!scopedMount.is_open() || !hasData) { return; } } const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); @@ -493,30 +647,22 @@ static void auto_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskD taskData->killTask = false; + // Store and swap the path. + fslib::Path originalPath = std::move(path); + path = basePath / backupName; + if (autoUpload && remote) { + // Store this for uploading. taskData->remoteName = std::move(backupName); + // Create the auto backup. tasks::backup::create_new_backup_remote(taskData); } - else - { - // We're going to get the target dir from the path passed. - const size_t lastSlash = target.find_last_of('/'); - if (lastSlash == target.NOT_FOUND) { return; } + else { tasks::backup::create_new_backup_local(taskData); } - fslib::Path autoTarget{target.sub_path(lastSlash) / backupName}; - - // This is used to move and store the path before using the local auto path. - fslib::Path storePath = std::move(taskData->path); - // Swap em. - taskData->path = std::move(autoTarget); - - tasks::backup::create_new_backup_local(taskData); - - // Swap em back. - taskData->path = std::move(storePath); - } + // Restore the original backup path. + taskData->path = std::move(originalPath); } static bool read_and_process_meta(const fslib::Path &targetDir, BackupMenuState::TaskData taskData, sys::ProgressTask *task)