diff --git a/include/appstates/MainMenuState.hpp b/include/appstates/MainMenuState.hpp index 8c3634e..23bff12 100644 --- a/include/appstates/MainMenuState.hpp +++ b/include/appstates/MainMenuState.hpp @@ -34,6 +34,15 @@ class MainMenuState final : public BaseState /// @brief Calls refresh on on view states in the vector. static void refresh_view_states(); + // clang-format off + struct DataStruct + { + data::UserList userList; + }; + // clang-format on + + using TaskData = std::shared_ptr; + private: /// @brief Render target this state renders to. sdl::SharedTexture m_renderTarget{}; @@ -56,6 +65,9 @@ class MainMenuState final : public BaseState /// @brief X coordinate of the control guide in the bottom right corner. int m_controlGuideX{}; + /// @brief This is the data struct passed to tasks. + MainMenuState::TaskData m_dataStruct{}; + /// @brief Records the size of the sm_users vector. static inline int sm_userCount{}; @@ -77,9 +89,15 @@ class MainMenuState final : public BaseState /// @brief Pushes the icons to the main menu. void initialize_menu(); + /// @brief Initializes the data struct. + void initialize_data_struct(); + /// @brief Pushes the target state to the vector. void push_target_state(); /// @brief Creates the user option state. void create_user_options(); + + /// @brief Backups up all save data for all users. + void backup_all_for_all(); }; diff --git a/include/appstates/UserOptionState.hpp b/include/appstates/UserOptionState.hpp index f705030..876ace4 100644 --- a/include/appstates/UserOptionState.hpp +++ b/include/appstates/UserOptionState.hpp @@ -33,7 +33,7 @@ class UserOptionState final : public BaseState /// @brief Signals to the main update() function that a refresh is needed. /// @note Like this to prevent threading headaches. - void data_and_view_refresh_required(); + void refresh_required(); // clang-format off struct DataStruct @@ -43,6 +43,8 @@ class UserOptionState final : public BaseState }; // clang-format on + using TaskData = std::shared_ptr; + private: /// @brief Pointer to the target user. data::User *m_user{}; diff --git a/include/fs/zip.hpp b/include/fs/zip.hpp index f448a2c..b1bc19c 100644 --- a/include/fs/zip.hpp +++ b/include/fs/zip.hpp @@ -17,7 +17,7 @@ namespace fs /// @note Task is optional. void copy_zip_to_directory(fs::MiniUnzip &source, const fslib::Path &dest, - uint64_t journalSize, + int64_t journalSize, std::string_view commitDevice, sys::ProgressTask *Task = nullptr); diff --git a/include/strings.hpp b/include/strings.hpp index e47be64..34000c7 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -25,6 +25,7 @@ namespace strings static constexpr std::string_view IO_STATUSES = "IOStatuses"; static constexpr std::string_view IO_POPS = "IOPops"; static constexpr std::string_view KEYBOARD = "KeyboardStrings"; + static constexpr std::string_view MAINMENU_CONFS = "MainMenuConfs"; static constexpr std::string_view MAINMENU_POPS = "MainMenuPops"; static constexpr std::string_view ON_OFF = "OnOff"; static constexpr std::string_view REMOTE_POPS = "RemotePops"; diff --git a/include/tasks/mainmenu.hpp b/include/tasks/mainmenu.hpp new file mode 100644 index 0000000..c27c19e --- /dev/null +++ b/include/tasks/mainmenu.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "appstates/MainMenuState.hpp" +#include "sys/sys.hpp" + +namespace tasks +{ + namespace mainmenu + { + void backup_all_for_all_users(sys::ProgressTask *task, MainMenuState::TaskData taskData); + } +} diff --git a/include/tasks/useroptions.hpp b/include/tasks/useroptions.hpp new file mode 100644 index 0000000..97281a4 --- /dev/null +++ b/include/tasks/useroptions.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "appstates/UserOptionState.hpp" +#include "sys/sys.hpp" + +namespace tasks +{ + namespace useroptions + { + // These are reused elsewhere and killing the task is optional. + void backup_all_for_user_local(sys::ProgressTask *task, UserOptionState::TaskData taskData, bool killTask = true); + void backup_all_for_user_remote(sys::ProgressTask *task, UserOptionState::TaskData taskData, bool killTask = true); + void create_all_save_data_for_user(sys::Task *task, UserOptionState::TaskData taskData); + void delete_all_save_data_for_user(sys::Task *task, UserOptionState::TaskData taskData); + } +} diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index eaaccfe..4bc0254 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -83,6 +83,9 @@ "7: Enter a new output folder name for %s.", "8: Enter how much to expand (in MB)." ], + "MainMenuConfs": [ + "0: Are you sure you want to backup the save data for every user on this system? This can take an *extremely* long time!" + ], "MainMenuPops": [ "0: `%s` has no save data!" ], diff --git a/source/appstates/MainMenuState.cpp b/source/appstates/MainMenuState.cpp index 5de3ea3..c1e1905 100644 --- a/source/appstates/MainMenuState.cpp +++ b/source/appstates/MainMenuState.cpp @@ -1,6 +1,7 @@ #include "appstates/MainMenuState.hpp" #include "StateManager.hpp" +#include "appstates/ConfirmState.hpp" #include "appstates/ExtrasMenuState.hpp" #include "appstates/SettingsState.hpp" #include "appstates/TextTitleSelectState.hpp" @@ -14,8 +15,14 @@ #include "sdl.hpp" #include "strings.hpp" #include "stringutil.hpp" +#include "tasks/mainmenu.hpp" #include "ui/PopMessageManager.hpp" +namespace +{ + using ProgressConfirm = ConfirmState; +} + MainMenuState::MainMenuState() : m_renderTarget{sdl::TextureManager::create_load_texture("mainMenuTarget", 200, @@ -27,10 +34,12 @@ MainMenuState::MainMenuState() , m_mainMenu{50, 15, 555} , m_controlGuide{strings::get_by_name(strings::names::CONTROL_GUIDES, 0)} , m_controlGuideX{static_cast(1220 - sdl::text::get_width(22, m_controlGuide))} + , m_dataStruct{std::make_shared()} { MainMenuState::initialize_settings_extras(); MainMenuState::initialize_menu(); MainMenuState::initialize_view_states(); + MainMenuState::initialize_data_struct(); } std::shared_ptr MainMenuState::create() { return std::make_shared(); } @@ -48,11 +57,13 @@ void MainMenuState::update() const bool hasFocus = BaseState::has_focus(); const bool aPressed = input::button_pressed(HidNpadButton_A); const bool xPressed = input::button_pressed(HidNpadButton_X); + const bool yPressed = input::button_pressed(HidNpadButton_Y); const bool toUserOptions = xPressed && selected < sm_userCount; if (aPressed) { MainMenuState::push_target_state(); } - else if (toUserOptions) { MainMenuState::create_user_options(); }; + else if (toUserOptions) { MainMenuState::create_user_options(); } + else if (yPressed) { MainMenuState::backup_all_for_all(); } m_mainMenu.update(hasFocus); } @@ -118,6 +129,8 @@ void MainMenuState::initialize_menu() m_mainMenu.add_option(m_extrasIcon); } +void MainMenuState::initialize_data_struct() { m_dataStruct->userList = sm_users; } + void MainMenuState::push_target_state() { const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; @@ -149,6 +162,11 @@ void MainMenuState::create_user_options() data::User *user = sm_users.at(selected); TitleSelectCommon *titleSelect = static_cast(sm_states[selected].get()); - auto userOptions = std::make_shared(user, titleSelect); - StateManager::push_state(userOptions); + UserOptionState::create_and_push(user, titleSelect); +} + +void MainMenuState::backup_all_for_all() +{ + const char *query = strings::get_by_name(strings::names::MAINMENU_CONFS, 0); + ProgressConfirm::create_and_push(query, true, tasks::mainmenu::backup_all_for_all_users, m_dataStruct); } diff --git a/source/appstates/UserOptionState.cpp b/source/appstates/UserOptionState.cpp index 66ff7b5..a72ca77 100644 --- a/source/appstates/UserOptionState.cpp +++ b/source/appstates/UserOptionState.cpp @@ -13,9 +13,11 @@ #include "fslib.hpp" #include "input.hpp" #include "logger.hpp" +#include "remote/remote.hpp" #include "strings.hpp" #include "stringutil.hpp" #include "sys/sys.hpp" +#include "tasks/useroptions.hpp" #include "ui/PopMessageManager.hpp" namespace @@ -34,14 +36,6 @@ namespace } // namespace -// Declarations here. Defintions after class. -// Backs up all save data for the target user. -static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr dataStruct); -// // Creates all save data for the current user. -static void create_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct); -// // Deletes all save data from the system for the target user. -static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct); - UserOptionState::UserOptionState(data::User *user, TitleSelectCommon *titleSelect) : m_user{user} , m_titleSelect{titleSelect} @@ -112,7 +106,7 @@ void UserOptionState::render() sm_menuPanel->render(NULL, BaseState::has_focus()); } -void UserOptionState::data_and_view_refresh_required() { m_refreshRequired = true; } +void UserOptionState::refresh_required() { m_refreshRequired = true; } void UserOptionState::create_menu_panel() { @@ -139,30 +133,28 @@ void UserOptionState::initialize_data_struct() void UserOptionState::backup_all() { + remote::Storage *remote = remote::get_remote_storage(); + bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); const char *confirmFormat = strings::get_by_name(strings::names::USEROPTION_CONFS, 0); const char *nickname = m_user->get_nickname(); - const std::string queryString = stringutil::get_formatted_string(confirmFormat, nickname); - - auto confirm = std::make_shared(queryString, true, backup_all_for_user, m_dataStruct); - StateManager::push_state(confirm); + const std::string query = stringutil::get_formatted_string(confirmFormat, nickname); + if (remote && autoUpload) + { + ProgressConfirm::create_and_push(query, true, tasks::useroptions::backup_all_for_user_remote, m_dataStruct, false); + } + else { ProgressConfirm::create_and_push(query, true, tasks::useroptions::backup_all_for_user_local, m_dataStruct, false); } } -void UserOptionState::create_save_create() -{ - auto saveCreate = std::make_shared(m_user, m_titleSelect); - StateManager::push_state(saveCreate); -} +void UserOptionState::create_save_create() { SaveCreateState::create_and_push(m_user, m_titleSelect); } void UserOptionState::create_all_save_data() { const char *confirmFormat = strings::get_by_name(strings::names::USEROPTION_CONFS, 1); const char *nickname = m_user->get_nickname(); - const std::string queryString = stringutil::get_formatted_string(confirmFormat, nickname); - - auto confirm = std::make_shared(queryString, true, create_all_save_data_for_user, m_dataStruct); - StateManager::push_state(confirm); + const std::string query = stringutil::get_formatted_string(confirmFormat, nickname); + TaskConfirm::create_and_push(query, true, tasks::useroptions::create_all_save_data_for_user, m_dataStruct); } void UserOptionState::delete_all_save_data() @@ -170,150 +162,8 @@ void UserOptionState::delete_all_save_data() const uint8_t saveType = m_user->get_account_save_type(); if (saveType == FsSaveDataType_System || saveType == FsSaveDataType_SystemBcat) { return; } - const char *confirmFormat = strings::get_by_name(strings::names::USEROPTION_CONFS, 2); - const char *nickname = m_user->get_nickname(); - + const char *confirmFormat = strings::get_by_name(strings::names::USEROPTION_CONFS, 2); + const char *nickname = m_user->get_nickname(); const std::string queryString = stringutil::get_formatted_string(confirmFormat, nickname); - - auto confirm = std::make_shared(queryString, true, delete_all_save_data_for_user, m_dataStruct); - StateManager::push_state(confirm); -} - -static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr dataStruct) -{ - if (error::is_null(task)) { return; } - - data::User *user = dataStruct->user; - - const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP) || config::get_by_key(config::keys::AUTO_UPLOAD); - const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); - const size_t titleCount = user->get_total_data_entries(); - for (size_t i = 0; i < titleCount; i++) - { - const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); - data::TitleInfo *titleInfo = data::get_title_info_by_id(saveInfo->application_id); - if (error::is_null(saveInfo) || error::is_null(titleInfo)) { continue; } - - const fslib::Path targetDir{config::get_working_directory() / titleInfo->get_path_safe_title()}; - const bool targetExists = fslib::directory_exists(targetDir); - const bool targetFailed = !targetExists && error::fslib(fslib::create_directories_recursively(targetDir)); - if (!targetExists && targetFailed) { continue; } - - const bool mountFailed = error::fslib(fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)); - const bool validSave = !mountFailed && fs::directory_has_contents(fs::DEFAULT_SAVE_ROOT); - if (mountFailed || !validSave) - { - fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - continue; - } - - fs::SaveMetaData saveMeta{}; - const bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta); - - fslib::Path backupPath{targetDir / "AUTO - " + user->get_path_safe_nickname() + " - " + stringutil::get_date_string()}; - if (exportZip) - { - backupPath += ".zip"; - fs::MiniZip targetZip{backupPath}; - if (!targetZip.is_open()) - { - fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - continue; - } - - if (hasMeta && targetZip.open_new_file(fs::NAME_SAVE_META)) - { - targetZip.write(&saveMeta, sizeof(fs::SaveMetaData)); - targetZip.close_current_file(); - } - fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, targetZip, task); - } - else - { - { - fslib::File metaFile{backupPath / fs::NAME_SAVE_META, FsOpenMode_Create | FsOpenMode_Write}; - if (hasMeta && metaFile.is_open()) { metaFile.write(&saveMeta, sizeof(fs::SaveMetaData)); } - fs::copy_directory(fs::DEFAULT_SAVE_ROOT, backupPath, task); - } - } - } - task->finished(); -} - -static void create_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct) -{ - if (error::is_null(task)) { return; } - - data::User *user = dataStruct->user; - UserOptionState *spawningState = dataStruct->spawningState; - - auto &titleInfoMap = data::get_title_info_map(); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *statusTemplate = strings::get_by_name(strings::names::USEROPTION_STATUS, 0); - const char *popFailure = strings::get_by_name(strings::names::SAVECREATE_POPS, 1); - const uint8_t saveType = user->get_account_save_type(); - - for (auto &[applicationID, titleInfo] : titleInfoMap) - { - - const bool hasType = titleInfo.has_save_data_type(saveType); - if (!hasType) { continue; } - - { - const std::string status = stringutil::get_formatted_string(statusTemplate, titleInfo.get_title()); - task->set_status(status); - } - - const bool saveCreated = fs::create_save_data_for(user, &titleInfo); - if (!saveCreated) - { - const char *title = titleInfo.get_title(); - const std::string pop = stringutil::get_formatted_string(popFailure, title); - ui::PopMessageManager::push_message(popTicks, pop); - } - } - spawningState->data_and_view_refresh_required(); - task->finished(); -} - -static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct) -{ - if (error::is_null(task)) { return; } - - data::User *user = dataStruct->user; - UserOptionState *spawningState = dataStruct->spawningState; - const char *statusTemplate = strings::get_by_name(strings::names::USEROPTION_STATUS, 1); // Borrowed. No duplication. - const char *popFailed = strings::get_by_name(strings::names::SAVECREATE_POPS, 2); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const size_t totalDataEntries = user->get_total_data_entries(); - std::vector applicationIDs; - - // Check this quick just in case. - if (user->get_account_save_type() == FsSaveDataType_System) { TASK_FINISH_RETURN(task); } - - for (size_t i = 0; i < totalDataEntries; i++) - { - const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); - if (error::is_null(saveInfo) || saveInfo->save_data_type == FsSaveDataType_System) { continue; } - - { - const uint64_t applicationID = user->get_application_id_at(i); - data::TitleInfo *titleInfo = data::get_title_info_by_id(applicationID); - const char *title = titleInfo->get_title(); - const std::string status = stringutil::get_formatted_string(statusTemplate, title); - task->set_status(status); - } - - const bool saveDeleted = fs::delete_save_data(saveInfo); - if (!saveDeleted) - { - ui::PopMessageManager::push_message(popTicks, popFailed); - continue; - } - applicationIDs.push_back(saveInfo->application_id); - } - - for (uint64_t &applicationID : applicationIDs) { user->erase_save_info_by_id(applicationID); } - spawningState->data_and_view_refresh_required(); - task->finished(); + TaskConfirm::create_and_push(queryString, true, tasks::useroptions::delete_all_save_data_for_user, m_dataStruct); } diff --git a/source/config.cpp b/source/config.cpp index 80081cc..525aa92 100644 --- a/source/config.cpp +++ b/source/config.cpp @@ -19,9 +19,9 @@ namespace // Folder path. constexpr std::string_view PATH_CONFIG_FOLDER = "sdmc:/config/JKSV"; // Paths file. Funny name too. - constexpr std::string_view PATH_PATHS_PATH = "sdmc:/config/JKSV/Paths.json"; + constexpr const char *PATH_PATHS_PATH = "sdmc:/config/JKSV/Paths.json"; // Actual config path. - const char *PATH_CONFIG_FILE = "sdmc:/config/JKSV/JKSV.json"; + constexpr const char *PATH_CONFIG_FILE = "sdmc:/config/JKSV/JKSV.json"; /// @brief This map holds the majority of config values. std::map s_configMap; @@ -93,19 +93,19 @@ void config::initialize() return; } - json::Object pathsJSON = json::new_object(json_object_from_file, PATH_PATHS_PATH.data()); + json::Object pathsJSON = json::new_object(json_object_from_file, PATH_PATHS_PATH); if (!pathsJSON) { return; } json_object_iterator pathsIterator = json_object_iter_begin(pathsJSON.get()); json_object_iterator pathsEnd = json_object_iter_end(pathsJSON.get()); while (!json_object_iter_equal(&pathsIterator, &pathsEnd)) { - // Grab these - uint64_t applicationID = std::strtoull(json_object_iter_peek_name(&pathsIterator), NULL, 16); - json_object *path = json_object_iter_peek_value(&pathsIterator); + const char *idString = json_object_iter_peek_name(&pathsIterator); + json_object *pathObject = json_object_iter_peek_value(&pathsIterator); + const uint64_t applicationID = std::strtoull(idString, nullptr, 16); + const char *path = json_object_get_string(pathObject); - // Map em. - s_pathMap[applicationID] = json_object_get_string(path); + s_pathMap[applicationID] = path; json_object_iter_next(&pathsIterator); } @@ -139,23 +139,18 @@ void config::save() { json::Object configJSON = json::new_object(json_object_new_object); - // Add working directory first. json_object *workingDirectory = json_object_new_string(s_workingDirectory.full_path()); json::add_object(configJSON, config::keys::WORKING_DIRECTORY.data(), workingDirectory); - // Loop through map and add it. for (const auto &[key, value] : s_configMap) { json_object *jsonValue = json_object_new_uint64(value); - json::add_object(configJSON, key.c_str(), jsonValue); } - // Add UI scaling. json_object *scaling = json_object_new_double(s_uiAnimationScaling); json::add_object(configJSON, config::keys::UI_ANIMATION_SCALE.data(), scaling); - // Favorites json_object *favoritesArray = json_object_new_array(); for (const uint64_t &titleID : s_favorites) { @@ -166,7 +161,6 @@ void config::save() } json::add_object(configJSON, config::keys::FAVORITES.data(), favoritesArray); - // Same but blacklist json_object *blacklistArray = json_object_new_array(); for (const uint64_t &titleID : s_blacklist) { @@ -177,7 +171,6 @@ void config::save() } json::add_object(configJSON, config::keys::BLACKLIST.data(), blacklistArray); - // Write config file const char *jsonString = json_object_get_string(configJSON.get()); const int64_t configLength = std::char_traits::length(jsonString); fslib::File configFile{PATH_CONFIG_FILE, FsOpenMode_Create | FsOpenMode_Write, configLength}; @@ -222,9 +215,7 @@ void config::add_remove_favorite(uint64_t applicationID) bool config::is_favorite(uint64_t applicationID) { - const auto findID = std::find(s_favorites.begin(), s_favorites.end(), applicationID); - if (findID == s_favorites.end()) { return false; } - return true; + return std::find(s_favorites.begin(), s_favorites.end(), applicationID) != s_favorites.end(); } void config::add_remove_blacklist(uint64_t applicationID) @@ -236,9 +227,7 @@ void config::add_remove_blacklist(uint64_t applicationID) bool config::is_blacklisted(uint64_t applicationID) { - const bool found = std::find(s_blacklist.begin(), s_blacklist.end(), applicationID) == s_blacklist.end(); - if (found) { return false; } - return true; + return std::find(s_blacklist.begin(), s_blacklist.end(), applicationID) != s_blacklist.end(); } void config::add_custom_path(uint64_t applicationID, std::string_view customPath) @@ -247,11 +236,7 @@ void config::add_custom_path(uint64_t applicationID, std::string_view customPath save_custom_paths(); } -bool config::has_custom_path(uint64_t applicationID) -{ - if (s_pathMap.find(applicationID) == s_pathMap.end()) { return false; } - return true; -} +bool config::has_custom_path(uint64_t applicationID) { return s_pathMap.find(applicationID) != s_pathMap.end(); } void config::get_custom_path(uint64_t applicationID, char *pathOut, size_t pathOutSize) { diff --git a/source/curl/curl.cpp b/source/curl/curl.cpp index 106c192..0cf663e 100644 --- a/source/curl/curl.cpp +++ b/source/curl/curl.cpp @@ -74,7 +74,8 @@ size_t curl::download_file_threaded(const char *buffer, size_t size, size_t coun sys::ProgressTask *task = download->task; size_t &offset = download->offset; int64_t &fileSize = download->fileSize; - const size_t downloadSize = size * count; + + const size_t downloadSize = size * count; const std::span bufferSpan{reinterpret_cast(buffer), downloadSize}; { @@ -82,8 +83,9 @@ size_t curl::download_file_threaded(const char *buffer, size_t size, size_t coun condition.wait(bufferLock, [&]() { return bufferReady == false; }); sharedBuffer.append_range(bufferSpan); - const size_t sharedSize = sharedBuffer.size(); - if (sharedSize >= SIZE_DOWNLOAD_THRESHOLD || offset + downloadSize >= fileSize) + const size_t sharedSize = sharedBuffer.size(); + const int64_t nextOffset = offset + downloadSize; + if (sharedSize >= SIZE_DOWNLOAD_THRESHOLD || nextOffset >= fileSize) { bufferReady = true; condition.notify_one(); diff --git a/source/fs/save_data_functions.cpp b/source/fs/save_data_functions.cpp index ef1b840..2296f48 100644 --- a/source/fs/save_data_functions.cpp +++ b/source/fs/save_data_functions.cpp @@ -35,7 +35,6 @@ bool fs::create_save_data_for(data::User *targetUser, data::TitleInfo *titleInfo bool fs::delete_save_data(const FsSaveDataInfo *saveInfo) { - const FsSaveDataType saveType = static_cast(saveInfo->save_data_type); const FsSaveDataSpaceId spaceID = static_cast(saveInfo->save_data_space_id); const bool isSystem = fs::is_system_save_data(saveInfo); if (isSystem) { return false; } diff --git a/source/fs/zip.cpp b/source/fs/zip.cpp index 9e301cb..b461257 100644 --- a/source/fs/zip.cpp +++ b/source/fs/zip.cpp @@ -161,7 +161,7 @@ void fs::copy_directory_to_zip(const fslib::Path &source, fs::MiniZip &dest, sys void fs::copy_zip_to_directory(fs::MiniUnzip &unzip, const fslib::Path &dest, - uint64_t journalSize, + int64_t journalSize, std::string_view commitDevice, sys::ProgressTask *task) { diff --git a/source/tasks/backup.cpp b/source/tasks/backup.cpp index 3ace561..2af9ac5 100644 --- a/source/tasks/backup.cpp +++ b/source/tasks/backup.cpp @@ -33,6 +33,7 @@ void tasks::backup::create_new_backup_local(sys::ProgressTask *task, bool killTask) { if (error::is_null(task)) { return; } + if (error::is_null(user) || error::is_null(titleInfo)) { TASK_FINISH_RETURN(task); } const bool hasZipExt = std::strstr(target.full_path(), STRING_ZIP_EXT); const uint64_t applicationID = titleInfo->get_application_id(); @@ -55,7 +56,8 @@ void tasks::backup::create_new_backup_local(sys::ProgressTask *task, fs::copy_directory(fs::DEFAULT_SAVE_ROOT, target, task); } - spawningState->refresh(); + // This is like this so I can reuse this code. + if (spawningState) { spawningState->refresh(); } if (killTask) { task->finished(); } } @@ -66,12 +68,10 @@ void tasks::backup::create_new_backup_remote(sys::ProgressTask *task, BackupMenuState *spawningState, bool killTask) { + if (error::is_null(task)) { return; } + remote::Storage *remote = remote::get_remote_storage(); - if (error::is_null(task) || error::is_null(user) || error::is_null(titleInfo) || error::is_null(spawningState) || - error::is_null(remote)) - { - return; - } + if (error::is_null(user) || error::is_null(titleInfo) || error::is_null(remote)) { TASK_FINISH_RETURN(task); } const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); @@ -108,7 +108,7 @@ void tasks::backup::create_new_backup_remote(sys::ProgressTask *task, ui::PopMessageManager::push_message(popTicks, popErrorUploading); } - spawningState->refresh(); + if (spawningState) { spawningState->refresh(); } if (killTask) { task->finished(); } } diff --git a/source/tasks/mainmenu.cpp b/source/tasks/mainmenu.cpp new file mode 100644 index 0000000..8527715 --- /dev/null +++ b/source/tasks/mainmenu.cpp @@ -0,0 +1,46 @@ +#include "tasks/mainmenu.hpp" + +#include "config.hpp" +#include "data/data.hpp" +#include "error.hpp" +#include "remote/remote.hpp" +#include "stringutil.hpp" +#include "tasks/useroptions.hpp" // We're going to use the function from here. + +void tasks::mainmenu::backup_all_for_all_users(sys::ProgressTask *task, MainMenuState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + remote::Storage *remote = remote::get_remote_storage(); + const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); + const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP); + data::UserList &userList = taskData->userList; + + // Using this to make use of the user option functions. + auto placeHolder = std::make_shared(); + placeHolder->spawningState = nullptr; + + for (data::User *user : userList) + { + placeHolder->user = user; + + const int64_t titleCount = user->get_total_data_entries(); + for (int64_t i = 0; i < titleCount; i++) + { + const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); + if (error::is_null(saveInfo)) { continue; } + + const uint64_t applicationID = saveInfo->application_id; + data::TitleInfo *titleInfo = data::get_title_info_by_id(applicationID); + if (error::is_null(titleInfo)) { continue; } + + const std::string name = user->get_path_safe_nickname() + " - " + stringutil::get_date_string(); + if (exportZip || autoUpload) { name += ".zip"; } + + if (remote && autoUpload) { tasks::useroptions::backup_all_for_user_remote(task, placeHolder, false); } + else { tasks::useroptions::backup_all_for_user_local(task, placeHolder, false); } + } + } + + task->finished(); +} diff --git a/source/tasks/useroptions.cpp b/source/tasks/useroptions.cpp new file mode 100644 index 0000000..e4ee56e --- /dev/null +++ b/source/tasks/useroptions.cpp @@ -0,0 +1,168 @@ +#include "tasks/useroptions.hpp" + +#include "config.hpp" +#include "error.hpp" +#include "fs/fs.hpp" +#include "remote/remote.hpp" +#include "strings.hpp" +#include "stringutil.hpp" +#include "tasks/backup.hpp" +#include "ui/ui.hpp" + +void tasks::useroptions::backup_all_for_user_local(sys::ProgressTask *task, UserOptionState::TaskData taskData, bool killTask) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + if (error::is_null(user)) { TASK_FINISH_RETURN(task); } + + const fslib::Path workDir = config::get_working_directory(); + const bool exportToZip = config::get_by_key(config::keys::EXPORT_TO_ZIP); + const int titleCount = user->get_total_data_entries(); + for (int i = 0; i < titleCount; i++) + { + const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); + if (error::is_null(saveInfo)) { continue; } + + { + fs::ScopedSaveMount scopedMount{fs::DEFAULT_SAVE_MOUNT, saveInfo}; + if (!scopedMount.is_open() || !fs::directory_has_contents(fs::DEFAULT_SAVE_ROOT)) { continue; } + } + + data::TitleInfo *titleInfo = data::get_title_info_by_id(saveInfo->application_id); + if (error::is_null(titleInfo)) { continue; } + + const fslib::Path targetDir{workDir / titleInfo->get_path_safe_title()}; + const bool targetExists = fslib::directory_exists(targetDir); + const bool createError = !targetExists && error::fslib(fslib::create_directories_recursively(targetDir)); + if (!targetExists && createError) { continue; } + + fslib::Path target{targetDir / user->get_path_safe_nickname() + " - " + stringutil::get_date_string()}; + if (exportToZip) { target += ".zip"; } + + tasks::backup::create_new_backup_local(task, user, titleInfo, target, nullptr, false); + } + + if (killTask) { task->finished() }; +} + +void tasks::useroptions::backup_all_for_user_remote(sys::ProgressTask *task, UserOptionState::TaskData taskData, bool killTask) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(user) || error::is_null(remote)) { TASK_FINISH_RETURN(task); } + + const int titleCount = user->get_total_data_entries(); + for (int i = 0; i < titleCount; i++) + { + const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); + if (error::is_null(saveInfo)) { continue; } + + { + fs::ScopedSaveMount scopedMount{fs::DEFAULT_SAVE_MOUNT, saveInfo}; + if (!scopedMount.is_open() || !fs::directory_has_contents(fs::DEFAULT_SAVE_ROOT)) { continue; } + } + + data::TitleInfo *titleInfo = data::get_title_info_by_id(saveInfo->application_id); + if (error::is_null(saveInfo)) { continue; } + + const std::string_view remoteTitle = + remote->supports_utf8() ? titleInfo->get_title() : titleInfo->get_path_safe_title(); + const bool dirExists = remote->directory_exists(remoteTitle); + const bool created = !dirExists && remote->create_directory(remoteTitle); + if (!dirExists && !created) { continue; } + + remote::Item *target = remote->get_directory_by_name(remoteTitle); + remote->change_directory(target); + + const std::string dateString = stringutil::get_date_string(); + const std::string remoteName = + stringutil::get_formatted_string("%s - %s.zip", user->get_nickname(), dateString.c_str()); + tasks::backup::create_new_backup_remote(task, user, titleInfo, remoteName, nullptr, false); + + remote->return_to_root(); + } + + if (killTask) { task->finished() }; +} + +void tasks::useroptions::create_all_save_data_for_user(sys::Task *task, UserOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + UserOptionState *spawningState = taskData->spawningState; + if (error::is_null(user) || error::is_null(spawningState)) { TASK_FINISH_RETURN(task); } + + auto &titleInfoMap = data::get_title_info_map(); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *statusFormat = strings::get_by_name(strings::names::USEROPTION_STATUS, 0); + const char *popFailure = strings::get_by_name(strings::names::SAVECREATE_POPS, 1); + const uint8_t saveType = user->get_account_save_type(); + + for (auto &[applicationID, titleInfo] : titleInfoMap) + { + const char *title = titleInfo.get_title(); + const bool hasSaveType = titleInfo.has_save_data_type(saveType); + if (!hasSaveType) { continue; } + + { + const std::string status = stringutil::get_formatted_string(statusFormat, title); + task->set_status(status); + } + + const bool saveCreated = fs::create_save_data_for(user, &titleInfo); + if (!saveCreated) + { + const std::string popMessage = stringutil::get_formatted_string(popFailure, title); + ui::PopMessageManager::push_message(popTicks, popMessage); + } + } + + spawningState->refresh_required(); + task->finished(); +} + +void tasks::useroptions::delete_all_save_data_for_user(sys::Task *task, UserOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + UserOptionState *spawningState = taskData->spawningState; + if (error::is_null(user) || error::is_null(spawningState)) { TASK_FINISH_RETURN(task); } + if (user->get_account_save_type() == FsSaveDataType_System) { TASK_FINISH_RETURN(task); } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *statusFormat = strings::get_by_name(strings::names::USEROPTION_STATUS, 1); + const char *popFailed = strings::get_by_name(strings::names::SAVECREATE_POPS, 2); + + std::vector applicationIDs{}; + const int titleCount = user->get_total_data_entries(); + for (int i = 0; i < titleCount; i++) + { + const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); + if (error::is_null(saveInfo) || saveInfo->save_data_type == FsSaveDataType_System) { continue; } + + const uint64_t applicationID = saveInfo->application_id; + { + data::TitleInfo *titleInfo = data::get_title_info_by_id(applicationID); + const char *title = titleInfo->get_title(); + const std::string status = stringutil::get_formatted_string(statusFormat, title); + task->set_status(status); + } + + const bool saveDeleted = fs::delete_save_data(saveInfo); + if (!saveDeleted) + { + ui::PopMessageManager::push_message(popTicks, popFailed); + continue; + } + applicationIDs.push_back(applicationID); + } + + for (const uint64_t &applicationID : applicationIDs) { user->erase_save_info_by_id(applicationID); } + spawningState->refresh_required(); + task->finished(); +}