diff --git a/Libraries/FsLib b/Libraries/FsLib index d07abd3..ebd3747 160000 --- a/Libraries/FsLib +++ b/Libraries/FsLib @@ -1 +1 @@ -Subproject commit d07abd375283a6f206aa21db981a6da7453b9f30 +Subproject commit ebd3747271f3f741f5cf04b8edf55c11f05c652c diff --git a/include/appstates/BackupMenuState.hpp b/include/appstates/BackupMenuState.hpp index d5fa019..be9d2bd 100644 --- a/include/appstates/BackupMenuState.hpp +++ b/include/appstates/BackupMenuState.hpp @@ -4,7 +4,7 @@ #include "fslib.hpp" #include "remote/remote.hpp" #include "sdl.hpp" -#include "system/Timer.hpp" +#include "sys/sys.hpp" #include "ui/Menu.hpp" #include "ui/SlideOutPanel.hpp" #include "ui/TextScroll.hpp" @@ -24,6 +24,12 @@ class BackupMenuState final : public BaseState /// @brief Destructor. This is required even if it doesn't free or do anything. ~BackupMenuState(); + /// @brief Creates and returns a new BackupMenuState. + static std::shared_ptr create(data::User *user, data::TitleInfo *titleInfo); + + /// @brief Creates and pushes a new BackupMenuState to the vector. + static std::shared_ptr create_and_push(data::User *user, data::TitleInfo *titleInfo); + /// @brief Required. Inherited virtual function from AppState. void update() override; diff --git a/include/appstates/BaseTask.hpp b/include/appstates/BaseTask.hpp index 0eeaf5d..b9be706 100644 --- a/include/appstates/BaseTask.hpp +++ b/include/appstates/BaseTask.hpp @@ -1,7 +1,6 @@ #pragma once #include "appstates/BaseState.hpp" -#include "system/Task.hpp" -#include "system/Timer.hpp" +#include "sys/sys.hpp" #include "ui/ColorMod.hpp" #include diff --git a/include/appstates/ConfirmState.hpp b/include/appstates/ConfirmState.hpp index 726206c..530ca3e 100644 --- a/include/appstates/ConfirmState.hpp +++ b/include/appstates/ConfirmState.hpp @@ -8,7 +8,7 @@ #include "logger.hpp" #include "sdl.hpp" #include "strings.hpp" -#include "system/Task.hpp" +#include "sys/sys.hpp" #include "ui/render_functions.hpp" #include @@ -62,15 +62,26 @@ class ConfirmState final : public BaseState /// @brief Required even if it does nothing. ~ConfirmState() {}; - /// @brief Creation function to help clean up code elsewhere. - std::shared_ptr create(std::string_view query, - bool holdRequired, - TaskFunction function, - std::shared_ptr dataStruct) + /// @brief Returns a new ConfirmState. See constructor. + static std::shared_ptr create(std::string_view query, + bool holdRequired, + TaskFunction function, + std::shared_ptr dataStruct) { return std::make_shared(query, holdRequired, function, dataStruct); } + /// @brief Creates and returns a new ConfirmState and pushes it. + static std::shared_ptr create_and_push(std::string_view query, + bool holdRequired, + TaskFunction function, + std::shared_ptr dataStruct) + { + auto newState = create(query, holdRequired, function, dataStruct); + StateManager::push_state(newState); + return newState; + } + /// @brief Just updates the ConfirmState. void update() override { diff --git a/include/appstates/ExtrasMenuState.hpp b/include/appstates/ExtrasMenuState.hpp index 47b5d23..4a500d7 100644 --- a/include/appstates/ExtrasMenuState.hpp +++ b/include/appstates/ExtrasMenuState.hpp @@ -13,6 +13,9 @@ class ExtrasMenuState final : public BaseState /// @brief Required even if nothing happens. ~ExtrasMenuState() {}; + /// @brief Returns a new ExtrasMenuState + static std::shared_ptr create(); + /// @brief Updates the menu. void update() override; diff --git a/include/appstates/FadeInState.hpp b/include/appstates/FadeInState.hpp index c9be99d..45267e7 100644 --- a/include/appstates/FadeInState.hpp +++ b/include/appstates/FadeInState.hpp @@ -1,6 +1,6 @@ #pragma once #include "appstates/BaseState.hpp" -#include "system/Timer.hpp" +#include "sys/sys.hpp" #include @@ -13,6 +13,12 @@ class FadeInState final : public BaseState ~FadeInState() {}; + /// @brief Returns a new fade in state. See constructor. + static std::shared_ptr create(std::shared_ptr nextState); + + /// @brief Creates, returns and pushes a new FadeInState to the statemanager. + static std::shared_ptr create_and_push(std::shared_ptr nextState); + /// @brief Update override. void update() override; diff --git a/include/appstates/MainMenuState.hpp b/include/appstates/MainMenuState.hpp index e8d14a4..8c3634e 100644 --- a/include/appstates/MainMenuState.hpp +++ b/include/appstates/MainMenuState.hpp @@ -16,6 +16,12 @@ class MainMenuState final : public BaseState /// @brief Required even if it does nothing. ~MainMenuState() {}; + /// @brief Returns a new MainMenuState + static std::shared_ptr create(); + + /// @brief Creates and returns a new MainMenuState. Pushes it automatically. + static std::shared_ptr create_and_push(); + /// @brief Runs update routine. void update() override; diff --git a/include/appstates/ProgressState.hpp b/include/appstates/ProgressState.hpp index 22d24d1..ef5dff7 100644 --- a/include/appstates/ProgressState.hpp +++ b/include/appstates/ProgressState.hpp @@ -1,6 +1,7 @@ #pragma once +#include "StateManager.hpp" #include "appstates/BaseTask.hpp" -#include "system/ProgressTask.hpp" +#include "sys/sys.hpp" #include #include @@ -23,6 +24,20 @@ class ProgressState final : public BaseTask /// @brief Required destructor. ~ProgressState() {}; + template + static std::shared_ptr create(void (*function)(sys::ProgressTask *, Args...), Args... args) + { + return std::make_shared(function, std::forward(args)...); + } + + template + static std::shared_ptr create_and_push(void (*function)(sys::ProgressTask *, Args...), Args... args) + { + auto newState = ProgressState::create(function, std::forward(args)...); + StateManager::push_state(newState); + return newState; + } + /// @brief Checks if the thread is finished and deactivates this state. void update() override; diff --git a/include/appstates/SaveCreateState.hpp b/include/appstates/SaveCreateState.hpp index 5d99607..b1ff021 100644 --- a/include/appstates/SaveCreateState.hpp +++ b/include/appstates/SaveCreateState.hpp @@ -20,6 +20,12 @@ class SaveCreateState final : public BaseState /// @brief Required destructor. ~SaveCreateState() {}; + /// @brief Returns a new SaveCreate state. See constructor for arguments. + static std::shared_ptr create(data::User *user, TitleSelectCommon *titleSelect); + + /// @brief Creates, pushes, returns and new SaveCreateState. + static std::shared_ptr create_and_push(data::User *user, TitleSelectCommon *titleSelect); + /// @brief Runs the update routine. void update() override; @@ -27,7 +33,7 @@ class SaveCreateState final : public BaseState void render() override; /// @brief This signals so data and the view can be refreshed on the next update() to avoid threading shenanigans. - void data_and_view_refresh_required(); + void refresh_required(); private: /// @brief Pointer to target user. @@ -47,4 +53,16 @@ class SaveCreateState final : public BaseState /// @brief Shared slide panel all instances use. There's no point in allocating a new one every time. static inline std::unique_ptr sm_slidePanel{}; + + /// @brief Initializes static members if they haven't been already. + void initialize_static_members(); + + /// @brief Retrieves the data needed from data:: + void initialize_title_info_vector(); + + /// @brief Pushes the titles to the menu + void initialize_menu(); + + /// @brief Launches the save creation task. + void create_save_data_for(); }; diff --git a/include/appstates/SettingsState.hpp b/include/appstates/SettingsState.hpp index fcbb0be..3255c50 100644 --- a/include/appstates/SettingsState.hpp +++ b/include/appstates/SettingsState.hpp @@ -13,6 +13,9 @@ class SettingsState final : public BaseState /// @brief Required destructor. ~SettingsState() {}; + /// @brief Returns a new SettingsState. + std::shared_ptr create(); + /// @brief Runs the update routine. void update() override; diff --git a/include/appstates/TaskState.hpp b/include/appstates/TaskState.hpp index d939578..0b6abdd 100644 --- a/include/appstates/TaskState.hpp +++ b/include/appstates/TaskState.hpp @@ -1,6 +1,7 @@ #pragma once +#include "StateManager.hpp" #include "appstates/BaseTask.hpp" -#include "system/Task.hpp" +#include "sys/sys.hpp" #include @@ -22,6 +23,20 @@ class TaskState final : public BaseTask /// @brief Required destructor. ~TaskState() {}; + template + static std::shared_ptr create(void (*function)(sys::Task *, Args...), Args... args) + { + return std::make_shared(function, std::forward(args)...); + } + + template + static std::shared_ptr create_and_push(void (*function)(sys::Task *, Args...), Args... args) + { + auto newState = TaskState::create(function, std::forward(args)...); + StateManager::push_state(newState); + return newState; + } + /// @brief Runs update routine. Waits for thread function to signal finish and deactivates. void update() override; diff --git a/include/appstates/TextTitleSelectState.hpp b/include/appstates/TextTitleSelectState.hpp index f206998..43ab09b 100644 --- a/include/appstates/TextTitleSelectState.hpp +++ b/include/appstates/TextTitleSelectState.hpp @@ -15,6 +15,12 @@ class TextTitleSelectState final : public TitleSelectCommon /// @brief Required destructor. ~TextTitleSelectState() {}; + /// @brief Creates and returns a new TextTitleSelect. See constructor. + static std::shared_ptr create(data::User *user); + + /// @brief Creates, pushes, and returns a new TextTitleSelect. + static std::shared_ptr create_and_push(data::User *user); + /// @brief Runs update routine. void update() override; diff --git a/include/appstates/TitleInfoState.hpp b/include/appstates/TitleInfoState.hpp index d28ff91..0041e8d 100644 --- a/include/appstates/TitleInfoState.hpp +++ b/include/appstates/TitleInfoState.hpp @@ -1,7 +1,7 @@ #pragma once #include "appstates/BaseState.hpp" #include "data/data.hpp" -#include "system/Timer.hpp" +#include "sys/sys.hpp" #include "ui/SlideOutPanel.hpp" #include "ui/TextScroll.hpp" @@ -20,6 +20,12 @@ class TitleInfoState final : public BaseState /// @brief Required destructor. ~TitleInfoState(); + /// @brief Creates a new TitleInfoState. + static std::shared_ptr create(data::User *user, data::TitleInfo *titleInfo); + + /// @brief Creates, pushes, and returns a new TitleInfoState. + static std::shared_ptr create_and_push(data::User *user, data::TitleInfo *titleInfo); + /// @brief Runs update routine. void update() override; @@ -46,8 +52,8 @@ class TitleInfoState final : public BaseState void initialize_static_members(); /// @brief Creates the scrolling text/cheating fields. - void create_info_fields(); + void create_info_scrolls(); /// @brief Helper function for creating text fields. - std::shared_ptr create_new_field(std::string_view text, int y); + std::shared_ptr create_new_scroll(std::string_view text, int y); }; diff --git a/include/appstates/TitleOptionState.hpp b/include/appstates/TitleOptionState.hpp index 3bc097d..ed5f057 100644 --- a/include/appstates/TitleOptionState.hpp +++ b/include/appstates/TitleOptionState.hpp @@ -18,6 +18,16 @@ class TitleOptionState final : public BaseState /// @brief Required destructor. ~TitleOptionState() {}; + /// @brief Returns a new TitleOptionState. See constructor. + static std::shared_ptr create(data::User *user, + data::TitleInfo *titleInfo, + TitleSelectCommon *titleSelect); + + /// @brief Creates, pushes, and returns a new TitleOptionState + static std::shared_ptr create_and_push(data::User *user, + data::TitleInfo *titleInfo, + TitleSelectCommon *titleSelect); + /// @brief Runs update routine. void update() override; @@ -61,12 +71,33 @@ class TitleOptionState final : public BaseState /// @brief This stores whether or a not a refresh is required on the next update(). bool m_refreshRequired{}; - /// @brief This is so it's known whether or not to initialize the static members of this class. - static inline bool sm_initialized{}; - /// @brief Menu used and shared by all instances. - static inline std::unique_ptr sm_titleOptionMenu{}; + static inline std::shared_ptr sm_titleOptionMenu{}; /// @brief This is shared by all instances of this class. static inline std::unique_ptr sm_slidePanel{}; + + void initialize_static_members(); + + void initialize_data_struct(); + + void create_push_info_state(); + + void add_to_blacklist(); + + void change_output_directory(); + + void create_push_file_mode(); + + void delete_all_local_backups(); + + void delete_all_remote_backups(); + + void reset_save_data(); + + void delete_save_from_system(); + + void extend_save_container(); + + void export_svi_file(); }; diff --git a/include/appstates/TitleSelectState.hpp b/include/appstates/TitleSelectState.hpp index 1ddce21..b0b079b 100644 --- a/include/appstates/TitleSelectState.hpp +++ b/include/appstates/TitleSelectState.hpp @@ -15,6 +15,12 @@ class TitleSelectState final : public TitleSelectCommon /// @brief Required destructor. ~TitleSelectState() {}; + /// @brief Returns a new TitleSelect state. + static std::shared_ptr create(data::User *user); + + /// @brief Creates, pushes, and returns a new TitleSelectState. + static std::shared_ptr create_and_push(data::User *user); + /// @brief Runs the update routine. void update() override; diff --git a/include/appstates/UserOptionState.hpp b/include/appstates/UserOptionState.hpp index 71b38b7..f705030 100644 --- a/include/appstates/UserOptionState.hpp +++ b/include/appstates/UserOptionState.hpp @@ -19,6 +19,12 @@ class UserOptionState final : public BaseState /// @brief Required destructor. ~UserOptionState() {}; + /// @brief Returns a new UserOptionState. See constructor. + static std::shared_ptr create(data::User *user, TitleSelectCommon *titleSelect); + + /// @brief Creates, pushes, and returns a new UserOptionState. + static std::shared_ptr create_and_push(data::User *user, TitleSelectCommon *titleSelect); + /// @brief Runs the render routine. void update() override; diff --git a/include/curl/DownloadStruct.hpp b/include/curl/DownloadStruct.hpp index ad6f898..82bc9db 100644 --- a/include/curl/DownloadStruct.hpp +++ b/include/curl/DownloadStruct.hpp @@ -1,7 +1,6 @@ #pragma once #include "fslib.hpp" -#include "system/ProgressTask.hpp" -#include "system/defines.hpp" +#include "sys/sys.hpp" #include #include @@ -14,12 +13,12 @@ namespace curl { std::mutex lock{}; std::condition_variable condition{}; - std::vector sharedBuffer{}; + std::vector sharedBuffer{}; bool bufferReady{}; fslib::File *dest{}; sys::ProgressTask *task{}; size_t offset{}; - size_t fileSize{}; + int64_t fileSize{}; }; // clang-format on } diff --git a/include/curl/UploadStruct.hpp b/include/curl/UploadStruct.hpp index e75bc1b..5e82bbb 100644 --- a/include/curl/UploadStruct.hpp +++ b/include/curl/UploadStruct.hpp @@ -1,6 +1,6 @@ #pragma once #include "fslib.hpp" -#include "system/ProgressTask.hpp" +#include "sys/sys.hpp" namespace curl { diff --git a/include/fs/io.hpp b/include/fs/io.hpp index e02c7c5..469c122 100644 --- a/include/fs/io.hpp +++ b/include/fs/io.hpp @@ -1,6 +1,6 @@ #pragma once #include "fslib.hpp" -#include "system/ProgressTask.hpp" +#include "sys/sys.hpp" #include diff --git a/include/fs/zip.hpp b/include/fs/zip.hpp index 83cbd07..f448a2c 100644 --- a/include/fs/zip.hpp +++ b/include/fs/zip.hpp @@ -2,8 +2,8 @@ // Major to do: Stop using minizip and finish the ZipFile class. #include "fs/MiniUnzip.hpp" #include "fs/MiniZip.hpp" +#include "fs/fs.hpp" #include "fslib.hpp" -#include "system/ProgressTask.hpp" #include diff --git a/include/mathutil.hpp b/include/mathutil.hpp new file mode 100644 index 0000000..20ad04f --- /dev/null +++ b/include/mathutil.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace math +{ + template + class Util + { + public: + static inline Type get_absolute_distance(Type a, Type b) { return a > b ? a - b : b - a; } + }; +} diff --git a/include/remote/GoogleDrive.hpp b/include/remote/GoogleDrive.hpp index 57a9694..279b2be 100644 --- a/include/remote/GoogleDrive.hpp +++ b/include/remote/GoogleDrive.hpp @@ -36,6 +36,11 @@ namespace remote /// @param item Pointer to item containing data to delete the item. bool delete_item(const remote::Item *item) override; + /// @brief Renames an item on Google Drive. + /// @param item Item to rename. + /// @param newName New name of the item. + bool rename_item(remote::Item *item, std::string_view newName) override; + /// @brief Returns whether or not a sign in is required to use drive. AKA the refresh token is missing. bool sign_in_required() const; diff --git a/include/remote/Storage.hpp b/include/remote/Storage.hpp index 0004d86..194cc48 100644 --- a/include/remote/Storage.hpp +++ b/include/remote/Storage.hpp @@ -2,7 +2,7 @@ #include "curl/curl.hpp" #include "fslib.hpp" #include "remote/Item.hpp" -#include "system/ProgressTask.hpp" +#include "sys/sys.hpp" #include #include @@ -85,6 +85,11 @@ namespace remote /// @param item Item to delete. virtual bool delete_item(const remote::Item *item) = 0; + /// @brief Renames a file on the remote server. + /// @param item Target item to rename. + /// @param newName New name of the target item. + virtual bool rename_item(remote::Item *item, std::string_view newName) = 0; + /// @brief Returns whether or not the remote storage type supports UTF-8 for names or requires path safe titles. bool supports_utf8() const; diff --git a/include/remote/WebDav.hpp b/include/remote/WebDav.hpp index f3d2d0e..eb9e6fb 100644 --- a/include/remote/WebDav.hpp +++ b/include/remote/WebDav.hpp @@ -38,6 +38,11 @@ namespace remote /// @param item Item to delete. bool delete_item(const remote::Item *item) override; + /// @brief Renames an item on WebDav. + /// @param item Item to rename. + /// @param newName New name of the item. + bool rename_item(remote::Item *item, std::string_view newName) override; + private: /// @brief Origin or server address. std::string m_origin{}; diff --git a/include/remote/remote.hpp b/include/remote/remote.hpp index a78074d..18c4911 100644 --- a/include/remote/remote.hpp +++ b/include/remote/remote.hpp @@ -9,6 +9,9 @@ namespace remote static constexpr std::string_view PATH_GOOGLE_DRIVE_CONFIG = "sdmc:/config/JKSV/client_secret.json"; static constexpr std::string_view PATH_WEBDAV_CONFIG = "sdmc:/config/JKSV/webdav.json"; + /// @brief Returns whether or not the console has an active internet connection. + bool has_internet_connection(); + /// @brief Initializes the Storage instance to Google Drive. void initialize_google_drive(); diff --git a/include/strings.hpp b/include/strings.hpp index 584714a..e47be64 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -27,6 +27,7 @@ namespace strings static constexpr std::string_view KEYBOARD = "KeyboardStrings"; static constexpr std::string_view MAINMENU_POPS = "MainMenuPops"; static constexpr std::string_view ON_OFF = "OnOff"; + static constexpr std::string_view REMOTE_POPS = "RemotePops"; static constexpr std::string_view SAVECREATE_POPS = "SaveCreatePops"; static constexpr std::string_view SAVE_DATA_TYPES = "SaveDataTypes"; static constexpr std::string_view SETTINGS_DESCRIPTIONS = "SettingsDescriptions"; diff --git a/include/system/ProgressTask.hpp b/include/sys/ProgressTask.hpp similarity index 98% rename from include/system/ProgressTask.hpp rename to include/sys/ProgressTask.hpp index e10a8fc..bb75689 100644 --- a/include/system/ProgressTask.hpp +++ b/include/sys/ProgressTask.hpp @@ -1,5 +1,5 @@ #pragma once -#include "system/Task.hpp" +#include "sys/Task.hpp" namespace sys { diff --git a/include/system/Task.hpp b/include/sys/Task.hpp similarity index 88% rename from include/system/Task.hpp rename to include/sys/Task.hpp index f7634a5..05ac60d 100644 --- a/include/system/Task.hpp +++ b/include/sys/Task.hpp @@ -3,6 +3,11 @@ #include #include +// This macro helps keep things a bit easier to read and cuts down on repetition. +#define TASK_FINISH_RETURN(x) \ + x->finished(); \ + return + namespace sys { /// @brief Class that runs tasks in a thread and automatically deactivates when finished. diff --git a/include/system/Timer.hpp b/include/sys/Timer.hpp similarity index 100% rename from include/system/Timer.hpp rename to include/sys/Timer.hpp diff --git a/include/sys/defines.hpp b/include/sys/defines.hpp new file mode 100644 index 0000000..501b6d6 --- /dev/null +++ b/include/sys/defines.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace sys +{ + using byte = unsigned char; +} diff --git a/include/sys/sys.hpp b/include/sys/sys.hpp new file mode 100644 index 0000000..c979d72 --- /dev/null +++ b/include/sys/sys.hpp @@ -0,0 +1,5 @@ +#pragma once +#include "sys/ProgressTask.hpp" +#include "sys/Task.hpp" +#include "sys/Timer.hpp" +#include "sys/defines.hpp" diff --git a/include/system/defines.hpp b/include/system/defines.hpp deleted file mode 100644 index 501af09..0000000 --- a/include/system/defines.hpp +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -using byte = unsigned char; diff --git a/include/system/system.hpp b/include/system/system.hpp deleted file mode 100644 index dfbed29..0000000 --- a/include/system/system.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once -#include "system/ProgressTask.hpp" -#include "system/Task.hpp" -#include "system/Timer.hpp" diff --git a/include/tasks/backup.hpp b/include/tasks/backup.hpp index ab4a0f1..a405b65 100644 --- a/include/tasks/backup.hpp +++ b/include/tasks/backup.hpp @@ -1,7 +1,6 @@ #pragma once #include "appstates/BackupMenuState.hpp" -#include "system/ProgressTask.hpp" -#include "system/Task.hpp" +#include "sys/sys.hpp" #include @@ -37,5 +36,8 @@ namespace tasks /// @brief Uploads a backup void upload_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData); + + /// @brief Patches a pre-existing backup on the remote storage. + void patch_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData); } } diff --git a/include/tasks/savecreate.hpp b/include/tasks/savecreate.hpp new file mode 100644 index 0000000..e3ba05b --- /dev/null +++ b/include/tasks/savecreate.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "appstates/SaveCreateState.hpp" +#include "data/data.hpp" +#include "sys/sys.hpp" + +namespace tasks +{ + namespace savecreate + { + void create_save_data_for(sys::Task *task, + data::User *user, + data::TitleInfo *titleInfo, + SaveCreateState *spawningState); + } +} diff --git a/include/tasks/titleoptions.hpp b/include/tasks/titleoptions.hpp new file mode 100644 index 0000000..b6b29d3 --- /dev/null +++ b/include/tasks/titleoptions.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "appstates/TitleOptionState.hpp" +#include "sys/sys.hpp" + +namespace tasks +{ + namespace titleoptions + { + /// @brief Adds a title to the blacklist. Needs to be task formatted to work with confirmations. + void blacklist_title(sys::Task *task, TitleOptionState::TaskData taskData); + + /// @brief Wipes deletes all local backups for the current title. + void delete_all_local_backups_for_title(sys::Task *task, TitleOptionState::TaskData taskData); + + /// @brief Deletes all backups found on the remote storage service. + void delete_all_remote_backups_for_title(sys::Task *task, TitleOptionState::TaskData taskData); + + /// @brief Resets save data for the current title. + void reset_save_data(sys::Task *task, TitleOptionState::TaskData taskData); + + /// @brief Deletes the save data from the system the same way Data Management does. + void delete_save_data_from_system(sys::Task *task, TitleOptionState::TaskData taskData); + + /// @brief Extends the save container for the current save info. + void extend_save_data(sys::Task *task, TitleOptionState::TaskData taskData); + } +} diff --git a/include/ui/PopMessageManager.hpp b/include/ui/PopMessageManager.hpp index 80c523f..7e3b5f3 100644 --- a/include/ui/PopMessageManager.hpp +++ b/include/ui/PopMessageManager.hpp @@ -1,5 +1,5 @@ #pragma once -#include "system/Timer.hpp" +#include "sys/sys.hpp" #include #include diff --git a/include/ui/SlideOutPanel.hpp b/include/ui/SlideOutPanel.hpp index b4ffb5b..3b0a415 100644 --- a/include/ui/SlideOutPanel.hpp +++ b/include/ui/SlideOutPanel.hpp @@ -83,7 +83,16 @@ namespace ui /// @brief Render target if panel. sdl::SharedTexture m_renderTarget{}; + /// @brief This is so I don't need to fetch the scaling every loop. + double m_scaling{}; + /// @brief Vector of elements. std::vector> m_elements{}; + + void slide_out_left(); + + void slide_out_right(); + + int get_absolute_x_distance(); }; } // namespace ui diff --git a/include/ui/TextScroll.hpp b/include/ui/TextScroll.hpp index 82c8aa4..1cd3ed5 100644 --- a/include/ui/TextScroll.hpp +++ b/include/ui/TextScroll.hpp @@ -1,6 +1,6 @@ #pragma once #include "sdl.hpp" -#include "system/Timer.hpp" +#include "sys/sys.hpp" #include "ui/Element.hpp" #include @@ -8,7 +8,7 @@ namespace ui { /// @brief This is used in multiple places and rewriting it over and over is a waste of time. - class TextScroll : public ui::Element + class TextScroll final : public ui::Element { public: /// @brief This is only here so I can get around the backup menu having static members. diff --git a/include/ui/TitleTile.hpp b/include/ui/TitleTile.hpp index 9b9f11e..273b85d 100644 --- a/include/ui/TitleTile.hpp +++ b/include/ui/TitleTile.hpp @@ -10,11 +10,11 @@ namespace ui /// @brief Constructor. /// @param isFavorite Whether the title is a favorite and should have the little heart rendered. /// @param icon Shared texture pointer to the icon. - TitleTile(bool isFavorite, sdl::SharedTexture icon); + TitleTile(bool isFavorite, int index, sdl::SharedTexture icon); /// @brief Runs the update routine. /// @param isSelected Whether or not the tile is selected and needs to expand. - void update(bool isSelected); + void update(int selected); /// @brief Runs the render routine. /// @param target Target to render to. @@ -36,11 +36,16 @@ namespace ui private: /// @brief Width in pixels to render icon at. int m_renderWidth = 128; + /// @brief Height in pixels to render icon at. int m_renderHeight = 128; + /// @brief Whether or not the title is a favorite. - bool m_isFavorite = false; + bool m_isFavorite{}; + + int m_index{}; + /// @brief Title's icon texture. - sdl::SharedTexture m_icon = nullptr; + sdl::SharedTexture m_icon{}; }; } // namespace ui diff --git a/include/ui/TitleView.hpp b/include/ui/TitleView.hpp index a7b4d13..070d7c0 100644 --- a/include/ui/TitleView.hpp +++ b/include/ui/TitleView.hpp @@ -60,5 +60,11 @@ namespace ui /// @brief Vector of selection tiles. std::vector m_titleTiles{}; + + void handle_input(); + + void handle_scrolling(); + + void update_tiles(); }; } // namespace ui diff --git a/include/ui/ui.hpp b/include/ui/ui.hpp new file mode 100644 index 0000000..cbbcdd8 --- /dev/null +++ b/include/ui/ui.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "ui/ColorMod.hpp" +#include "ui/Element.hpp" +#include "ui/IconMenu.hpp" +#include "ui/Menu.hpp" +#include "ui/PopMessageManager.hpp" +#include "ui/SlideOutPanel.hpp" +#include "ui/TextScroll.hpp" +#include "ui/TitleTile.hpp" +#include "ui/TitleView.hpp" +#include "ui/render_functions.hpp" diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index eaea153..eaaccfe 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -90,6 +90,9 @@ "0: Off", "1: >On>" ], + "RemotePops": [ + "0: No internet connection available!" + ], "SaveCreatePops": [ "0: Save data created for #%s#!", "1: Error creating save data!", @@ -169,7 +172,7 @@ "3: Are you sure you want to delete `%s`'s save data for #%s#? *This will permanently delete it from the system.*" ], "TitleOptionPops": [ - "0: All backups deleted for `%s`!", + "0: All backups deleted for #%s#!", "1: Failed to delete all backups!", "2: Error resetting save data!", "3: Save data successfully reset!", @@ -193,11 +196,12 @@ "1: Blacklist Title", "2: Change Output folder", "3: Open in File Mode", - "4: Delete all save backups", - "5: Reset save data.", - "6: Delete save data from system", - "7: Extend save data", - "8: Export SVI file" + "4: Delete all local backups", + "5: Delete all remote backups", + "6: Reset save data.", + "7: Delete save data from system", + "8: Extend save data", + "9: Export SVI file" ], "TranslationInfo": [ "0: Translated by: %s", diff --git a/source/JKSV.cpp b/source/JKSV.cpp index 9ff7836..54e4e65 100644 --- a/source/JKSV.cpp +++ b/source/JKSV.cpp @@ -71,7 +71,7 @@ JKSV::JKSV() ABORT_ON_FAILURE(data::initialize(false)); // Push initial main menu state. - auto mainMenu = std::make_shared(); + auto mainMenu = MainMenuState::create(); StateManager::push_state(mainMenu); // Init drive or webdav. @@ -165,6 +165,7 @@ bool JKSV::initialize_services() serviceInit = serviceInit && initialize_service(setInitialize, "Set"); serviceInit = serviceInit && initialize_service(setsysInitialize, "SetSys"); serviceInit = serviceInit && initialize_service(socketInitializeDefault, "Socket"); + serviceInit = serviceInit && initialize_service(nifmInitialize, "NIFM", NifmServiceType_User); return serviceInit; } @@ -206,6 +207,7 @@ void JKSV::add_color_chars() void JKSV::exit_services() { + nifmExit(); socketExit(); setsysExit(); setExit(); diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index e0ef538..6e0416b 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -13,7 +13,7 @@ #include "sdl.hpp" #include "strings.hpp" #include "stringutil.hpp" -#include "system/system.hpp" +#include "sys/sys.hpp" #include "tasks/backup.hpp" #include "ui/PopMessageManager.hpp" #include "ui/TextScroll.hpp" @@ -56,6 +56,18 @@ BackupMenuState::~BackupMenuState() if (remote && remote->is_initialized()) { remote->return_to_root(); } } +std::shared_ptr BackupMenuState::create(data::User *user, data::TitleInfo *titleInfo) +{ + return std::make_shared(user, titleInfo); +} + +std::shared_ptr BackupMenuState::create_and_push(data::User *user, data::TitleInfo *titleInfo) +{ + auto newState = BackupMenuState::create(user, titleInfo); + StateManager::push_state(newState); + return newState; +} + void BackupMenuState::update() { const bool hasFocus = BaseState::has_focus(); @@ -187,7 +199,6 @@ void BackupMenuState::initialize_info_string() const std::string infoString = stringutil::get_formatted_string("`%s` - %s", nickname, title); m_titleScroll.create(infoString, 8, 8, sm_panelWidth - 16, 30, 22, colors::WHITE, colors::TRANSPARENT); - // m_titleScroll.create(infoString, 22, sm_panelWidth - 16, 8, 8, true, colors::WHITE, colors::TRANSPARENT); } void BackupMenuState::save_data_check() @@ -223,37 +234,35 @@ void BackupMenuState::name_and_create_backup() static constexpr size_t SIZE_NAME_LENGTH = 0x80; static constexpr const char *STRING_ZIP_EXT = ".zip"; - 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 char *keyboardHeader = strings::get_by_name(strings::names::KEYBOARD, 0); - const bool autoNamed = (autoName || zrHeld); // This can be eval'd here. - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); + 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. + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; char name[SIZE_NAME_LENGTH + 1] = {0}; - { const char *nickname = m_user->get_path_safe_nickname(); const std::string date = stringutil::get_date_string(); std::snprintf(name, SIZE_NAME_LENGTH, "%s - %s", nickname, date.c_str()); } + + const char *keyboardHeader = strings::get_by_name(strings::names::KEYBOARD, 0); const bool named = autoNamed || keyboard::get_input(SwkbdType_QWERTY, name, keyboardHeader, name, SIZE_NAME_LENGTH); if (!named) { return; } const bool hasZipExt = std::strstr(name, STRING_ZIP_EXT); // This might not be the best check. - std::shared_ptr backupTask{}; if (autoUpload && remote) { if (!hasZipExt) { std::strncat(name, STRING_ZIP_EXT, SIZE_NAME_LENGTH); } - backupTask = std::make_shared(tasks::backup::create_new_backup_remote, - m_user, - m_titleInfo, - std::string{name}, - this, - true); + ProgressState::create_and_push(tasks::backup::create_new_backup_remote, + m_user, + m_titleInfo, + std::string{name}, + this, + true); } else { @@ -264,14 +273,12 @@ void BackupMenuState::name_and_create_backup() if (exportZip && !hasZipExt) { target += STRING_ZIP_EXT; } else if (!exportZip && dirNeeded && dirError) { + const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); ui::PopMessageManager::push_message(popTicks, popErrorCreating); return; } - - backupTask = - std::make_shared(tasks::backup::create_new_backup_local, m_user, m_titleInfo, target, this, true); + ProgressState::create_and_push(tasks::backup::create_new_backup_local, m_user, m_titleInfo, target, this, true); } - if (backupTask) { StateManager::push_state(backupTask); } } void BackupMenuState::confirm_overwrite() @@ -281,47 +288,45 @@ void BackupMenuState::confirm_overwrite() const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); - std::shared_ptr confirm{}; if (entry.type == MenuEntryType::Remote) { m_dataStruct->remoteItem = m_remoteListing.at(entry.index); const char *itemName = m_dataStruct->remoteItem->get_name().data(); const std::string query = stringutil::get_formatted_string(confirmTemplate, itemName); - confirm = std::make_shared(query, holdRequired, tasks::backup::overwrite_backup_remote, m_dataStruct); + ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::overwrite_backup_remote, m_dataStruct); } else if (entry.type == MenuEntryType::Local) { m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; const char *targetName = m_directoryListing[entry.index]; const std::string query = stringutil::get_formatted_string(confirmTemplate, targetName); - confirm = std::make_shared(query, holdRequired, tasks::backup::overwrite_backup_local, m_dataStruct); + ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::overwrite_backup_local, m_dataStruct); } - - if (confirm) { StateManager::push_state(confirm); } } void BackupMenuState::confirm_restore() { - const int selected = sm_backupMenu->get_selected(); - 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); - const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 1); - const char *popBackupEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 1); - const char *popSysNotAllowed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 6); + const int selected = sm_backupMenu->get_selected(); + 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); + 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); 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); return; } - std::shared_ptr confirm{}; if (entry.type == MenuEntryType::Local) { + const char *popBackupEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 1); + const fslib::Path target = m_directoryPath / m_directoryListing[entry.index]; const bool targetIsDirectory = fslib::directory_exists(target); const bool backupIsGood = targetIsDirectory ? fs::directory_has_contents(target) : fs::zip_has_contents(target); @@ -332,17 +337,17 @@ void BackupMenuState::confirm_restore() } m_dataStruct->path = target; const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); - confirm = std::make_shared(query, holdRequired, tasks::backup::restore_backup_local, m_dataStruct); + + ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::restore_backup_local, m_dataStruct); } else if (entry.type == MenuEntryType::Remote) { remote::Item *target = m_remoteListing[entry.index]; const std::string query = stringutil::get_formatted_string(confirmTemplate, target->get_name().data()); m_dataStruct->remoteItem = target; - confirm = std::make_shared(query, holdRequired, tasks::backup::restore_backup_remote, m_dataStruct); - } - if (confirm) { StateManager::push_state(confirm); } + ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::restore_backup_remote, m_dataStruct); + } } void BackupMenuState::confirm_delete() @@ -356,18 +361,19 @@ void BackupMenuState::confirm_delete() if (entry.type == MenuEntryType::Local) { m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; - const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); - confirm = std::make_shared(query, holdRequired, tasks::backup::delete_backup_local, m_dataStruct); + const char *targetName = m_directoryListing[entry.index]; + const std::string query = stringutil::get_formatted_string(confirmTemplate, targetName); + + TaskConfirm::create_and_push(query, holdRequired, tasks::backup::delete_backup_local, m_dataStruct); } else if (entry.type == MenuEntryType::Remote) { m_dataStruct->remoteItem = m_remoteListing.at(entry.index); - const std::string query = - stringutil::get_formatted_string(confirmTemplate, m_dataStruct->remoteItem->get_name().data()); - confirm = std::make_shared(query, holdRequired, tasks::backup::delete_backup_remote, m_dataStruct); - } + const char *itemName = m_dataStruct->remoteItem->get_name().data(); + const std::string query = stringutil::get_formatted_string(confirmTemplate, itemName); - StateManager::push_state(confirm); + TaskConfirm::create_and_push(query, holdRequired, tasks::backup::delete_backup_remote, m_dataStruct); + } } void BackupMenuState::upload_backup() @@ -376,22 +382,34 @@ void BackupMenuState::upload_backup() if (error::is_null(remote)) { return; } const int selected = sm_backupMenu->get_selected(); - const MenuEntry &entry = m_menuEntries.at(selected); const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popNotZip = strings::get_by_name(strings::names::BACKUPMENU_POPS, 13); + const MenuEntry &entry = m_menuEntries[selected]; if (entry.type != BackupMenuState::MenuEntryType::Local) { return; } - fslib::Path target = m_directoryPath / m_directoryListing[entry.index]; - const bool isDir = fslib::directory_exists(target); + const char *targetName = m_directoryListing[entry.index]; + fslib::Path target = m_directoryPath / targetName; + 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); return; } - m_dataStruct->path = std::move(target); - auto upload = std::make_shared(tasks::backup::upload_backup, m_dataStruct); - StateManager::push_state(upload); + m_dataStruct->path = std::move(target); + const std::string_view itemName = m_dataStruct->path.get_filename(); + const bool exists = remote->file_exists(itemName); + if (exists) + { + remote::Item *remoteItem = remote->get_file_by_name(itemName); + const char *queryFormat = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); + const 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; + + ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::patch_backup, m_dataStruct); + } + else { ProgressState::create_and_push(tasks::backup::upload_backup, m_dataStruct); } } void BackupMenuState::pop_save_empty() diff --git a/source/appstates/BaseTask.cpp b/source/appstates/BaseTask.cpp index 9d443a2..998bf31 100644 --- a/source/appstates/BaseTask.cpp +++ b/source/appstates/BaseTask.cpp @@ -38,6 +38,7 @@ void BaseTask::update() void BaseTask::render_loading_glyph() { - const char *currentFrame = sm_glyphArray.at(m_currentFrame).data(); + const char *currentFrame = sm_glyphArray[m_currentFrame].data(); + sdl::text::render(NULL, 56, 673, 32, sdl::text::NO_TEXT_WRAP, m_colorMod, currentFrame); } diff --git a/source/appstates/ExtrasMenuState.cpp b/source/appstates/ExtrasMenuState.cpp index cbadf4d..0d9ac26 100644 --- a/source/appstates/ExtrasMenuState.cpp +++ b/source/appstates/ExtrasMenuState.cpp @@ -38,6 +38,8 @@ ExtrasMenuState::ExtrasMenuState() ExtrasMenuState::initialize_menu(); } +std::shared_ptr create() { return std::make_shared(); } + void ExtrasMenuState::update() { const bool hasFocus = BaseState::has_focus(); diff --git a/source/appstates/FadeInState.cpp b/source/appstates/FadeInState.cpp index f94461a..b1cbb88 100644 --- a/source/appstates/FadeInState.cpp +++ b/source/appstates/FadeInState.cpp @@ -9,6 +9,18 @@ FadeInState::FadeInState(std::shared_ptr nextState) m_fadeTimer.start(1); } +std::shared_ptr FadeInState::create(std::shared_ptr nextState) +{ + return std::make_shared(nextState); +} + +std::shared_ptr FadeInState::create_and_push(std::shared_ptr nextState) +{ + auto newState = FadeInState::create(nextState); + StateManager::push_state(newState); + return newState; +} + void FadeInState::update() { if (m_alpha == 0x00) diff --git a/source/appstates/MainMenuState.cpp b/source/appstates/MainMenuState.cpp index 072d824..5de3ea3 100644 --- a/source/appstates/MainMenuState.cpp +++ b/source/appstates/MainMenuState.cpp @@ -33,6 +33,15 @@ MainMenuState::MainMenuState() MainMenuState::initialize_view_states(); } +std::shared_ptr MainMenuState::create() { return std::make_shared(); } + +std::shared_ptr MainMenuState::create_and_push() +{ + auto newState = MainMenuState::create(); + StateManager::push_state(newState); + return newState; +} + void MainMenuState::update() { const int selected = m_mainMenu.get_selected(); diff --git a/source/appstates/ProgressState.cpp b/source/appstates/ProgressState.cpp index 29937e6..c3eac9a 100644 --- a/source/appstates/ProgressState.cpp +++ b/source/appstates/ProgressState.cpp @@ -12,13 +12,15 @@ void ProgressState::update() { + static constexpr double SIZE_BAR_WIDTH = 656.0f; + sys::ProgressTask *task = static_cast(m_task.get()); const double current = task->get_progress(); // Base routine. BaseTask::update(); - m_progressBarWidth = std::ceil(656.0f * current); + m_progressBarWidth = std::ceil(SIZE_BAR_WIDTH * current); m_progress = std::ceil(current * 100); m_percentageString = stringutil::get_formatted_string("%u", m_progress); m_percentageX = 640 - (sdl::text::get_width(18, m_percentageString.c_str())); diff --git a/source/appstates/SaveCreateState.cpp b/source/appstates/SaveCreateState.cpp index 9a85edc..49eb0f7 100644 --- a/source/appstates/SaveCreateState.cpp +++ b/source/appstates/SaveCreateState.cpp @@ -9,7 +9,8 @@ #include "logger.hpp" #include "strings.hpp" #include "stringutil.hpp" -#include "system/Task.hpp" +#include "sys/sys.hpp" +#include "tasks/savecreate.hpp" #include "ui/PopMessageManager.hpp" #include @@ -17,12 +18,6 @@ #include #include -// Declarations here. Definitions under class. -static void create_save_data(sys::Task *task, - data::User *targetUser, - data::TitleInfo *titleInfo, - SaveCreateState *spawningState); - // This is the sorting function. static bool compare_info(data::TitleInfo *infoA, data::TitleInfo *infoB); @@ -31,20 +26,21 @@ SaveCreateState::SaveCreateState(data::User *user, TitleSelectCommon *titleSelec , m_titleSelect{titleSelect} , m_saveMenu{8, 8, 624, 22, 720} { - // If the panel is null, create it. - if (!sm_slidePanel) - { - // Create panel and menu. - sm_slidePanel = std::make_unique(640, ui::SlideOutPanel::Side::Right); - } + SaveCreateState::initialize_static_members(); + SaveCreateState::initialize_title_info_vector(); + SaveCreateState::initialize_menu(); +} - // Get title info vector and copy titles to menu. - data::get_title_info_by_type(m_user->get_account_save_type(), m_titleInfoVector); +std::shared_ptr SaveCreateState::create(data::User *user, TitleSelectCommon *titleSelect) +{ + return std::make_shared(user, titleSelect); +} - // Sort it by alpha - std::sort(m_titleInfoVector.begin(), m_titleInfoVector.end(), compare_info); - - for (size_t i = 0; i < m_titleInfoVector.size(); i++) { m_saveMenu.add_option(m_titleInfoVector.at(i)->get_title()); } +std::shared_ptr SaveCreateState::create_and_push(data::User *user, TitleSelectCommon *titleSelect) +{ + auto newState = SaveCreateState::create(user, titleSelect); + StateManager::push_state(newState); + return newState; } void SaveCreateState::update() @@ -57,7 +53,6 @@ void SaveCreateState::update() const bool aPressed = input::button_pressed(HidNpadButton_A); const bool bPressed = input::button_pressed(HidNpadButton_B); const bool panelClosed = sm_slidePanel->is_closed(); - const int selected = m_saveMenu.get_selected(); if (m_refreshRequired.load()) { @@ -66,13 +61,7 @@ void SaveCreateState::update() m_refreshRequired.store(false); } - if (aPressed) - { - data::TitleInfo *titleInfo = m_titleInfoVector[selected]; - auto createTask = std::make_shared(create_save_data, m_user, titleInfo, this); - - StateManager::push_state(createTask); - } + if (aPressed) { SaveCreateState::create_save_data_for(); } else if (bPressed) { sm_slidePanel->close(); } else if (panelClosed) { @@ -91,36 +80,37 @@ void SaveCreateState::render() sm_slidePanel->render(NULL, hasFocus); } -void SaveCreateState::data_and_view_refresh_required() { m_refreshRequired.store(true); } +void SaveCreateState::refresh_required() { m_refreshRequired.store(true); } -static void create_save_data(sys::Task *task, - data::User *targetUser, - data::TitleInfo *titleInfo, - SaveCreateState *spawningState) +void SaveCreateState::initialize_static_members() { - if (error::is_null(task)) { return; } + if (!sm_slidePanel) { sm_slidePanel = std::make_unique(640, ui::SlideOutPanel::Side::Right); } +} - const char *statusTemplate = strings::get_by_name(strings::names::USEROPTION_STATUS, 0); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popSuccess = strings::get_by_name(strings::names::SAVECREATE_POPS, 0); - const char *popFailed = strings::get_by_name(strings::names::SAVECREATE_POPS, 1); +void SaveCreateState::initialize_title_info_vector() +{ + const FsSaveDataType saveType = m_user->get_account_save_type(); + data::get_title_info_by_type(saveType, m_titleInfoVector); + std::sort(m_titleInfoVector.begin(), m_titleInfoVector.end(), compare_info); +} + +void SaveCreateState::initialize_menu() +{ + for (data::TitleInfo *titleInfo : m_titleInfoVector) { - const std::string status = stringutil::get_formatted_string(statusTemplate, titleInfo->get_title()); - task->set_status(status); + const std::string_view title = titleInfo->get_title(); + m_saveMenu.add_option(title); } +} - const bool created = fs::create_save_data_for(targetUser, titleInfo); - if (created) - { - const char *title = titleInfo->get_title(); - std::string popMessage = stringutil::get_formatted_string(popSuccess, title); - ui::PopMessageManager::push_message(popTicks, popMessage); - } - else { ui::PopMessageManager::push_message(popTicks, popFailed); } +void SaveCreateState::create_save_data_for() +{ + const int selected = m_saveMenu.get_selected(); + data::TitleInfo *titleInfo = m_titleInfoVector[selected]; + auto createTask = TaskState::create(tasks::savecreate::create_save_data_for, m_user, titleInfo, this); - spawningState->data_and_view_refresh_required(); - task->finished(); + StateManager::push_state(createTask); } static bool compare_info(data::TitleInfo *infoA, data::TitleInfo *infoB) diff --git a/source/appstates/SettingsState.cpp b/source/appstates/SettingsState.cpp index cc7f4a1..81094e6 100644 --- a/source/appstates/SettingsState.cpp +++ b/source/appstates/SettingsState.cpp @@ -57,6 +57,8 @@ SettingsState::SettingsState() SettingsState::update_menu_options(); } +std::shared_ptr SettingsState::create() { return std::make_shared(); } + void SettingsState::update() { const bool hasFocus = BaseState::has_focus(); diff --git a/source/appstates/TextTitleSelectState.cpp b/source/appstates/TextTitleSelectState.cpp index 4640016..1c46954 100644 --- a/source/appstates/TextTitleSelectState.cpp +++ b/source/appstates/TextTitleSelectState.cpp @@ -32,6 +32,18 @@ TextTitleSelectState::TextTitleSelectState(data::User *user) TextTitleSelectState::refresh(); } +std::shared_ptr TextTitleSelectState::create(data::User *user) +{ + return std::make_shared(user); +} + +std::shared_ptr TextTitleSelectState::create_and_push(data::User *user) +{ + auto newState = TextTitleSelectState::create(user); + StateManager::push_state(newState); + return newState; +} + void TextTitleSelectState::update() { const bool hasFocus = BaseState::has_focus(); diff --git a/source/appstates/TitleInfoState.cpp b/source/appstates/TitleInfoState.cpp index e675a1d..58033e2 100644 --- a/source/appstates/TitleInfoState.cpp +++ b/source/appstates/TitleInfoState.cpp @@ -1,5 +1,6 @@ #include "appstates/TitleInfoState.hpp" +#include "StateManager.hpp" #include "colors.hpp" #include "error.hpp" #include "input.hpp" @@ -30,7 +31,7 @@ TitleInfoState::TitleInfoState(data::User *user, data::TitleInfo *titleInfo) , m_icon{m_titleInfo->get_icon()} { TitleInfoState::initialize_static_members(); - TitleInfoState::create_info_fields(); + TitleInfoState::create_info_scrolls(); } TitleInfoState::~TitleInfoState() @@ -39,6 +40,18 @@ TitleInfoState::~TitleInfoState() sm_slidePanel->clear_elements(); } +std::shared_ptr TitleInfoState::create(data::User *user, data::TitleInfo *titleInfo) +{ + return std::make_shared(user, titleInfo); +} + +std::shared_ptr TitleInfoState::create_and_push(data::User *user, data::TitleInfo *titleInfo) +{ + auto newState = TitleInfoState::create(user, titleInfo); + StateManager::push_state(newState); + return newState; +} + void TitleInfoState::update() { // Grab this instead of calling the function over and over. @@ -74,7 +87,7 @@ void TitleInfoState::initialize_static_members() } } -void TitleInfoState::create_info_fields() +void TitleInfoState::create_info_scrolls() { static constexpr int SIZE_VERT_GAP = SIZE_TEXT_TARGET_HEIGHT + 4; static constexpr int COORD_INIT_Y = 278; @@ -133,12 +146,12 @@ void TitleInfoState::create_info_fields() int y = COORD_INIT_Y; for (const std::string_view &string : textVector) { - auto newField = TitleInfoState::create_new_field(string, (y += SIZE_VERT_GAP)); + auto newField = TitleInfoState::create_new_scroll(string, (y += SIZE_VERT_GAP)); sm_slidePanel->push_new_element(newField); } } -std::shared_ptr TitleInfoState::create_new_field(std::string_view text, int y) +std::shared_ptr TitleInfoState::create_new_scroll(std::string_view text, int y) { static constexpr int SIZE_FIELD_WIDTH = SIZE_PANEL_WIDTH - SIZE_PANEL_SUB; auto textFieldScroll = std::make_shared(text, diff --git a/source/appstates/TitleOptionState.cpp b/source/appstates/TitleOptionState.cpp index 6f586e2..83c840a 100644 --- a/source/appstates/TitleOptionState.cpp +++ b/source/appstates/TitleOptionState.cpp @@ -12,9 +12,11 @@ #include "input.hpp" #include "keyboard.hpp" #include "logger.hpp" +#include "remote/remote.hpp" #include "strings.hpp" #include "stringutil.hpp" -#include "system/system.hpp" +#include "sys/sys.hpp" +#include "tasks/titleoptions.hpp" #include "ui/PopMessageManager.hpp" #include @@ -28,22 +30,17 @@ namespace BLACKLIST, CHANGE_OUTPUT, FILE_MODE, - DELETE_ALL_BACKUPS, + DELETE_ALL_LOCAL_BACKUPS, + DELETE_ALL_REMOTE_BACKUPS, RESET_SAVE_DATA, DELETE_SAVE_FROM_SYSTEM, EXTEND_CONTAINER, EXPORT_SVI }; -} // namespace -// Declarations. Definitions after class. Some of these are only here to be compatible with confirmations. -static void blacklist_title(sys::Task *task, std::shared_ptr dataStruct); -static void change_output_path(data::TitleInfo *targetTitle); -static void delete_all_backups_for_title(sys::Task *task, std::shared_ptr dataStruct); -static void reset_save_data(sys::Task *task, std::shared_ptr dataStruct); -static void delete_save_data_from_system(sys::Task *task, std::shared_ptr dataStruct); -static void extend_save_data(sys::Task *task, std::shared_ptr dataStruct); -static void export_svi_file(data::TitleInfo *titleInfo); + using TaskConfirm = ConfirmState; + using ProgressConfirm = ConfirmState; +} // namespace TitleOptionState::TitleOptionState(data::User *user, data::TitleInfo *titleInfo, TitleSelectCommon *titleSelect) : m_user{user} @@ -51,34 +48,33 @@ TitleOptionState::TitleOptionState(data::User *user, data::TitleInfo *titleInfo, , m_titleSelect{titleSelect} , m_dataStruct{std::make_shared()} { - // Create panel if needed. - if (!sm_initialized) - { - // Allocate static members. - sm_slidePanel = std::make_unique(480, ui::SlideOutPanel::Side::Right); - sm_titleOptionMenu = std::make_unique(8, 8, 460, 22, 720); + TitleOptionState::initialize_static_members(); + TitleOptionState::initialize_data_struct(); +} - // Populate menu. - int stringIndex = 0; - const char *currentString = nullptr; - while ((currentString = strings::get_by_name(strings::names::TITLEOPTION, stringIndex++)) != nullptr) - { - sm_titleOptionMenu->add_option(currentString); - } +std::shared_ptr TitleOptionState::create(data::User *user, + data::TitleInfo *titleInfo, + TitleSelectCommon *titleSelect) +{ + return std::make_shared(user, titleInfo, titleSelect); +} - // Only do this once. - sm_initialized = true; - } - - // Fill this out. - m_dataStruct->user = m_user; - m_dataStruct->titleInfo = m_titleInfo; - m_dataStruct->spawningState = this; - m_dataStruct->titleSelect = m_titleSelect; +std::shared_ptr TitleOptionState::create_and_push(data::User *user, + data::TitleInfo *titleInfo, + TitleSelectCommon *titleSelect) +{ + auto newState = TitleOptionState::create(user, titleInfo, titleSelect); + StateManager::push_state(newState); + return newState; } void TitleOptionState::update() { + const bool hasFocus = BaseState::has_focus(); + const bool aPressed = input::button_pressed(HidNpadButton_A); + const bool bPressed = input::button_pressed(HidNpadButton_B); + const int selected = sm_titleOptionMenu->get_selected(); + // This is kind of tricky to handle, because the blacklist function uses both. if (m_refreshRequired) { @@ -90,151 +86,24 @@ void TitleOptionState::update() } if (m_exitRequired) { sm_slidePanel->close(); } - // Update panel and menu. - sm_slidePanel->update(BaseState::has_focus()); - sm_titleOptionMenu->update(BaseState::has_focus()); - - if (input::button_pressed(HidNpadButton_A)) + sm_slidePanel->update(hasFocus); + if (aPressed) { - switch (sm_titleOptionMenu->get_selected()) + switch (selected) { - case INFORMATION: - { - auto titleInfoState = std::make_shared(m_user, m_titleInfo); - - // Just push the state. - StateManager::push_state(titleInfoState); - } - break; - - case BLACKLIST: - { - // Get the string. - std::string confirmString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 0), - m_titleInfo->get_title()); - - // The actual state. - auto confirm = - std::make_shared>(confirmString, - false, - blacklist_title, - m_dataStruct); - - // Push - StateManager::push_state(confirm); - } - break; - - case CHANGE_OUTPUT: - { - change_output_path(m_titleInfo); - } - break; - - case FILE_MODE: - { - } - break; - - case DELETE_ALL_BACKUPS: - { - // String - std::string confirmString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 1), - m_titleInfo->get_title()); - - // State. This always requires holding because I hate people complaining to me about how it's my fault they - // don't read things first. - auto confirm = std::make_shared>( - confirmString, - true, - delete_all_backups_for_title, - m_dataStruct); - - StateManager::push_state(confirm); - } - break; - - case RESET_SAVE_DATA: - { - // Need to check this first. For safety. - FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(m_titleInfo->get_application_id()); - if (fs::is_system_save_data(saveInfo) && !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 6)); - return; - } - - // String - std::string confirmString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 2), - m_titleInfo->get_title()); - - auto confirm = - std::make_shared>(confirmString, - true, - reset_save_data, - m_dataStruct); - - StateManager::push_state(confirm); - } - break; - - case DELETE_SAVE_FROM_SYSTEM: - { - FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(m_titleInfo->get_application_id()); - if (fs::is_system_save_data(saveInfo) && !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 6)); - return; - } - - // String - std::string confirmString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 3), - m_user->get_nickname(), - m_titleInfo->get_title()); - - // Confirmation. - auto confirm = std::make_shared>( - confirmString, - true, - delete_save_data_from_system, - m_dataStruct); - - StateManager::push_state(confirm); - } - break; - - case EXTEND_CONTAINER: - { - FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(m_titleInfo->get_application_id()); - if (fs::is_system_save_data(saveInfo) && !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 6)); - return; - } - - // State. - StateManager::push_state(std::make_shared(extend_save_data, m_dataStruct)); - } - break; - - case EXPORT_SVI: - { - // This type of save data can't have this exported anyway. - FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(m_titleInfo->get_application_id()); - if (fs::is_system_save_data(saveInfo)) { return; } - export_svi_file(m_titleInfo); - } - break; + case INFORMATION: TitleOptionState::create_push_info_state(); break; + case BLACKLIST: TitleOptionState::add_to_blacklist(); break; + case CHANGE_OUTPUT: TitleOptionState::change_output_directory(); break; + case FILE_MODE: TitleOptionState::create_push_file_mode(); break; + case DELETE_ALL_LOCAL_BACKUPS: TitleOptionState::delete_all_local_backups(); break; + case DELETE_ALL_REMOTE_BACKUPS: TitleOptionState::delete_all_remote_backups(); break; + case RESET_SAVE_DATA: TitleOptionState::reset_save_data(); break; + case DELETE_SAVE_FROM_SYSTEM: TitleOptionState::delete_save_from_system(); break; + case EXTEND_CONTAINER: TitleOptionState::extend_save_container(); break; + case EXPORT_SVI: TitleOptionState::export_svi_file(); break; } } - else if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); } + else if (bPressed) { sm_slidePanel->close(); } else if (sm_slidePanel->is_closed()) { // Reset static members. @@ -247,239 +116,225 @@ void TitleOptionState::update() void TitleOptionState::render() { + const bool hasFocus = BaseState::has_focus(); + sm_slidePanel->clear_target(); - sm_titleOptionMenu->render(sm_slidePanel->get_target(), BaseState::has_focus()); - sm_slidePanel->render(NULL, BaseState::has_focus()); + sm_slidePanel->render(NULL, hasFocus); } void TitleOptionState::close_on_update() { m_exitRequired = true; } void TitleOptionState::refresh_required() { m_refreshRequired = true; } -static void blacklist_title(sys::Task *task, std::shared_ptr dataStruct) +void TitleOptionState::initialize_static_members() { - if (error::is_null(task)) { return; } + if (sm_slidePanel && sm_titleOptionMenu) { return; } - data::TitleInfo *titleInfo = dataStruct->titleInfo; - TitleOptionState *spawningState = dataStruct->spawningState; - const uint64_t applicationID = titleInfo->get_application_id(); + sm_slidePanel = std::make_unique(480, ui::SlideOutPanel::Side::Right); + sm_titleOptionMenu = std::make_shared(8, 8, 460, 22, 720); - config::add_remove_blacklist(applicationID); - - data::UserList userList; - data::get_users(userList); - for (data::User *user : userList) { user->erase_save_info_by_id(applicationID); } - - // This will tell the main thread a refresh is required on the next update call. - spawningState->refresh_required(); - spawningState->close_on_update(); - - task->finished(); + for (int i = 0; const char *option = strings::get_by_name(strings::names::TITLEOPTION, i); i++) + { + sm_titleOptionMenu->add_option(option); + } + sm_slidePanel->push_new_element(sm_titleOptionMenu); } -static void change_output_path(data::TitleInfo *targetTitle) +void TitleOptionState::initialize_data_struct() +{ + m_dataStruct->user = m_user; + m_dataStruct->titleInfo = m_titleInfo; + m_dataStruct->spawningState = this; + m_dataStruct->titleSelect = m_titleSelect; +} + +void TitleOptionState::create_push_info_state() +{ + auto titleInfoState = TitleInfoState::create(m_user, m_titleInfo); + StateManager::push_state(titleInfoState); +} + +void TitleOptionState::add_to_blacklist() +{ + const char *title = m_titleInfo->get_title(); + const char *confirmFormat = strings::get_by_name(strings::names::TITLEOPTION_CONFS, 0); + const std::string query = stringutil::get_formatted_string(confirmFormat, title); + + TaskConfirm::create_and_push(query, false, tasks::titleoptions::blacklist_title, m_dataStruct); +} + +void TitleOptionState::change_output_directory() { static constexpr size_t SIZE_PATH_BUFFER = 0x200; - const char *headerTemplate = strings::get_by_name(strings::names::KEYBOARD, 7); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 8); - const char *popFailure = strings::get_by_name(strings::names::TITLEOPTION_POPS, 9); - const char *pathSafeTitle = targetTitle->get_path_safe_title(); - const std::string headerString = stringutil::get_formatted_string(headerTemplate, targetTitle->get_title()); - char pathBuffer[SIZE_PATH_BUFFER] = {0}; - const bool inputIsValid = keyboard::get_input(SwkbdType_QWERTY, pathSafeTitle, headerString, pathBuffer, SIZE_PATH_BUFFER); - const bool sanitized = inputIsValid && stringutil::sanitize_string_for_path(pathBuffer, pathBuffer, SIZE_PATH_BUFFER); - const bool notEmpty = std::char_traits::length(pathBuffer) > 0; - if (!inputIsValid || !sanitized || !notEmpty) + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *pathSafe = m_titleInfo->get_path_safe_title(); + const char *popFailed = strings::get_by_name(strings::names::TITLEOPTION_POPS, 9); + std::array pathBuffer = {0}; + { - ui::PopMessageManager::push_message(popTicks, popFailure); + const char *title = m_titleInfo->get_title(); + const char *headerFormat = strings::get_by_name(strings::names::KEYBOARD, 7); + const std::string header = stringutil::get_formatted_string(headerFormat, title); + const bool inputValid = keyboard::get_input(SwkbdType_QWERTY, pathSafe, header, pathBuffer.data(), SIZE_PATH_BUFFER); + if (!inputValid) { return; } + } + + const bool sanitized = stringutil::sanitize_string_for_path(pathBuffer.data(), pathBuffer.data(), SIZE_PATH_BUFFER); + const bool empty = std::char_traits::length(pathBuffer.data()) <= 0; + if (!sanitized || empty) + { + logger::log("here"); + ui::PopMessageManager::push_message(popTicks, popFailed); return; } + const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); const fslib::Path workDir = config::get_working_directory(); - const fslib::Path oldPath{workDir / targetTitle->get_path_safe_title()}; - const fslib::Path newPath{workDir / pathBuffer}; - const bool dirExists = fslib::directory_exists(oldPath); - const bool renameFailed = dirExists && error::fslib(fslib::rename_directory(oldPath, newPath)); - if (dirExists && renameFailed) + const fslib::Path oldPath = workDir / pathSafe; + const fslib::Path newPath = workDir / pathBuffer.data(); + const bool oldExists = fslib::directory_exists(oldPath); + const bool renameFailed = oldExists && error::fslib(fslib::rename_directory(oldPath, newPath)); + if (!autoUpload && (!oldExists || renameFailed)) { ui::PopMessageManager::push_message(popTicks, popFailed); } + + // Need to change WebDav to match. + remote::Storage *remote = remote::get_remote_storage(); + const bool dirExists = remote && !remote->supports_utf8() && remote->directory_exists(pathSafe); // This is guaranteed DAV + if (dirExists) { - ui::PopMessageManager::push_message(popTicks, popFailure); + remote::Item *item = remote->get_directory_by_name(pathSafe); + remote->rename_item(item, pathBuffer.data()); + } + + const uint64_t applicationID = m_titleInfo->get_application_id(); + m_titleInfo->set_path_safe_title(pathBuffer.data(), SIZE_PATH_BUFFER); + config::add_custom_path(applicationID, pathBuffer.data()); + + const char *popSuccessFormat = strings::get_by_name(strings::names::TITLEOPTION_POPS, 8); + const std::string popSuccess = stringutil::get_formatted_string(popSuccessFormat, pathBuffer.data()); + ui::PopMessageManager::push_message(popTicks, popSuccess); +} + +void TitleOptionState::create_push_file_mode() {} + +void TitleOptionState::delete_all_local_backups() +{ + const char *title = m_titleInfo->get_title(); + const char *confirmFormat = strings::get_by_name(strings::names::TITLEOPTION_CONFS, 1); + const std::string query = stringutil::get_formatted_string(confirmFormat, title); + + TaskConfirm::create_and_push(query, true, tasks::titleoptions::delete_all_local_backups_for_title, m_dataStruct); +} + +void TitleOptionState::delete_all_remote_backups() +{ + const char *title = m_titleInfo->get_title(); + const char *confirmFormat = strings::get_by_name(strings::names::TITLEOPTION_CONFS, 1); + const std::string query = stringutil::get_formatted_string(confirmFormat, title); + + TaskConfirm::create_and_push(query, true, tasks::titleoptions::delete_all_remote_backups_for_title, m_dataStruct); +} + +void TitleOptionState::reset_save_data() +{ + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const uint64_t applicationID = m_titleInfo->get_application_id(); + const FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); + if (error::is_null(saveInfo)) { return; } + + const bool allowSystemWriting = config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM); + if (!allowSystemWriting && fs::is_system_save_data(saveInfo)) + { + const char *popNoSysWrite = strings::get_by_name(strings::names::TITLEOPTION_POPS, 6); + ui::PopMessageManager::push_message(popTicks, popNoSysWrite); return; } - targetTitle->set_path_safe_title(pathBuffer, std::strlen(pathBuffer)); - config::add_custom_path(targetTitle->get_application_id(), pathBuffer); + const char *title = m_titleInfo->get_title(); + const char *confirmFormat = strings::get_by_name(strings::names::TITLEOPTION_CONFS, 2); + const std::string query = stringutil::get_formatted_string(confirmFormat, title); - const std::string popMessage = stringutil::get_formatted_string(popSuccess, pathBuffer); - ui::PopMessageManager::push_message(popTicks, popMessage); + TaskConfirm::create_and_push(query, true, tasks::titleoptions::reset_save_data, m_dataStruct); } -static void delete_all_backups_for_title(sys::Task *task, std::shared_ptr dataStruct) +void TitleOptionState::delete_save_from_system() { - if (error::is_null(task)) { return; } - data::TitleInfo *titleInfo = dataStruct->titleInfo; - - const char *statusTemplate = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 0); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 0); - const char *popFailure = strings::get_by_name(strings::names::TITLEOPTION_POPS, 1); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const uint64_t applicationID = m_titleInfo->get_application_id(); + const FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); + if (error::is_null(saveInfo)) { return; } + // This isn't allowed at ALL. + if (fs::is_system_save_data(saveInfo)) { - const std::string status = stringutil::get_formatted_string(statusTemplate, titleInfo->get_title()); - task->set_status(status); + const char *popUnavailable = strings::get_by_name(strings::names::TITLEOPTION_POPS, 6); + ui::PopMessageManager::push_message(popTicks, popUnavailable); + return; } - const fslib::Path titlePath{config::get_working_directory() / titleInfo->get_path_safe_title()}; - const bool deleteFailed = error::fslib(fslib::delete_directory_recursively(titlePath)); - if (deleteFailed) { ui::PopMessageManager::push_message(popTicks, popFailure); } - else { const std::string popMessage = stringutil::get_formatted_string(popSuccess, titleInfo->get_title()); } + const char *nickname = m_user->get_nickname(); + const char *title = m_titleInfo->get_title(); + const char *confirmFormat = strings::get_by_name(strings::names::TITLEOPTION_CONFS, 3); + const std::string query = stringutil::get_formatted_string(confirmFormat, nickname, title); - task->finished(); + TaskConfirm::create_and_push(query, true, tasks::titleoptions::delete_save_data_from_system, m_dataStruct); } -static void reset_save_data(sys::Task *task, std::shared_ptr dataStruct) +void TitleOptionState::extend_save_container() { - if (error::is_null(task)) { return; } - - data::User *user = dataStruct->user; - data::TitleInfo *titleInfo = dataStruct->titleInfo; - - const uint64_t applicationID = titleInfo->get_application_id(); - const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popFailed = strings::get_by_name(strings::names::TITLEOPTION_POPS, 2); - const char *popSucceeded = strings::get_by_name(strings::names::TITLEOPTION_POPS, 3); + const uint64_t applicationID = m_titleInfo->get_application_id(); + const FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); + if (error::is_null(saveInfo)) { return; } - const bool mountFailed = error::fslib(fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)); - if (mountFailed) + if (fs::is_system_save_data(saveInfo)) + { + const char *popUnavailable = strings::get_by_name(strings::names::TITLEOPTION_POPS, 6); + ui::PopMessageManager::push_message(popTicks, popUnavailable); + return; + } + + TaskState::create_and_push(tasks::titleoptions::extend_save_data, m_dataStruct); +} + +void TitleOptionState::export_svi_file() +{ + static constexpr size_t SIZE_SVI_FILE = sizeof(uint64_t) + sizeof(NsApplicationControlData); + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *popFailed = strings::get_by_name(strings::names::TITLEOPTION_POPS, 5); + + const uint64_t applicationID = m_titleInfo->get_application_id(); + const std::string titleIdHex = stringutil::get_formatted_string("%016llX", applicationID); + const fslib::Path workDir = config::get_working_directory(); + const fslib::Path sviPath = workDir / "svi" / titleIdHex + ".svi"; + + const bool exists = fslib::file_exists(sviPath); + if (exists) { ui::PopMessageManager::push_message(popTicks, popFailed); - task->finished(); return; } - const bool wipeFailed = error::fslib(fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)); - const bool commitFailed = !wipeFailed && error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); - if (wipeFailed || commitFailed) { ui::PopMessageManager::push_message(popTicks, popFailed); } - else { ui::PopMessageManager::push_message(popTicks, popSucceeded); } - - fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - task->finished(); -} - -static void delete_save_data_from_system(sys::Task *task, std::shared_ptr dataStruct) -{ - data::User *user = dataStruct->user; - data::TitleInfo *titleInfo = dataStruct->titleInfo; - TitleSelectCommon *titleSelect = dataStruct->titleSelect; - TitleOptionState *spawningState = dataStruct->spawningState; - - const uint64_t applicationID = titleInfo->get_application_id(); - const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); - const char *statusTemplate = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 2); - if (error::is_null(task) || error::is_null(saveInfo)) { return; } - + fslib::File sviFile{sviPath, FsOpenMode_Create | FsOpenMode_Write, SIZE_SVI_FILE}; + if (!sviFile.is_open()) { - const char *nickname = user->get_nickname(); - const char *title = titleInfo->get_title(); - const std::string status = stringutil::get_formatted_string(statusTemplate, nickname, title); - task->set_status(status); - } - - const bool saveDeleted = fs::delete_save_data(saveInfo); - if (!saveDeleted) - { - task->finished(); + ui::PopMessageManager::push_message(popTicks, popFailed); return; } - user->erase_save_info_by_id(applicationID); - titleSelect->refresh(); - spawningState->close_on_update(); // Since the save was deleted, state is no longer valid. - task->finished(); -} - -static void extend_save_data(sys::Task *task, std::shared_ptr dataStruct) -{ - static constexpr size_t SIZE_EXTRA = sizeof(FsSaveDataExtraData); - static constexpr size_t SIZE_MB = 0x100000; - - data::User *user = dataStruct->user; - data::TitleInfo *titleInfo = dataStruct->titleInfo; - const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(titleInfo->get_application_id()); - const char *statusTemplate = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 3); - const char *keyboardHeader = strings::get_by_name(strings::names::KEYBOARD, 8); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 10); - const char *popFailed = strings::get_by_name(strings::names::TITLEOPTION_POPS, 11); - if (error::is_null(task) || error::is_null(saveInfo)) { return; } + const NsApplicationControlData *controlData = m_titleInfo->get_control_data(); + const bool magicWritten = sviFile.write(&fs::SAVE_META_MAGIC, sizeof(uint32_t)) == sizeof(uint32_t); + const bool appIdWritten = magicWritten && sviFile.write(&applicationID, sizeof(uint64_t)) == sizeof(uint64_t); + const bool controlWritten = + appIdWritten && sviFile.write(controlData, sizeof(NsApplicationControlData)) == sizeof(NsApplicationControlData); + if (!magicWritten || !appIdWritten || !controlWritten) { - const char *nickname = user->get_nickname(); - const char *title = titleInfo->get_title(); - const std::string status = stringutil::get_formatted_string(statusTemplate, nickname, title); - task->set_status(status); - } - - FsSaveDataExtraData extraData{}; - char buffer[5] = {0}; - const bool extraError = error::libnx(fsReadSaveDataFileSystemExtraData(&extraData, SIZE_EXTRA, saveInfo->save_data_id)); - const std::string keyboardDefault = stringutil::get_formatted_string("%u", (extraData.data_size / SIZE_MB) + SIZE_MB); - const bool validInput = keyboard::get_input(SwkbdType_NumPad, keyboardDefault, keyboardHeader, buffer, 5); - if (!validInput) - { - task->finished(); + ui::PopMessageManager::push_message(popTicks, popFailed); return; } - const uint8_t saveType = saveInfo->save_data_type; - const int64_t size = std::strtoll(buffer, NULL, 10) * 0x100000; - const int64_t journal = extraError ? titleInfo->get_journal_size(saveType) : extraData.journal_size; - const bool saveExtended = fs::extend_save_data(saveInfo, size, journal); - if (saveExtended) { ui::PopMessageManager::push_message(popTicks, popSuccess); } - else { ui::PopMessageManager::push_message(popTicks, popFailed); } - - task->finished(); -} - -static void export_svi_file(data::TitleInfo *titleInfo) -{ - // This is to allow the files to be create with a starting size. This cuts down on FS calls with fslib. - constexpr size_t SIZE_SVI_FILE = sizeof(uint64_t) + sizeof(NsApplicationControlData); - - // Export path. - fslib::Path sviPath = config::get_working_directory() / "svi" / - stringutil::get_formatted_string("%016llX.svi", titleInfo->get_application_id()); - - // Check if it already exists. - if (fslib::file_exists(sviPath)) - { - logger::log("SVI for %016llX already exists!", titleInfo->get_application_id()); - // Just show this and bail. - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 5)); - return; - } - - // File - fslib::File sviFile(sviPath, FsOpenMode_Create | FsOpenMode_Write, SIZE_SVI_FILE); - if (!sviFile) - { - logger::log("Error exporting SVI file: %s", fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 5)); - } - - // Ok. Letsa go~ - // This is needed like this. - uint64_t applicationID = titleInfo->get_application_id(); - - // Write the stuff we need. - sviFile.write(&applicationID, sizeof(uint64_t)); - sviFile.write(titleInfo->get_control_data(), sizeof(NsApplicationControlData)); - - // Show this so we know things happened.jpg - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 4)); + const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 4); + ui::PopMessageManager::push_message(popTicks, popSuccess); } diff --git a/source/appstates/TitleSelectState.cpp b/source/appstates/TitleSelectState.cpp index eaa93e0..3b5bf08 100644 --- a/source/appstates/TitleSelectState.cpp +++ b/source/appstates/TitleSelectState.cpp @@ -30,6 +30,18 @@ TitleSelectState::TitleSelectState(data::User *user) SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)) , m_titleView(m_user) {}; +std::shared_ptr TitleSelectState::create(data::User *user) +{ + return std::make_shared(user); +} + +std::shared_ptr TitleSelectState::create_and_push(data::User *user) +{ + auto newState = TitleSelectState::create(user); + StateManager::push_state(newState); + return newState; +} + void TitleSelectState::update() { if (!TitleSelectState::title_count_check()) { return; } diff --git a/source/appstates/UserOptionState.cpp b/source/appstates/UserOptionState.cpp index 5e0abe5..66ff7b5 100644 --- a/source/appstates/UserOptionState.cpp +++ b/source/appstates/UserOptionState.cpp @@ -15,7 +15,7 @@ #include "logger.hpp" #include "strings.hpp" #include "stringutil.hpp" -#include "system/system.hpp" +#include "sys/sys.hpp" #include "ui/PopMessageManager.hpp" namespace @@ -53,6 +53,18 @@ UserOptionState::UserOptionState(data::User *user, TitleSelectCommon *titleSelec UserOptionState::initialize_data_struct(); } +std::shared_ptr UserOptionState::create(data::User *user, TitleSelectCommon *titleSelect) +{ + return std::make_shared(user, titleSelect); +} + +std::shared_ptr UserOptionState::create_and_push(data::User *user, TitleSelectCommon *titleSelect) +{ + auto newState = UserOptionState::create(user, titleSelect); + StateManager::push_state(newState); + return newState; +} + void UserOptionState::update() { const bool hasFocus = BaseState::has_focus(); @@ -277,11 +289,7 @@ static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptr applicationIDs; // Check this quick just in case. - if (user->get_account_save_type() == FsSaveDataType_System) - { - task->finished(); - return; - } + if (user->get_account_save_type() == FsSaveDataType_System) { TASK_FINISH_RETURN(task); } for (size_t i = 0; i < totalDataEntries; i++) { diff --git a/source/curl/curl.cpp b/source/curl/curl.cpp index 47b775e..106c192 100644 --- a/source/curl/curl.cpp +++ b/source/curl/curl.cpp @@ -67,15 +67,15 @@ size_t curl::write_data_to_file(const char *buffer, size_t size, size_t count, f size_t curl::download_file_threaded(const char *buffer, size_t size, size_t count, curl::DownloadStruct *download) { - std::mutex &lock = download->lock; - std::condition_variable &condition = download->condition; - std::vector &sharedBuffer = download->sharedBuffer; - bool &bufferReady = download->bufferReady; - sys::ProgressTask *task = download->task; - size_t &offset = download->offset; - size_t &fileSize = download->fileSize; - const size_t downloadSize = size * count; - const std::span bufferSpan{reinterpret_cast(buffer), downloadSize}; + std::mutex &lock = download->lock; + std::condition_variable &condition = download->condition; + std::vector &sharedBuffer = download->sharedBuffer; + bool &bufferReady = download->bufferReady; + sys::ProgressTask *task = download->task; + size_t &offset = download->offset; + int64_t &fileSize = download->fileSize; + const size_t downloadSize = size * count; + const std::span bufferSpan{reinterpret_cast(buffer), downloadSize}; { std::unique_lock bufferLock(lock); @@ -99,14 +99,14 @@ size_t curl::download_file_threaded(const char *buffer, size_t size, size_t coun void curl::download_write_thread_function(curl::DownloadStruct &download) { - std::mutex &lock = download.lock; - std::condition_variable &condition = download.condition; - std::vector &sharedBuffer = download.sharedBuffer; - bool &bufferReady = download.bufferReady; - fslib::File *dest = download.dest; - size_t fileSize = download.fileSize; + std::mutex &lock = download.lock; + std::condition_variable &condition = download.condition; + std::vector &sharedBuffer = download.sharedBuffer; + bool &bufferReady = download.bufferReady; + fslib::File *dest = download.dest; + size_t fileSize = download.fileSize; - auto localBuffer = std::make_unique(SIZE_DOWNLOAD_THRESHOLD + 0x100000); // Gonna give this some room. + auto localBuffer = std::make_unique(SIZE_DOWNLOAD_THRESHOLD + 0x100000); // Gonna give this some room. for (size_t i = 0; i < fileSize;) { diff --git a/source/fs/io.cpp b/source/fs/io.cpp index 4c7c603..c5accf4 100644 --- a/source/fs/io.cpp +++ b/source/fs/io.cpp @@ -5,7 +5,7 @@ #include "fslib.hpp" #include "strings.hpp" #include "stringutil.hpp" -#include "system/defines.hpp" +#include "sys/sys.hpp" #include "ui/PopMessageManager.hpp" #include @@ -28,18 +28,18 @@ struct FileThreadStruct std::condition_variable condition{}; bool bufferReady{}; ssize_t readSize{}; - std::unique_ptr sharedBuffer{}; + std::unique_ptr sharedBuffer{}; }; // clang-format on static void readThreadFunction(fslib::File &sourceFile, std::shared_ptr sharedData) { - std::mutex &lock = sharedData->lock; - std::condition_variable &condition = sharedData->condition; - bool &bufferReady = sharedData->bufferReady; - ssize_t &readSize = sharedData->readSize; - std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; - const int64_t fileSize = sourceFile.get_size(); + std::mutex &lock = sharedData->lock; + std::condition_variable &condition = sharedData->condition; + bool &bufferReady = sharedData->bufferReady; + ssize_t &readSize = sharedData->readSize; + std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; + const int64_t fileSize = sourceFile.get_size(); for (int64_t i = 0; i < fileSize;) { @@ -76,8 +76,8 @@ void fs::copy_file(const fslib::Path &source, const fslib::Path &destination, sy } auto sharedData = std::make_shared(); - sharedData->sharedBuffer = std::make_unique(SIZE_FILE_BUFFER); - auto localBuffer = std::make_unique(SIZE_FILE_BUFFER); + sharedData->sharedBuffer = std::make_unique(SIZE_FILE_BUFFER); + auto localBuffer = std::make_unique(SIZE_FILE_BUFFER); std::mutex &lock = sharedData->lock; std::condition_variable &condition = sharedData->condition; @@ -132,8 +132,8 @@ void fs::copy_file_commit(const fslib::Path &source, } auto sharedData = std::make_shared(); - auto localBuffer = std::make_unique(SIZE_FILE_BUFFER); - sharedData->sharedBuffer = std::make_unique(SIZE_FILE_BUFFER); + auto localBuffer = std::make_unique(SIZE_FILE_BUFFER); + sharedData->sharedBuffer = std::make_unique(SIZE_FILE_BUFFER); std::mutex &lock = sharedData->lock; std::condition_variable &condition = sharedData->condition; diff --git a/source/fs/zip.cpp b/source/fs/zip.cpp index b2e892e..9e301cb 100644 --- a/source/fs/zip.cpp +++ b/source/fs/zip.cpp @@ -6,7 +6,7 @@ #include "logger.hpp" #include "strings.hpp" #include "stringutil.hpp" -#include "system/defines.hpp" +#include "sys/sys.hpp" #include "ui/PopMessageManager.hpp" #include @@ -33,19 +33,19 @@ struct ZipIOStruct std::condition_variable condition{}; ssize_t readSize{}; bool bufferReady{}; - std::unique_ptr sharedBuffer{}; + std::unique_ptr sharedBuffer{}; }; // clang-format on // Function for reading files for Zipping. static void zipReadThreadFunction(fslib::File &source, std::shared_ptr sharedData) { - std::mutex &lock = sharedData->lock; - std::condition_variable &condition = sharedData->condition; - ssize_t &readSize = sharedData->readSize; - bool &bufferReady = sharedData->bufferReady; - std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; - const int64_t fileSize = source.get_size(); + std::mutex &lock = sharedData->lock; + std::condition_variable &condition = sharedData->condition; + ssize_t &readSize = sharedData->readSize; + bool &bufferReady = sharedData->bufferReady; + std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; + const int64_t fileSize = source.get_size(); for (int64_t i = 0; i < fileSize;) { @@ -68,12 +68,12 @@ static void zipReadThreadFunction(fslib::File &source, std::shared_ptr sharedData) { - std::mutex &lock = sharedData->lock; - std::condition_variable &condition = sharedData->condition; - ssize_t &readSize = sharedData->readSize; - bool &bufferReady = sharedData->bufferReady; - std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; - const int64_t fileSize = unzip.get_uncompressed_size(); + std::mutex &lock = sharedData->lock; + std::condition_variable &condition = sharedData->condition; + ssize_t &readSize = sharedData->readSize; + bool &bufferReady = sharedData->bufferReady; + std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; + const int64_t fileSize = unzip.get_uncompressed_size(); for (int64_t i = 0; i < fileSize;) { @@ -113,8 +113,8 @@ void fs::copy_directory_to_zip(const fslib::Path &source, fs::MiniZip &dest, sys const int64_t fileSize = sourceFile.get_size(); auto sharedData = std::make_shared(); - sharedData->sharedBuffer = std::make_unique(SIZE_ZIP_BUFFER); - auto localBuffer = std::make_unique(SIZE_ZIP_BUFFER); + sharedData->sharedBuffer = std::make_unique(SIZE_ZIP_BUFFER); + auto localBuffer = std::make_unique(SIZE_ZIP_BUFFER); if (task) { @@ -195,14 +195,14 @@ void fs::copy_zip_to_directory(fs::MiniUnzip &unzip, } auto sharedData = std::make_shared(); - sharedData->sharedBuffer = std::make_unique(SIZE_UNZIP_BUFFER); - auto localBuffer = std::make_unique(SIZE_UNZIP_BUFFER); + sharedData->sharedBuffer = std::make_unique(SIZE_UNZIP_BUFFER); + auto localBuffer = std::make_unique(SIZE_UNZIP_BUFFER); - std::mutex &lock = sharedData->lock; - std::condition_variable &condition = sharedData->condition; - ssize_t &readSize = sharedData->readSize; - bool &bufferReady = sharedData->bufferReady; - std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; + std::mutex &lock = sharedData->lock; + std::condition_variable &condition = sharedData->condition; + ssize_t &readSize = sharedData->readSize; + bool &bufferReady = sharedData->bufferReady; + std::unique_ptr &sharedBuffer = sharedData->sharedBuffer; std::thread readThread(unzipReadThreadFunction, std::ref(unzip), sharedData); int64_t journalCount{}; diff --git a/source/remote/GoogleDrive.cpp b/source/remote/GoogleDrive.cpp index 0a82904..bfa2ab3 100644 --- a/source/remote/GoogleDrive.cpp +++ b/source/remote/GoogleDrive.cpp @@ -296,14 +296,15 @@ bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::P { if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } - fslib::File destFile{destination, FsOpenMode_Create | FsOpenMode_Write, file->get_size()}; + const int64_t itemSize = file->get_size(); + fslib::File destFile{destination, FsOpenMode_Create | FsOpenMode_Write, itemSize}; if (!destFile) { logger::log("Error downloading file: local file could not be opened for writing!"); return false; } - if (task) { task->reset(static_cast(file->get_size())); } + if (task) { task->reset(static_cast(itemSize)); } curl::HeaderList header = curl::new_header_list(); curl::append_header(header, m_authHeader); @@ -311,7 +312,7 @@ bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::P remote::URL url{URL_DRIVE_FILE_API}; url.append_path(file->get_id()).append_parameter("alt", "media"); - curl::DownloadStruct download{.dest = &destFile, .task = task, .fileSize = file->get_size()}; + curl::DownloadStruct download{.dest = &destFile, .task = task, .fileSize = itemSize}; curl::prepare_get(m_curl); curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); curl::set_option(m_curl, CURLOPT_URL, url.get()); @@ -319,9 +320,6 @@ bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::P curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::download_file_threaded); curl::set_option(m_curl, CURLOPT_WRITEDATA, &download); - // curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::download_file_threaded); - // curl::set_option(m_curl, CURLOPT_WRITEDATA, &download); - std::thread writeThread(curl::download_write_thread_function, std::ref(download)); if (!curl::perform(m_curl)) { return false; } writeThread.join(); @@ -334,10 +332,9 @@ bool remote::GoogleDrive::delete_item(const remote::Item *item) if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } // Iterator is needed to remove it from the list. - auto findItem = std::find_if(m_list.begin(), - m_list.end(), - [item](const Item &listItem) { return item->get_id() == listItem.get_id(); }); - + const std::string_view itemId = item->get_id(); + auto findItem = + std::find_if(m_list.begin(), m_list.end(), [&](const Item &listItem) { return itemId == listItem.get_id(); }); if (findItem == m_list.end()) { logger::log("Error deleting item: Item not found in list!"); @@ -348,7 +345,7 @@ bool remote::GoogleDrive::delete_item(const remote::Item *item) curl::append_header(header, m_authHeader); remote::URL url{URL_DRIVE_FILE_API}; - url.append_path(item->get_id()); + url.append_path(itemId); curl::reset_handle(m_curl); curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); @@ -371,6 +368,42 @@ bool remote::GoogleDrive::delete_item(const remote::Item *item) return true; } +bool remote::GoogleDrive::rename_item(remote::Item *item, std::string_view newName) +{ + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } + + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, m_authHeader); + curl::append_header(header, HEADER_CONTENT_TYPE_JSON); + + remote::URL url{URL_DRIVE_FILE_API}; + url.append_path(item->get_id()); + + json::Object post = json::new_object(json_object_new_object); + json_object *name = json_object_new_string(newName.data()); + json::add_object(post, "name", name); + + std::string response{}; + const char *postString = json_object_get_string(post.get()); + curl::reset_handle(m_curl); + curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_POSTFIELDS, HEADER_CONTENT_TYPE_JSON); + curl::set_option(m_curl, CURLOPT_POSTFIELDS, postString); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); + + if (!curl::perform(m_curl)) { return false; } + + // We're only doing this to check for errors. + json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); + if (GoogleDrive::error_occurred(responseParser)) { return false; } + + item->set_name(newName); + return true; +} + bool remote::GoogleDrive::sign_in_required() const { return !m_isInitialized || m_refreshToken.empty(); } bool remote::GoogleDrive::get_sign_in_data(std::string &message, std::string &code, std::time_t &expiration, int &wait) diff --git a/source/remote/WebDav.cpp b/source/remote/WebDav.cpp index 70fe624..8bb8bf9 100644 --- a/source/remote/WebDav.cpp +++ b/source/remote/WebDav.cpp @@ -184,19 +184,20 @@ bool remote::WebDav::download_file(const remote::Item *item, const fslib::Path & { static constexpr const char *STRING_ERROR_DOWNLOADING = "Error downloading file: %s"; - fslib::File destFile{destination, FsOpenMode_Create | FsOpenMode_Write, static_cast(item->get_size())}; + const int64_t itemSize = item->get_size(); + fslib::File destFile{destination, FsOpenMode_Create | FsOpenMode_Write, itemSize}; if (!destFile) { logger::log(STRING_ERROR_DOWNLOADING, fslib::error::get_string()); return false; } - if (task) { task->reset(static_cast(item->get_size())); } + if (task) { task->reset(static_cast(itemSize)); } remote::URL url{m_origin}; url.append_path(item->get_id()); - curl::DownloadStruct download{.dest = &destFile, .task = task, .fileSize = item->get_size()}; + curl::DownloadStruct download{.dest = &destFile, .task = task, .fileSize = itemSize}; curl::reset_handle(m_curl); WebDav::append_credentials(); curl::set_option(m_curl, CURLOPT_HTTPGET, 1L); @@ -222,7 +223,6 @@ bool remote::WebDav::delete_item(const remote::Item *item) remote::URL url{m_origin}; url.append_path(item->get_id()); if (item->is_directory()) { url.append_slash(); } - logger::log(url.get()); curl::reset_handle(m_curl); WebDav::append_credentials(); @@ -245,6 +245,48 @@ bool remote::WebDav::delete_item(const remote::Item *item) return true; } +bool remote::WebDav::rename_item(remote::Item *item, std::string_view newName) +{ + std::string escapedName{}; + const bool escaped = curl::escape_string(m_curl, newName, escapedName); + if (!escaped) { return false; } + + remote::URL url{m_origin}; + url.append_path(item->get_id()); + + remote::URL destLocation{m_origin}; + destLocation.append_path(m_parent).append_path(escapedName); + if (item->is_directory()) + { + url.append_slash(); + destLocation.append_slash(); + } + + const std::string destHeader = stringutil::get_formatted_string("Destination: %s", destLocation.get()); + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, destHeader); + + curl::reset_handle(m_curl); + WebDav::append_credentials(); + curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "MOVE"); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + + if (!curl::perform(m_curl)) { return false; } + + const long code = curl::get_response_code(m_curl); + if (code != 403 || code != 409) { return false; } + + std::string newId{}; + if (item->is_directory()) { newId = m_parent + escapedName + "/"; } + else { newId = m_parent + escapedName; } + + item->set_name(newName); + item->set_id(newId); + + return false; +} + void remote::WebDav::append_credentials() { if (!m_username.empty()) { curl::set_option(m_curl, CURLOPT_USERNAME, m_username.c_str()); } @@ -312,8 +354,6 @@ bool remote::WebDav::process_listing(std::string_view xml) tinyxml2::XMLElement *collection = get_element_by_name(resourceType, tagCollection); if (collection) { - logger::log("%s, %s, %s", name.c_str(), hrefText, parentLocationText); - m_list.emplace_back(name, hrefText, parentLocationText, 0, true); remote::URL nextUrl{m_origin}; diff --git a/source/remote/remote.cpp b/source/remote/remote.cpp index 5a64b1d..5726593 100644 --- a/source/remote/remote.cpp +++ b/source/remote/remote.cpp @@ -2,6 +2,7 @@ #include "StateManager.hpp" #include "appstates/TaskState.hpp" +#include "error.hpp" #include "logger.hpp" #include "remote/GoogleDrive.hpp" #include "remote/WebDav.hpp" @@ -30,18 +31,32 @@ static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive); /// @param drive Pointer to the drive instance.. static void drive_set_jksv_root(remote::GoogleDrive *drive); +bool remote::has_internet_connection() +{ + NifmInternetConnectionType type{}; + uint32_t strength{}; + NifmInternetConnectionStatus status{}; + const bool getError = error::libnx(nifmGetInternetConnectionStatus(&type, &strength, &status)); + if (getError || status != NifmInternetConnectionStatus_Connected) { return false; } + return true; +} + void remote::initialize_google_drive() { - s_storage = std::make_unique(); - remote::GoogleDrive *drive = static_cast(s_storage.get()); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popDriveSuccess = strings::get_by_name(strings::names::GOOGLE_DRIVE, 1); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + if (!remote::has_internet_connection()) + { + const char *popNoInternet = strings::get_by_name(strings::names::REMOTE_POPS, 0); + ui::PopMessageManager::push_message(popTicks, popNoInternet); + return; + } + + s_storage = std::make_unique(); + remote::GoogleDrive *drive = static_cast(s_storage.get()); if (drive->sign_in_required()) { - auto signIn = std::make_shared(drive_sign_in, drive); - StateManager::push_state(signIn); - // We can return here because the task should handle the rest of the setup for us. + TaskState::create_and_push(drive_sign_in, drive); return; } @@ -49,18 +64,26 @@ void remote::initialize_google_drive() if (!drive->is_initialized()) { return; } drive_set_jksv_root(drive); + const char *popDriveSuccess = strings::get_by_name(strings::names::GOOGLE_DRIVE, 1); ui::PopMessageManager::push_message(popTicks, popDriveSuccess); } void remote::initialize_webdav() { - s_storage = std::make_unique(); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popDavSuccess = strings::get_by_name(strings::names::WEBDAV, 0); - const char *popDavFailed = strings::get_by_name(strings::names::WEBDAV, 1); + if (!remote::has_internet_connection()) { return; } - if (s_storage->is_initialized()) { ui::PopMessageManager::push_message(popTicks, popDavSuccess); } - else { ui::PopMessageManager::push_message(popTicks, popDavFailed); } + s_storage = std::make_unique(); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + if (s_storage->is_initialized()) + { + const char *popDavSuccess = strings::get_by_name(strings::names::WEBDAV, 0); + ui::PopMessageManager::push_message(popTicks, popDavSuccess); + } + else + { + const char *popDavFailed = strings::get_by_name(strings::names::WEBDAV, 1); + ui::PopMessageManager::push_message(popTicks, popDavFailed); + } } remote::Storage *remote::get_remote_storage() @@ -72,18 +95,15 @@ remote::Storage *remote::get_remote_storage() static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive) { static constexpr const char *STRING_ERROR_SIGNING_IN = "Error signing into Google Drive: %s"; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popDriveSuccess = strings::get_by_name(strings::names::GOOGLE_DRIVE, 1); - const char *popDriveFailed = strings::get_by_name(strings::names::GOOGLE_DRIVE, 2); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; std::string message{}, deviceCode{}; std::time_t expiration{}; int pollingInterval{}; if (!drive->get_sign_in_data(message, deviceCode, expiration, pollingInterval)) { logger::log(STRING_ERROR_SIGNING_IN, "Getting sign in data failed!"); - task->finished(); - return; + TASK_FINISH_RETURN(task); } task->set_status(message.c_str()); @@ -96,17 +116,20 @@ static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive) if (drive->is_initialized()) { drive_set_jksv_root(drive); + const char *popDriveSuccess = strings::get_by_name(strings::names::GOOGLE_DRIVE, 1); ui::PopMessageManager::push_message(popTicks, popDriveSuccess); } - else { ui::PopMessageManager::push_message(popTicks, popDriveFailed); } + else + { + const char *popDriveFailed = strings::get_by_name(strings::names::GOOGLE_DRIVE, 2); + ui::PopMessageManager::push_message(popTicks, popDriveFailed); + } task->finished(); } static void drive_set_jksv_root(remote::GoogleDrive *drive) { - static constexpr const char *STRING_ERROR_SETTING_DIR = "Error creating/setting JKSV directory on Drive: %s"; - const bool jksvExists = drive->directory_exists(STRING_JKSV_DIR); const bool jksvCreated = !jksvExists && drive->create_directory(STRING_JKSV_DIR); if (!jksvExists && !jksvCreated) { return; } diff --git a/source/system/ProgressTask.cpp b/source/system/ProgressTask.cpp index 0baf564..13d50aa 100644 --- a/source/system/ProgressTask.cpp +++ b/source/system/ProgressTask.cpp @@ -1,4 +1,4 @@ -#include "system/ProgressTask.hpp" +#include "sys/ProgressTask.hpp" void sys::ProgressTask::reset(double goal) { diff --git a/source/system/Task.cpp b/source/system/Task.cpp index de69ee7..17c9d96 100644 --- a/source/system/Task.cpp +++ b/source/system/Task.cpp @@ -1,4 +1,4 @@ -#include "system/Task.hpp" +#include "sys/Task.hpp" #include diff --git a/source/system/Timer.cpp b/source/system/Timer.cpp index 2342095..d7666b0 100644 --- a/source/system/Timer.cpp +++ b/source/system/Timer.cpp @@ -1,4 +1,4 @@ -#include "system/Timer.hpp" +#include "sys/Timer.hpp" #include "logger.hpp" diff --git a/source/tasks/backup.cpp b/source/tasks/backup.cpp index a0fd5ea..3ace561 100644 --- a/source/tasks/backup.cpp +++ b/source/tasks/backup.cpp @@ -37,20 +37,12 @@ void tasks::backup::create_new_backup_local(sys::ProgressTask *task, const bool hasZipExt = std::strstr(target.full_path(), STRING_ZIP_EXT); const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); - if (error::is_null(saveInfo)) - { - task->finished(); - return; - } + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } if (hasZipExt) // At this point, this should have the zip extension appended if needed. { fs::MiniZip zip{target}; - if (!zip.is_open()) - { - task->finished(); - return; - } + if (!zip.is_open()) { TASK_FINISH_RETURN(task); } write_meta_zip(zip, saveInfo); auto scopedMount = create_scoped_mount(saveInfo); @@ -83,25 +75,17 @@ void tasks::backup::create_new_backup_remote(sys::ProgressTask *task, const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); - if (error::is_null(saveInfo)) - { - task->finished(); - return; - } + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } const fslib::Path tempPath{STRING_JKSV_TEMP}; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *uploadTemplate = strings::get_by_name(strings::names::IO_STATUSES, 5); - const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); - const char *popErrorUploading = strings::get_by_name(strings::names::BACKUPMENU_POPS, 10); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; fs::MiniZip zip{tempPath}; if (!zip.is_open()) { + const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); ui::PopMessageManager::push_message(popTicks, popErrorCreating); - task->finished(); - return; + TASK_FINISH_RETURN(task); } write_meta_zip(zip, saveInfo); @@ -112,12 +96,17 @@ void tasks::backup::create_new_backup_remote(sys::ProgressTask *task, zip.close(); { - const std::string status = stringutil::get_formatted_string(uploadTemplate, remoteName.data()); + const char *uploadFormat = strings::get_by_name(strings::names::IO_STATUSES, 5); + const std::string status = stringutil::get_formatted_string(uploadFormat, remoteName.data()); task->set_status(status); } const bool uploaded = remote->upload_file(tempPath, remoteName, task); const bool deleteError = error::fslib(fslib::delete_file(tempPath)); - if (!uploaded || deleteError) { ui::PopMessageManager::push_message(popTicks, popErrorUploading); } + if (!uploaded || deleteError) + { + const char *popErrorUploading = strings::get_by_name(strings::names::BACKUPMENU_POPS, 10); + ui::PopMessageManager::push_message(popTicks, popErrorUploading); + } spawningState->refresh(); if (killTask) { task->finished(); } @@ -131,17 +120,17 @@ void tasks::backup::overwrite_backup_local(sys::ProgressTask *task, BackupMenuSt data::TitleInfo *titleInfo = taskData->titleInfo; const fslib::Path &target = taskData->path; BackupMenuState *spawningState = taskData->spawningState; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + if (error::is_null(user) || error::is_null(titleInfo) || error::is_null(spawningState)) { TASK_FINISH_RETURN(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)); if (dirFailed && fileFailed) { + const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); ui::PopMessageManager::push_message(popTicks, popErrorDeleting); - task->finished(); - return; + TASK_FINISH_RETURN(task); } tasks::backup::create_new_backup_local(task, user, titleInfo, target, spawningState); } @@ -150,30 +139,21 @@ void tasks::backup::overwrite_backup_remote(sys::ProgressTask *task, BackupMenuS { if (error::is_null(task)) { return; } - remote::Storage *remote = remote::get_remote_storage(); - data::User *user = taskData->user; - data::TitleInfo *titleInfo = taskData->titleInfo; + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + remote::Item *target = taskData->remoteItem; + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(remote) || error::is_null(target)) { TASK_FINISH_RETURN(task); } + const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); - remote::Item *target = taskData->remoteItem; - if (error::is_null(remote) || error::is_null(saveInfo) || error::is_null(target)) - { - task->finished(); - return; - } + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } const fslib::Path tempPath{STRING_JKSV_TEMP}; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - const char *popErrorMeta = strings::get_by_name(strings::names::BACKUPMENU_POPS, 8); - const char *statusUploading = strings::get_by_name(strings::names::IO_STATUSES, 5); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; fs::MiniZip zip{tempPath}; - if (!zip.is_open()) - { - task->finished(); - return; - } + if (!zip.is_open()) { TASK_FINISH_RETURN(task); } write_meta_zip(zip, saveInfo); { @@ -184,42 +164,41 @@ void tasks::backup::overwrite_backup_remote(sys::ProgressTask *task, BackupMenuS { const char *targetName = target->get_name().data(); - const std::string status = stringutil::get_formatted_string(statusUploading, targetName); + const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 5); + const std::string status = stringutil::get_formatted_string(statusFormat, targetName); task->set_status(status); } remote->patch_file(target, tempPath, task); const bool deleteError = error::fslib(fslib::delete_file(tempPath)); - if (deleteError) { ui::PopMessageManager::push_message(popTicks, popErrorDeleting); }; + if (deleteError) + { + const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + ui::PopMessageManager::push_message(popTicks, popErrorDeleting); + } task->finished(); } void tasks::backup::restore_backup_local(sys::ProgressTask *task, BackupMenuState::TaskData taskData) { - static constexpr size_t SIZE_META = sizeof(fs::SaveMetaData); if (error::is_null(task)) { return; } - remote::Storage *remote = remote::get_remote_storage(); data::User *user = taskData->user; data::TitleInfo *titleInfo = taskData->titleInfo; const fslib::Path &target = taskData->path; BackupMenuState *spawningState = taskData->spawningState; - if (error::is_null(remote) || error::is_null(user) || error::is_null(titleInfo) || error::is_null(spawningState)) + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(user) || error::is_null(titleInfo) || error::is_null(spawningState) || error::is_null(remote)) { - task->finished(); - return; + TASK_FINISH_RETURN(task); } const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); const uint8_t saveType = user->get_account_save_type(); const uint64_t journalSize = titleInfo->get_journal_size(saveType); - if (error::is_null(saveInfo)) - { - task->finished(); - return; - } + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } const bool autoBackup = config::get_by_key(config::keys::AUTO_BACKUP_ON_RESTORE); const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); @@ -227,36 +206,28 @@ void tasks::backup::restore_backup_local(sys::ProgressTask *task, BackupMenuStat const bool isDir = fslib::directory_exists(target); const bool hasZipExt = std::strstr(target.full_path(), STRING_ZIP_EXT); - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorResetting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 2); - const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); - const char *popErrorOpenZip = strings::get_by_name(strings::names::EXTRASMENU_POPS, 7); - const char *statusUploading = strings::get_by_name(strings::names::IO_STATUSES, 5); - + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; if (autoBackup) { fslib::Path autoTarget{}; const size_t lastSlash = target.find_last_of('/'); if (lastSlash == target.NOT_FOUND) { + const char *popErrorCreating = strings::get_by_name(strings::names::BACKUPMENU_POPS, 5); ui::PopMessageManager::push_message(popTicks, popErrorCreating); - task->finished(); - return; + TASK_FINISH_RETURN(task); } const char *safeNickname = user->get_path_safe_nickname(); autoTarget = target.sub_path(lastSlash) / "AUTO - " + safeNickname + " - " + stringutil::get_date_string(); if (exportZip) { autoTarget += ".zip"; }; - - { - auto scopedMount = create_scoped_mount(saveInfo); - tasks::backup::create_new_backup_local(task, user, titleInfo, autoTarget, spawningState, false); - } + tasks::backup::create_new_backup_local(task, user, titleInfo, autoTarget, spawningState, false); // Not sure if this is really needed here, but I'm sure someone will point it out. if (autoUpload && remote) { - const std::string status = stringutil::get_formatted_string(statusUploading, autoTarget.get_filename()); + const char *statusUploading = strings::get_by_name(strings::names::IO_STATUSES, 5); + const std::string status = stringutil::get_formatted_string(statusUploading, autoTarget.get_filename()); task->set_status(status); remote->upload_file(autoTarget, autoTarget.get_filename(), task); fslib::delete_file(autoTarget); // I don't care if this fails, @@ -269,9 +240,9 @@ void tasks::backup::restore_backup_local(sys::ProgressTask *task, BackupMenuStat const bool commitError = error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); if (resetError || commitError) { + const char *popErrorResetting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 2); ui::PopMessageManager::push_message(popTicks, popErrorResetting); - task->finished(); - return; + TASK_FINISH_RETURN(task); } } @@ -280,9 +251,9 @@ void tasks::backup::restore_backup_local(sys::ProgressTask *task, BackupMenuStat fs::MiniUnzip unzip{target}; if (!unzip.is_open()) { + const char *popErrorOpenZip = strings::get_by_name(strings::names::EXTRASMENU_POPS, 7); ui::PopMessageManager::push_message(popTicks, popErrorOpenZip); - task->finished(); - return; + TASK_FINISH_RETURN(task); } read_and_process_meta(unzip, taskData, task); @@ -309,55 +280,39 @@ void tasks::backup::restore_backup_remote(sys::ProgressTask *task, BackupMenuSta { if (error::is_null(task)) { return; } - data::User *user = taskData->user; - data::TitleInfo *titleInfo = taskData->titleInfo; + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + remote::Storage *remote = remote::get_remote_storage(); + 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); - if (error::is_null(user) || error::is_null(titleInfo) || error::is_null(saveInfo)) + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + remote::Item *target = taskData->remoteItem; + const fslib::Path tempPath{STRING_JKSV_TEMP}; { - task->finished(); - return; - } - - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorResetting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 2); - const char *popErrorOpeningZip = strings::get_by_name(strings::names::BACKUPMENU_POPS, 3); - const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); - const char *popErrorDownloading = strings::get_by_name(strings::names::BACKUPMENU_POPS, 9); - const char *popErrorProcessingMeta = strings::get_by_name(strings::names::BACKUPMENU_POPS, 11); - const char *downloadingFormat = strings::get_by_name(strings::names::IO_STATUSES, 4); - - remote::Storage *remote = remote::get_remote_storage(); - if (!remote) - { - task->finished(); - return; - } - - remote::Item *target = taskData->remoteItem; - const char *statusDownloading = strings::get_by_name(strings::names::IO_STATUSES, 4); - const fslib::Path tempPath{"sdmc:/temp.zip"}; - - { - const char *name = target->get_name().data(); - const std::string status = stringutil::get_formatted_string(downloadingFormat, name); + const char *name = target->get_name().data(); + const char *downloadingFormat = strings::get_by_name(strings::names::IO_STATUSES, 4); + const std::string status = stringutil::get_formatted_string(downloadingFormat, name); task->set_status(status); } 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); - task->finished(); - return; + TASK_FINISH_RETURN(task); } 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); - task->finished(); - return; + TASK_FINISH_RETURN(task); } { @@ -366,9 +321,9 @@ void tasks::backup::restore_backup_remote(sys::ProgressTask *task, BackupMenuSta const bool commitError = error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); if (deleteError || commitError) { + const char *popErrorResetting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 2); ui::PopMessageManager::push_message(popTicks, popErrorResetting); - task->finished(); - return; + TASK_FINISH_RETURN(task); } } @@ -382,7 +337,11 @@ void tasks::backup::restore_backup_remote(sys::ProgressTask *task, BackupMenuSta backup.close(); const bool deleteError = error::fslib(fslib::delete_file(tempPath)); - if (deleteError) { ui::PopMessageManager::push_message(popTicks, popErrorDeleting); } + if (deleteError) + { + const char *popErrorDeleting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + ui::PopMessageManager::push_message(popTicks, popErrorDeleting); + } task->finished(); } @@ -390,43 +349,54 @@ void tasks::backup::restore_backup_remote(sys::ProgressTask *task, BackupMenuSta void tasks::backup::delete_backup_local(sys::Task *task, BackupMenuState::TaskData taskData) { if (error::is_null(task)) { return; } + const fslib::Path &path = taskData->path; BackupMenuState *spawningState = taskData->spawningState; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *statusTemplate = strings::get_by_name(strings::names::IO_STATUSES, 3); - const char *popFailed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + if (error::is_null(spawningState)) { TASK_FINISH_RETURN(task); } + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; { - const std::string status = stringutil::get_formatted_string(statusTemplate, path.full_path()); + const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 3); + const std::string status = stringutil::get_formatted_string(statusFormat, path.full_path()); task->set_status(status); } const bool isDir = fslib::directory_exists(path); const bool dirError = isDir && error::fslib(fslib::delete_directory_recursively(path)); const bool fileError = !isDir && error::fslib(fslib::delete_file(path)); - if (dirError || fileError) { ui::PopMessageManager::push_message(popTicks, popFailed); } + if (dirError || fileError) + { + const char *popFailed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + ui::PopMessageManager::push_message(popTicks, popFailed); + } + spawningState->refresh(); task->finished(); } void tasks::backup::delete_backup_remote(sys::Task *task, BackupMenuState::TaskData taskData) { - remote::Storage *remote = remote::get_remote_storage(); - if (error::is_null(task) || error::is_null(remote)) { return; } + if (error::is_null(task)) { return; } remote::Item *target = taskData->remoteItem; BackupMenuState *spawningState = taskData->spawningState; - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *statusTemplate = strings::get_by_name(strings::names::IO_STATUSES, 3); - const char *popFailed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(target) || error::is_null(spawningState) || error::is_null(remote)) { TASK_FINISH_RETURN(task); } + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; { - const std::string status = stringutil::get_formatted_string(statusTemplate, target->get_name().data()); + const char *targetName = target->get_name().data(); + const char *statusTemplate = strings::get_by_name(strings::names::IO_STATUSES, 3); + const std::string status = stringutil::get_formatted_string(statusTemplate, targetName); task->set_status(status); } const bool deleted = remote->delete_item(target); - if (!deleted) { ui::PopMessageManager::push_message(popTicks, popFailed); } + if (!deleted) + { + const char *popFailed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 4); + ui::PopMessageManager::push_message(popTicks, popFailed); + } spawningState->refresh(); task->finished(); @@ -434,45 +404,74 @@ void tasks::backup::delete_backup_remote(sys::Task *task, BackupMenuState::TaskD void tasks::backup::upload_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData) { - remote::Storage *remote = remote::get_remote_storage(); - if (error::is_null(task) || error::is_null(remote)) { return; } + if (error::is_null(task)) { return; } const fslib::Path &path = taskData->path; BackupMenuState *spawningState = taskData->spawningState; - const char *statusTemplate = strings::get_by_name(strings::names::IO_STATUSES, 5); + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(spawningState) || error::is_null(remote)) { TASK_FINISH_RETURN(task); } { const char *filename = path.get_filename(); - const std::string status = stringutil::get_formatted_string(statusTemplate, filename); + const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 5); + const std::string status = stringutil::get_formatted_string(statusFormat, filename); task->set_status(status); } + // The backup menu should've made sure the remote is pointing to the correct location. remote->upload_file(path, path.get_filename(), task); - spawningState->refresh(); task->finished(); } +void tasks::backup::patch_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + const fslib::Path &path = taskData->path; + remote::Item *remoteItem = taskData->remoteItem; + BackupMenuState *spawningState = taskData->spawningState; + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(spawningState) || error::is_null(remote)) { TASK_FINISH_RETURN(task); } + + { + const char *filename = path.get_filename(); + const char *statusFormat = strings::get_by_name(strings::names::IO_STATUSES, 5); + const std::string status = stringutil::get_formatted_string(statusFormat, filename); + task->set_status(status); + } + + remote->patch_file(remoteItem, path, task); + task->finished(); +} + static bool read_and_process_meta(const fslib::Path &targetDir, BackupMenuState::TaskData taskData, sys::ProgressTask *task) { if (error::is_null(task)) { return false; } - data::User *user = taskData->user; - data::TitleInfo *titleInfo = taskData->titleInfo; + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + if (error::is_null(user) || error::is_null(titleInfo)) + { + task->finished(); + return false; + } + const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); if (error::is_null(saveInfo)) { return false; } - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorProcessing = strings::get_by_name(strings::names::BACKUPMENU_POPS, 11); - const char *statusProcessing = strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0); - - task->set_status(statusProcessing); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + { + const char *statusProcessing = strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0); + task->set_status(statusProcessing); + } const fslib::Path metaPath{targetDir / fs::NAME_SAVE_META}; fslib::File metaFile{metaPath, FsOpenMode_Read}; if (!metaFile.is_open()) { + const char *popErrorProcessing = strings::get_by_name(strings::names::BACKUPMENU_POPS, 11); ui::PopMessageManager::push_message(popTicks, popErrorProcessing); return false; } @@ -482,6 +481,7 @@ static bool read_and_process_meta(const fslib::Path &targetDir, BackupMenuState: const bool processed = metaRead && fs::process_save_meta_data(saveInfo, metaData); if (!metaRead || !processed) { + const char *popErrorProcessing = strings::get_by_name(strings::names::BACKUPMENU_POPS, 11); ui::PopMessageManager::push_message(popTicks, popErrorProcessing); return false; } @@ -493,17 +493,23 @@ static bool read_and_process_meta(fs::MiniUnzip &unzip, BackupMenuState::TaskDat { if (error::is_null(task)) { return false; } - data::User *user = taskData->user; - data::TitleInfo *titleInfo = taskData->titleInfo; + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + if (error::is_null(user) || error::is_null(titleInfo)) + { + task->finished(); + return false; + } + const uint64_t applicationID = titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); if (error::is_null(saveInfo)) { return false; } - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorProcessing = strings::get_by_name(strings::names::BACKUPMENU_POPS, 11); - const char *statusProcessing = strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0); - - task->set_status(statusProcessing); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + { + const char *statusProcessing = strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0); + task->set_status(statusProcessing); + } fs::SaveMetaData saveMeta{}; const bool metaFound = unzip.locate_file(fs::NAME_SAVE_META); @@ -511,9 +517,11 @@ static bool read_and_process_meta(fs::MiniUnzip &unzip, BackupMenuState::TaskDat const bool metaProcessed = metaRead && fs::process_save_meta_data(saveInfo, saveMeta); if (!metaFound || !metaRead || !metaProcessed) { + const char *popErrorProcessing = strings::get_by_name(strings::names::BACKUPMENU_POPS, 11); ui::PopMessageManager::push_message(popTicks, popErrorProcessing); return false; } + return true; } @@ -522,8 +530,8 @@ static void write_meta_file(const fslib::Path &target, const FsSaveDataInfo *sav const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; const char *popErrorWritingMeta = strings::get_by_name(strings::names::BACKUPMENU_POPS, 8); - const fslib::Path metaPath{target / fs::NAME_SAVE_META}; fs::SaveMetaData saveMeta{}; + const fslib::Path metaPath{target / fs::NAME_SAVE_META}; const bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta); fslib::File metaFile{metaPath, FsOpenMode_Create | FsOpenMode_Write, SIZE_SAVE_META}; if (!metaFile.is_open() || !hasMeta) @@ -538,23 +546,30 @@ static void write_meta_file(const fslib::Path &target, const FsSaveDataInfo *sav static void write_meta_zip(fs::MiniZip &zip, const FsSaveDataInfo *saveInfo) { - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorWritingMeta = strings::get_by_name(strings::names::BACKUPMENU_POPS, 8); + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; fs::SaveMetaData saveMeta{}; - const bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta); const bool openMeta = hasMeta && zip.open_new_file(fs::NAME_SAVE_META); - const bool writeMeta = openMeta && zip.write(&saveMeta, SIZE_SAVE_META) == SIZE_SAVE_META; + const bool writeMeta = openMeta && zip.write(&saveMeta, SIZE_SAVE_META); const bool closeMeta = openMeta && zip.close_current_file(); + if (hasMeta && (!openMeta || !writeMeta || !closeMeta)) + { + const char *popErrorWritingMeta = strings::get_by_name(strings::names::BACKUPMENU_POPS, 8); + ui::PopMessageManager::push_message(popTicks, popErrorWritingMeta); + } } static fs::ScopedSaveMount create_scoped_mount(const FsSaveDataInfo *saveInfo) { - const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; - const char *popErrorMounting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 14); - fs::ScopedSaveMount saveMount{fs::DEFAULT_SAVE_MOUNT, saveInfo}; - if (!saveMount.is_open()) { ui::PopMessageManager::push_message(popTicks, popErrorMounting); } + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + fs::ScopedSaveMount saveMount{fs::DEFAULT_SAVE_MOUNT, saveInfo}; + if (!saveMount.is_open()) + { + + const char *popErrorMounting = strings::get_by_name(strings::names::BACKUPMENU_POPS, 14); + ui::PopMessageManager::push_message(popTicks, popErrorMounting); + } return saveMount; } diff --git a/source/tasks/savecreate.cpp b/source/tasks/savecreate.cpp new file mode 100644 index 0000000..0e05195 --- /dev/null +++ b/source/tasks/savecreate.cpp @@ -0,0 +1,37 @@ +#include "tasks/savecreate.hpp" + +#include "error.hpp" +#include "fs/fs.hpp" +#include "strings.hpp" +#include "stringutil.hpp" +#include "ui/PopMessageManager.hpp" + +void tasks::savecreate::create_save_data_for(sys::Task *task, + data::User *user, + data::TitleInfo *titleInfo, + SaveCreateState *spawningState) +{ + if (error::is_null(task) || error::is_null(user) || error::is_null(titleInfo) || error::is_null(spawningState)) { return; } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *statusFormat = strings::get_by_name(strings::names::USEROPTION_STATUS, 0); + const char *popSuccess = strings::get_by_name(strings::names::SAVECREATE_POPS, 0); + const char *popFailed = strings::get_by_name(strings::names::SAVECREATE_POPS, 1); + const char *title = titleInfo->get_title(); + + { + 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) { ui::PopMessageManager::push_message(popTicks, popFailed); } + else + { + const std::string popMessage = stringutil::get_formatted_string(popSuccess, title); + ui::PopMessageManager::push_message(popTicks, popMessage); + } + + spawningState->refresh_required(); + task->finished(); +} diff --git a/source/tasks/titleoptions.cpp b/source/tasks/titleoptions.cpp new file mode 100644 index 0000000..b8a3b48 --- /dev/null +++ b/source/tasks/titleoptions.cpp @@ -0,0 +1,234 @@ +#include "tasks/titleoptions.hpp" + +#include "config.hpp" +#include "data/data.hpp" +#include "error.hpp" +#include "fs/fs.hpp" +#include "keyboard.hpp" +#include "remote/remote.hpp" +#include "strings.hpp" +#include "stringutil.hpp" +#include "ui/ui.hpp" + +#include + +void tasks::titleoptions::blacklist_title(sys::Task *task, TitleOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::TitleInfo *titleInfo = taskData->titleInfo; + TitleOptionState *spawningState = taskData->spawningState; + if (error::is_null(titleInfo) || error::is_null(spawningState)) + { + task->finished(); + return; + } + + const uint64_t applicationID = titleInfo->get_application_id(); + config::add_remove_blacklist(applicationID); + + data::UserList list{}; + data::get_users(list); + for (data::User *user : list) { user->erase_save_info_by_id(applicationID); } + + // We need to signal both since the title in question is no longer valid. + spawningState->refresh_required(); + spawningState->close_on_update(); + + task->finished(); +} + +void tasks::titleoptions::delete_all_local_backups_for_title(sys::Task *task, TitleOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::TitleInfo *titleInfo = taskData->titleInfo; + if (error::is_null(titleInfo)) { TASK_FINISH_RETURN(task); } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 0); + const char *popFailure = strings::get_by_name(strings::names::TITLEOPTION_POPS, 1); + + { + const char *title = titleInfo->get_title(); + const char *statusFormat = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 0); + const std::string status = stringutil::get_formatted_string(statusFormat, title); + task->set_status(status); + } + + const char *safeTitle = titleInfo->get_path_safe_title(); + const fslib::Path workingDir{config::get_working_directory()}; + const fslib::Path targetPath{workingDir / safeTitle}; + + const bool dirExists = fslib::directory_exists(targetPath); + const bool deleteFailed = dirExists && error::fslib(fslib::delete_directory_recursively(targetPath)); + if (deleteFailed) { ui::PopMessageManager::push_message(popTicks, popFailure); } + else + { + const char *title = titleInfo->get_title(); + const std::string popMessage = stringutil::get_formatted_string(popSuccess, title); + ui::PopMessageManager::push_message(popTicks, popMessage); + } + + task->finished(); +} + +void tasks::titleoptions::delete_all_remote_backups_for_title(sys::Task *task, TitleOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::TitleInfo *titleInfo = taskData->titleInfo; + remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(titleInfo) || error::is_null(remote)) { TASK_FINISH_RETURN(task); } + + const char *title = titleInfo->get_title(); + const std::string_view remoteTitle = remote->supports_utf8() ? titleInfo->get_title() : titleInfo->get_path_safe_title(); + const bool exists = remote->directory_exists(remoteTitle); + if (!exists) { TASK_FINISH_RETURN(task); } + + remote::Item *workDir = remote->get_directory_by_name(remoteTitle); + remote->change_directory(workDir); + remote::Storage::DirectoryListing remoteListing = remote->get_directory_listing(); + + { + const char *statusFormat = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 0); + const std::string status = stringutil::get_formatted_string(statusFormat, title); + task->set_status(status); + } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 0); + const char *popFailure = strings::get_by_name(strings::names::TITLEOPTION_POPS, 1); + for (remote::Item *item : remoteListing) + { + const bool deleted = remote->delete_item(item); + if (!deleted) { ui::PopMessageManager::push_message(popTicks, popFailure); } + } + + const std::string popMessage = stringutil::get_formatted_string(popSuccess, title); + ui::PopMessageManager::push_message(popTicks, popMessage); + task->finished(); +} + +void tasks::titleoptions::reset_save_data(sys::Task *task, TitleOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + if (error::is_null(user) || error::is_null(titleInfo)) { TASK_FINISH_RETURN(task); } + + const uint64_t applicationID = titleInfo->get_application_id(); + const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const char *popFailed = strings::get_by_name(strings::names::TITLEOPTION_POPS, 2); + const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 3); + + { + const char *statusFormat = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 1); + const char *title = titleInfo->get_title(); + const std::string status = stringutil::get_formatted_string(statusFormat, title); + task->set_status(status); + } + + { + fs::ScopedSaveMount saveMount{fs::DEFAULT_SAVE_MOUNT, saveInfo}; + const bool resetFailed = error::fslib(fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)); + const bool commitFailed = error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); + if (resetFailed || commitFailed) { ui::PopMessageManager::push_message(popTicks, popFailed); } + else { ui::PopMessageManager::push_message(popTicks, popSuccess); } + } + + task->finished(); +} + +void tasks::titleoptions::delete_save_data_from_system(sys::Task *task, TitleOptionState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + TitleSelectCommon *titleSelect = taskData->titleSelect; + TitleOptionState *spawningState = taskData->spawningState; + if (error::is_null(user) || error::is_null(titleInfo) || error::is_null(titleSelect) || error::is_null(spawningState)) + { + TASK_FINISH_RETURN(task); + } + + const uint64_t applicationID = titleInfo->get_application_id(); + const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + { + const char *statusFormat = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 2); + const char *nickname = user->get_nickname(); + const char *title = titleInfo->get_title(); + const std::string status = stringutil::get_formatted_string(statusFormat, nickname, title); + task->set_status(status); + } + + const bool saveDeleted = fs::delete_save_data(saveInfo); + if (!saveDeleted) + { + const char *popError = strings::get_by_name(strings::names::SAVECREATE_POPS, 2); + ui::PopMessageManager::push_message(popTicks, popError); + } + else + { + const char *title = titleInfo->get_title(); + const char *popSuccessFormat = strings::get_by_name(strings::names::SAVECREATE_POPS, 0); + const std::string popMessage = stringutil::get_formatted_string(popSuccessFormat, title); + } + + user->erase_save_info_by_id(applicationID); + titleSelect->refresh(); + spawningState->close_on_update(); + task->finished(); +} + +void tasks::titleoptions::extend_save_data(sys::Task *task, TitleOptionState::TaskData taskData) +{ + static constexpr size_t SIZE_EXTRA_DATA = sizeof(FsSaveDataExtraData); + static constexpr size_t SIZE_MB = 0x100000; + + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + if (error::is_null(user) || error::is_null(titleInfo)) { TASK_FINISH_RETURN(task); } + + const int popTicks = ui::PopMessageManager::DEFAULT_TICKS; + const uint64_t applicationID = titleInfo->get_application_id(); + const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); + if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); } + + std::array sizeBuffer = {0}; + FsSaveDataExtraData extraData{}; + const FsSaveDataSpaceId spaceId = static_cast(saveInfo->save_data_space_id); + const uint64_t saveDataId = saveInfo->save_data_id; + const char *keyboardHeader = strings::get_by_name(strings::names::KEYBOARD, 8); + const std::string keyboardDefault = stringutil::get_formatted_string("%u", (extraData.data_size / SIZE_MB) + SIZE_MB); + const bool extraError = + error::libnx(fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(&extraData, SIZE_EXTRA_DATA, spaceId, saveDataId)); + const bool validInput = keyboard::get_input(SwkbdType_NumPad, keyboardDefault, keyboardHeader, sizeBuffer.data(), 5); + if (!validInput) { TASK_FINISH_RETURN(task); } + + const uint8_t saveType = saveInfo->save_data_type; + const int64_t size = std::strtoll(sizeBuffer.data(), nullptr, 10) * SIZE_MB; + const int64_t journal = extraError ? titleInfo->get_journal_size(saveType) : extraData.journal_size; + const bool saveExtended = fs::extend_save_data(saveInfo, size, journal); + if (saveExtended) + { + const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 10); + ui::PopMessageManager::push_message(popTicks, popSuccess); + } + else + { + const char *popFailed = strings::get_by_name(strings::names::TITLEOPTION_POPS, 11); + ui::PopMessageManager::push_message(popTicks, popFailed); + } + task->finished(); +} diff --git a/source/ui/Menu.cpp b/source/ui/Menu.cpp index 008bead..757c004 100644 --- a/source/ui/Menu.cpp +++ b/source/ui/Menu.cpp @@ -3,6 +3,7 @@ #include "colors.hpp" #include "config.hpp" #include "input.hpp" +#include "mathutil.hpp" #include "ui/render_functions.hpp" #include @@ -76,7 +77,13 @@ void ui::Menu::update(bool hasFocus) else if (m_selected >= endScrollPoint) { m_targetY = m_originalY - (optionsSize - m_maxDisplayOptions) * m_optionHeight; } else if (m_selected >= m_scrollLength) { m_targetY = m_originalY - (scrolledItems * m_optionHeight); } - if (m_y != m_targetY) { m_y += std::ceil((m_targetY - m_y) / scaling); } + if (m_y != m_targetY) + { + m_y += std::round((m_targetY - m_y) / scaling); + + const int distance = math::Util::get_absolute_distance(m_y, m_targetY); + if (distance <= 2) { m_y = m_targetY; } + } } void ui::Menu::render(SDL_Texture *target, bool hasFocus) diff --git a/source/ui/SlideOutPanel.cpp b/source/ui/SlideOutPanel.cpp index d15e91a..9e7109d 100644 --- a/source/ui/SlideOutPanel.cpp +++ b/source/ui/SlideOutPanel.cpp @@ -2,8 +2,10 @@ #include "colors.hpp" #include "config.hpp" +#include "mathutil.hpp" #include +#include namespace { @@ -11,35 +13,27 @@ namespace } ui::SlideOutPanel::SlideOutPanel(int width, Side side) - : m_x(side == Side::Left ? -width : SCREEN_WIDTH) - , m_width(width) - , m_targetX(side == Side::Left ? 0 : SCREEN_WIDTH - m_width) - , m_side(side) + : m_x{side == Side::Left ? static_cast(-width) : static_cast(SCREEN_WIDTH)} + , m_width{width} + , m_targetX{side == Side::Left ? 0.0f : static_cast(SCREEN_WIDTH) - m_width} + , m_side{side} + , m_scaling{config::get_animation_scaling()} { - static int slidePanelTargetID = 0; - std::string panelTargetName = "PanelTarget_" + std::to_string(slidePanelTargetID++); - m_renderTarget = sdl::TextureManager::create_load_texture(panelTargetName, - width, - 720, - SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); + static constexpr int sdlFlags = SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET; + + static int targetID = 0; + std::string targetName = "panelTarget_" + std::to_string(targetID++); + m_renderTarget = sdl::TextureManager::create_load_texture(targetName, width, 720, sdlFlags); } void ui::SlideOutPanel::update(bool hasFocus) { - const double scaling = config::get_animation_scaling(); const bool openingFromLeft = !m_isOpen && m_side == Side::Left && m_x < m_targetX; const bool openingFromRight = !m_isOpen && m_side == Side::Right && m_x > m_targetX; - if (openingFromLeft) { m_x -= std::round(m_x / scaling); } - else if (openingFromRight) - { - const double screenWidth = static_cast(SCREEN_WIDTH); - const double width = static_cast(m_width); - const double pixels = (screenWidth - width - m_x) / scaling; - m_x += std::round(pixels); - } - else { m_isOpen = true; } - // I'm going to leave it to the individual elements whether they update if the state is active. + if (openingFromLeft) { SlideOutPanel::slide_out_left(); } + else if (openingFromRight) { SlideOutPanel::slide_out_right(); } + if (m_isOpen) { for (auto ¤tElement : m_elements) { currentElement->update(hasFocus); } @@ -84,3 +78,31 @@ void ui::SlideOutPanel::push_new_element(std::shared_ptr newElement void ui::SlideOutPanel::clear_elements() { m_elements.clear(); } SDL_Texture *ui::SlideOutPanel::get_target() { return m_renderTarget->get(); } + +void ui::SlideOutPanel::slide_out_left() +{ + m_x -= std::round(m_x / m_scaling); + + // This is a workaround for the floating points never lining up quite right. + const int distance = math::Util::get_absolute_distance(m_x, m_targetX); + if (distance <= 2) + { + m_x = m_targetX; + m_isOpen = true; + } +} + +void ui::SlideOutPanel::slide_out_right() +{ + const double screenWidth = static_cast(SCREEN_WIDTH); + const double width = static_cast(m_width); + const double pixels = (screenWidth - width - m_x) / m_scaling; + m_x += std::round(pixels); + + const int distance = math::Util::get_absolute_distance(m_x, m_targetX); + if (distance <= 2) + { + m_x = m_targetX; + m_isOpen = true; + } +} diff --git a/source/ui/TitleTile.cpp b/source/ui/TitleTile.cpp index d1e6d13..487aa1a 100644 --- a/source/ui/TitleTile.cpp +++ b/source/ui/TitleTile.cpp @@ -3,17 +3,19 @@ #include "colors.hpp" #include "logger.hpp" -ui::TitleTile::TitleTile(bool isFavorite, sdl::SharedTexture icon) +ui::TitleTile::TitleTile(bool isFavorite, int index, sdl::SharedTexture icon) : m_isFavorite(isFavorite) + , m_index(index) , m_icon(icon) {}; -void ui::TitleTile::update(bool isSelected) +void ui::TitleTile::update(int selected) { static constexpr int BASE_WIDTH = 128; static constexpr int EXPAND_WIDTH = 176; static constexpr int INCREASE = 16; static constexpr int DECREASE = 8; + const bool isSelected = m_index == selected; if (isSelected && m_renderWidth != EXPAND_WIDTH) { // I think it's safe to assume both are too small. diff --git a/source/ui/TitleView.cpp b/source/ui/TitleView.cpp index b4df5d2..58bdbf1 100644 --- a/source/ui/TitleView.cpp +++ b/source/ui/TitleView.cpp @@ -3,7 +3,6 @@ #include "colors.hpp" #include "config.hpp" #include "input.hpp" -#include "logger.hpp" #include "ui/render_functions.hpp" #include @@ -21,45 +20,12 @@ ui::TitleView::TitleView(data::User *user) void ui::TitleView::update(bool hasFocus) { - // These are named like this because of where they sit on the screen. - static constexpr double UPPER_THRESHOLD = 32.0f; - static constexpr double LOWER_THRESHOLD = 388.0f; - if (m_titleTiles.empty()) { return; } - // Update pulse - if (hasFocus) { m_colorMod.update(); } - - const bool upPressed = input::button_pressed(HidNpadButton_AnyUp); - const bool downPressed = input::button_pressed(HidNpadButton_AnyDown); - const bool leftPressed = input::button_pressed(HidNpadButton_AnyLeft); - const bool rightPressed = input::button_pressed(HidNpadButton_AnyRight); - const bool lShoulderPressed = input::button_pressed(HidNpadButton_L); - const bool rShoulderPressed = input::button_pressed(HidNpadButton_R); - const int totalTiles = m_titleTiles.size() - 1; - - if (upPressed) { m_selected -= ICON_ROW_SIZE; } - else if (leftPressed) { --m_selected; } - else if (lShoulderPressed) { m_selected -= ICON_ROW_SIZE * 3; } - else if (downPressed) { m_selected += ICON_ROW_SIZE; } - else if (rightPressed) { ++m_selected; } - else if (rShoulderPressed) { m_selected += ICON_ROW_SIZE * 3; } - - if (m_selected < 0) { m_selected = 0; } - else if (m_selected > totalTiles) { m_selected = totalTiles; } - - const double scaling = config::get_animation_scaling(); - if (m_selectedY < UPPER_THRESHOLD) { m_y += std::ceil((UPPER_THRESHOLD - m_selectedY) / scaling); } - else if (m_selectedY > LOWER_THRESHOLD) { m_y += std::ceil((LOWER_THRESHOLD - m_selectedY) / scaling); } - - const int tileCount = m_titleTiles.size(); - for (int i = 0; i < tileCount; i++) - { - const bool isSelected = m_selected == i; - ui::TitleTile &tile = m_titleTiles[i]; - - tile.update(isSelected); - } + m_colorMod.update(); + TitleView::handle_input(); + TitleView::handle_scrolling(); + TitleView::update_tiles(); } void ui::TitleView::render(SDL_Texture *target, bool hasFocus) @@ -93,7 +59,7 @@ void ui::TitleView::render(SDL_Texture *target, bool hasFocus) ui::render_bounding_box(target, m_selectedX - 30, m_selectedY - 30, 188, 188, m_colorMod); } - ui::TitleTile &selectedTile = m_titleTiles.at(m_selected); + ui::TitleTile &selectedTile = m_titleTiles[m_selected]; selectedTile.render(target, m_selectedX, m_selectedY); } @@ -111,7 +77,7 @@ void ui::TitleView::refresh() data::TitleInfo *titleInfo = data::get_title_info_by_id(applicationID); sdl::SharedTexture icon = titleInfo->get_icon(); // I don't like this but w/e. - m_titleTiles.emplace_back(isFavorite, icon); + m_titleTiles.emplace_back(isFavorite, i, icon); } const int tileCount = m_titleTiles.size() - 1; @@ -123,3 +89,48 @@ void ui::TitleView::reset() { for (ui::TitleTile ¤tTile : m_titleTiles) { currentTile.reset(); } } + +void ui::TitleView::handle_input() +{ + const int totalTiles = m_titleTiles.size() - 1; + const bool upPressed = input::button_pressed(HidNpadButton_AnyUp); + const bool downPressed = input::button_pressed(HidNpadButton_AnyDown); + const bool leftPressed = input::button_pressed(HidNpadButton_AnyLeft); + const bool rightPressed = input::button_pressed(HidNpadButton_AnyRight); + const bool lShoulderPressed = input::button_pressed(HidNpadButton_L); + const bool rShoulderPressed = input::button_pressed(HidNpadButton_R); + + if (upPressed) { m_selected -= ICON_ROW_SIZE; } + else if (leftPressed) { --m_selected; } + else if (lShoulderPressed) { m_selected -= ICON_ROW_SIZE * 3; } + else if (downPressed) { m_selected += ICON_ROW_SIZE; } + else if (rightPressed) { ++m_selected; } + else if (rShoulderPressed) { m_selected += ICON_ROW_SIZE * 3; } + + if (m_selected < 0) { m_selected = 0; } + if (m_selected > totalTiles) { m_selected = totalTiles; } +} + +void ui::TitleView::handle_scrolling() +{ + static constexpr double UPPER_THRESHOLD = 32.0f; + static constexpr double LOWER_THRESHOLD = 388.0f; + + const double scaling = config::get_animation_scaling(); + + if (m_selectedY < UPPER_THRESHOLD) + { + const double shiftDown = (UPPER_THRESHOLD - m_selectedY) / scaling; + m_y += std::round(shiftDown); + } + else if (m_selectedY > LOWER_THRESHOLD) + { + const double shiftUp = (LOWER_THRESHOLD - m_selectedY) / scaling; + m_y += std::round(shiftUp); + } +} + +void ui::TitleView::update_tiles() +{ + for (ui::TitleTile &tile : m_titleTiles) { tile.update(m_selected); } +}