diff --git a/Makefile b/Makefile index 473b0d0..d4ef995 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ include $(DEVKITPRO)/libnx/switch_rules #--------------------------------------------------------------------------------- TARGET := JKSV BUILD := build -SOURCES := source source/appstates source/ui source/data source/system source/fs source/curl source/remote +SOURCES := source source/appstates source/ui source/data source/system source/fs source/curl source/remote source/tasks DATA := data INCLUDES := include ./Libraries/FsLib/Switch/FsLib/include ./Libraries/SDLLib/SDL/include EXEFS_SRC := exefs_src diff --git a/include/JKSV.hpp b/include/JKSV.hpp index c916323..b04adc7 100644 --- a/include/JKSV.hpp +++ b/include/JKSV.hpp @@ -45,6 +45,9 @@ class JKSV /// @brief Initializes the services JKSV uses. bool initialize_services(); + /// @brief Initializes SDL and loads the header icon. + bool initialize_sdl(); + // Creates the needed directories on SD. bool create_directories(); diff --git a/include/appstates/BackupMenuState.hpp b/include/appstates/BackupMenuState.hpp index 59d15f6..8f529d2 100644 --- a/include/appstates/BackupMenuState.hpp +++ b/include/appstates/BackupMenuState.hpp @@ -104,6 +104,24 @@ class BackupMenuState final : public BaseState /// @brief Inner render target so the menu only renders to a certain area. static inline sdl::SharedTexture sm_menuRenderTarget{}; + /// @brief Initializes the static members all instances share if they haven't been already. + void initialize_static_members(); + + /// @brief Checks for and tries to create the target directory if it hasn't been already. + void ensure_target_directory(); + + /// @brief Initializes the struct passed to tasks. + void initialize_task_data(); + + /// @brief Init's the string at the top of the backupmenu. + void initialize_info_string(); + + /// @brief Checks to see if the save data is empty. + void save_data_check(); + + /// @brief Ensures the remote storage is initalized and pointing to the right place. + void initialize_remote_storage(); + /// @brief This is the function called when New Backup is selected. void name_and_create_backup(); @@ -122,21 +140,6 @@ class BackupMenuState final : public BaseState /// @brief Just creates the pop-up that says Save is empty or w/e. void pop_save_empty(); - /// @brief Initializes the static members all instances share if they haven't been already. - void initialize_static_members(); - - /// @brief Checks for and tries to create the target directory if it hasn't been already. - void ensure_target_directory(); - - /// @brief Initializes the struct passed to tasks. - void initialize_task_data(); - - /// @brief Init's the string at the top of the backupmenu. - void initialize_info_string(); - - /// @brief Checks to see if the save data is empty. - void save_data_check(); - inline bool is_system_save_data() { return m_saveType == FsSaveDataType_System || m_saveType == FsSaveDataType_SystemBcat; diff --git a/include/appstates/FadeInState.hpp b/include/appstates/FadeInState.hpp new file mode 100644 index 0000000..c9be99d --- /dev/null +++ b/include/appstates/FadeInState.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "appstates/BaseState.hpp" +#include "system/Timer.hpp" + +#include + +class FadeInState final : public BaseState +{ + public: + /// @brief Creates a new fade in state. + /// @param nextState The next state to push after the the fade is finished. + FadeInState(std::shared_ptr nextState); + + ~FadeInState() {}; + + /// @brief Update override. + void update() override; + + /// @brief Render override. + void render() override; + + private: + /// @brief Alpha value. + uint8_t m_alpha = 0xFF; + + sys::Timer m_fadeTimer{}; + + /// @brief Pointer to the next state to push. + std::shared_ptr m_nextState{}; +}; diff --git a/include/appstates/TitleOptionState.hpp b/include/appstates/TitleOptionState.hpp index 26613cd..3bc097d 100644 --- a/include/appstates/TitleOptionState.hpp +++ b/include/appstates/TitleOptionState.hpp @@ -30,21 +30,17 @@ class TitleOptionState final : public BaseState /// @brief Signals to the main thread that a view refresh is required on the next update() call. void refresh_required(); - /// @brief This is the struct used to pass data to the thread functions. - typedef struct + // clang-format off + struct DataStruct { - /// @brief Pointer to the target user. - data::User *m_user{}; + data::User *user{}; + data::TitleInfo *titleInfo{}; + TitleOptionState *spawningState{}; + TitleSelectCommon *titleSelect{}; + }; + // clang-format on - /// @brief The target title's data. - data::TitleInfo *m_titleInfo{}; - - /// @brief Allows tasks to signal deactivation. - TitleOptionState *m_spawningState{}; - - /// @brief The target title select. This is used for updating it. - TitleSelectCommon *m_titleSelect{}; - } DataStruct; + using TaskData = std::shared_ptr; private: /// @brief This is just in case the option should only apply to the current user. diff --git a/include/appstates/UserOptionState.hpp b/include/appstates/UserOptionState.hpp index a2051bc..0763a64 100644 --- a/include/appstates/UserOptionState.hpp +++ b/include/appstates/UserOptionState.hpp @@ -29,15 +29,13 @@ class UserOptionState final : public BaseState /// @note Like this to prevent threading headaches. void data_and_view_refresh_required(); - /// @brief Struct used for passing data to functions/tasks. - typedef struct + // clang-format off + struct DataStruct { - /// @brief Pointer to the target user. - data::User *m_user{}; - - /// @brief Pointer to >this spawning state. - UserOptionState *m_spawningState{}; - } DataStruct; + data::User *user{}; + UserOptionState *spawningState{}; + }; + // clang-format on private: /// @brief Pointer to the target user. diff --git a/include/curl/UploadStruct.hpp b/include/curl/UploadStruct.hpp new file mode 100644 index 0000000..e75bc1b --- /dev/null +++ b/include/curl/UploadStruct.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "fslib.hpp" +#include "system/ProgressTask.hpp" + +namespace curl +{ + // clang-format off + struct UploadStruct + { + fslib::File *source{}; + sys::ProgressTask *task{}; + }; + // clang-format on +} diff --git a/include/curl/curl.hpp b/include/curl/curl.hpp index 14cea02..bfe6e01 100644 --- a/include/curl/curl.hpp +++ b/include/curl/curl.hpp @@ -1,5 +1,7 @@ #pragma once +#include "curl/UploadStruct.hpp" #include "fslib.hpp" + #include #include #include @@ -42,17 +44,11 @@ namespace curl /// @brief Inline function that returns a self cleaning curl handle. /// @return Curl handle. - static inline curl::Handle new_handle() - { - return curl::Handle(curl_easy_init(), curl_easy_cleanup); - } + static inline curl::Handle new_handle() { return curl::Handle(curl_easy_init(), curl_easy_cleanup); } /// @brief Inline function that returns a nullptr'd self cleaning curl_list. /// @return Self cleaning curl_slist. - static inline curl::HeaderList new_header_list() - { - return curl::HeaderList(nullptr, curl_slist_free_all); - } + static inline curl::HeaderList new_header_list() { return curl::HeaderList(nullptr, curl_slist_free_all); } /// @brief Inline wrapper function for curl_easy_reset. /// @param curl curl::Handle to reset. @@ -77,7 +73,7 @@ namespace curl /// @param count Element count. /// @param target Target file to read from. /// @return Number of bytes read so curl thinks everything went OK. - size_t read_data_from_file(char *buffer, size_t size, size_t count, fslib::File *target); + size_t read_data_from_file(char *buffer, size_t size, size_t count, curl::UploadStruct *upload); /// @brief Curl callback function that writes incoming headers to a vector/array. /// @param buffer Incoming buffer from curl. diff --git a/include/fs/MiniUnzip.hpp b/include/fs/MiniUnzip.hpp index e0f8985..f2fe873 100644 --- a/include/fs/MiniUnzip.hpp +++ b/include/fs/MiniUnzip.hpp @@ -29,9 +29,21 @@ namespace fs /// @brief Attempts to go to the next file. Returns false at the end. bool next_file(); + /// @brief Closes the currently open file. + bool close_current_file(); + + /// @brief Attempts to locate a file with filename in the ZIP. + bool locate_file(std::string_view filename); + + /// @brief Resets to the beginning file. + bool reset(); + /// @brief Reads from the currently open file to the buffer passed. ssize_t read(void *buffer, size_t bufferSize); + /// @brief Returns the name of the current file. + const char *get_filename(); + /// @brief Returns the compressed size of the currently open file. uint64_t get_compressed_size() const; diff --git a/include/fs/SaveMetaData.hpp b/include/fs/SaveMetaData.hpp index 8121f18..89ff579 100644 --- a/include/fs/SaveMetaData.hpp +++ b/include/fs/SaveMetaData.hpp @@ -1,5 +1,6 @@ #pragma once #include "data/TitleInfo.hpp" + #include #include @@ -11,44 +12,27 @@ namespace fs /// @brief This is the filename used for the save data meta info. static constexpr std::string_view NAME_SAVE_META = ".nx_save_meta.bin"; - /// @brief Save data meta data struct. - typedef struct __attribute__((packed)) + // clang-format off + struct SaveMetaData { - /// @brief Meta file magic. - uint32_t m_magic; - /// @brief Meta revision. - uint8_t m_revision; - /// @brief Application ID of the game. - uint64_t m_applicationID; - /// @brief User account ID. - AccountUid m_accountID; - /// @brief System save data ID. - uint64_t m_systemSaveID; - /// @brief Save data type. - uint8_t m_saveDataType; - /// @brief Save data rank - uint8_t m_saveDataRank; - /// @brief Save data index. This only really used for cache saves. - uint16_t m_saveDataIndex; - // The rest of the attribute struct is useless, empty padding that is always 0? - /// @brief Save data owner ID. - uint64_t m_ownerID; - /// @brief Just says timestamp. Not sure what time stamp. - uint64_t m_timestamp; - /// @brief Save Data flags. - uint32_t m_flags; - /// @brief Size of the save data. - int64_t m_saveDataSize; - /// @brief Save data's journal size. - int64_t m_journalSize; - /// @brief Commit ID. - uint64_t m_commitID; - // The rest of the struct is useless garbage padding. - } SaveMetaData; + uint32_t magic{}; + uint8_t revision{}; + uint64_t applicationID{}; + AccountUid accountID{}; + uint64_t systemSaveID{}; + uint8_t saveDataType{}; + uint8_t saveDataRank{}; + uint16_t saveDataIndex{}; + uint64_t ownerID{}; + uint64_t timestamp{}; + uint32_t flags{}; + int64_t saveDataSize{}; + int64_t journalSize{}; + uint64_t commitID{}; + } __attribute__((packed)); + // clang-format on - /// @brief Didn't feel like a whole new file just for this. Fills an fs::SaveMetaData struct using the passed TitleInfo pointer. - /// @param info Pointer to FsSaveDataInfo struct to use to fill out the meta struct. - /// @param meta Struct to fill. + /// @brief Didn't feel like a whole new file just for this. Fills an fs::SaveMetaData struct. bool fill_save_meta_data(const FsSaveDataInfo *saveInfo, SaveMetaData &meta); /// @brief Processes the save meta data and applies it to the passed saveInfo pointer. diff --git a/include/fs/fs.hpp b/include/fs/fs.hpp index 0d660ed..ffda502 100644 --- a/include/fs/fs.hpp +++ b/include/fs/fs.hpp @@ -1,4 +1,6 @@ #pragma once +#include "fs/MiniUnzip.hpp" +#include "fs/MiniZip.hpp" #include "fs/SaveMetaData.hpp" #include "fs/directory_functions.hpp" #include "fs/io.hpp" diff --git a/include/fs/io.hpp b/include/fs/io.hpp index 293c7b1..08cddaf 100644 --- a/include/fs/io.hpp +++ b/include/fs/io.hpp @@ -1,6 +1,7 @@ #pragma once #include "fslib.hpp" #include "system/ProgressTask.hpp" + #include namespace fs @@ -13,9 +14,9 @@ namespace fs /// @param Task Optional. Progress tracking task to display progress of operation if needed. void copy_file(const fslib::Path &source, const fslib::Path &destination, - uint64_t journalSize = 0, - std::string_view commitDevice = {}, - sys::ProgressTask *Task = nullptr); + sys::ProgressTask *Task = nullptr, + uint64_t journalSize = 0, + std::string_view commitDevice = {}); /// @brief Recursively copies source to destination. /// @param source Source path. @@ -25,7 +26,7 @@ namespace fs /// @param Task Option. Progress tracking task to be passed to copyFile to show progress of operation. void copy_directory(const fslib::Path &source, const fslib::Path &destination, - uint64_t journalSize = 0, - std::string_view commitDevice = {}, - sys::ProgressTask *Task = nullptr); + sys::ProgressTask *Task = nullptr, + uint64_t journalSize = 0, + std::string_view commitDevice = {}); } // namespace fs diff --git a/include/fs/zip.hpp b/include/fs/zip.hpp index 70014c1..83cbd07 100644 --- a/include/fs/zip.hpp +++ b/include/fs/zip.hpp @@ -1,49 +1,26 @@ #pragma once // Major to do: Stop using minizip and finish the ZipFile class. +#include "fs/MiniUnzip.hpp" +#include "fs/MiniZip.hpp" #include "fslib.hpp" #include "system/ProgressTask.hpp" -#include -#include + #include namespace fs { /// @brief Copies source to destination. - /// @param source Source file to copy from. - /// @param destination zipFile to write to. - /// @param Task Optional. Task to pass to show progress. - void copy_directory_to_zip(const fslib::Path &source, zipFile destination, sys::ProgressTask *Task = nullptr); + /// @note Task is optional. + void copy_directory_to_zip(const fslib::Path &source, fs::MiniZip &dest, sys::ProgressTask *Task = nullptr); /// @brief Unzips source to destination. - /// @param source Source zip file to read from. - /// @param destination Destination path to write to. - /// @param journalSize Size of journal for committing data. This is used exclusively for save data. - /// @param commitDevice Device to commit data to. - /// @param task Optional. Task to update to show progress. - void copy_zip_to_directory(unzFile source, - const fslib::Path &destination, + /// @note Task is optional. + void copy_zip_to_directory(fs::MiniUnzip &source, + const fslib::Path &dest, uint64_t journalSize, std::string_view commitDevice, sys::ProgressTask *Task = nullptr); - /// @brief Gets a filled zip_fileinfo struct. - /// @param info Reference to the info struct to fill. - void create_zip_fileinfo(zip_fileinfo &info); - - /// @brief Returns whether or not zip has files inside. - /// @param zipPath Path to zip to check. - /// @return True if at least one file is found. False if none. + /// @brief Returns whether or not zip has files inside besides the save meta. bool zip_has_contents(const fslib::Path &zipPath); - - /// @brief Attempts to locate a file in a zip file. If it's found, the current file is set to it. - /// @param zip Zip file to search. - /// @param name - /// @return True if the file is found. False if it's not. - bool locate_file_in_zip(unzFile zip, std::string_view name); - - /// @brief Gets the total uncompressed size of the files inside the zip file. - /// @param zip Zip file to get the total size of. - /// @return Total size of the zip file passed. - /// @note unzFile passed is set to the first file afterwards. - uint64_t get_zip_total_size(unzFile zip); } // namespace fs diff --git a/include/remote/GoogleDrive.hpp b/include/remote/GoogleDrive.hpp index d1490a0..3b02823 100644 --- a/include/remote/GoogleDrive.hpp +++ b/include/remote/GoogleDrive.hpp @@ -1,6 +1,7 @@ #pragma once #include "JSON.hpp" #include "remote/Storage.hpp" + #include namespace remote @@ -17,12 +18,12 @@ namespace remote /// @brief Uploads the file from source. File name is used to name the file. /// @param source Path to upload the file from. - bool upload_file(const fslib::Path &source) override; + bool upload_file(const fslib::Path &source, sys::ProgressTask *task = nullptr) override; /// @brief Patches or updates the file on Google Drive. /// @param file Pointer to the item containing the data needed to update the file. /// @param source Source path to update from. - bool patch_file(remote::Item *file, const fslib::Path &source) override; + bool patch_file(remote::Item *file, const fslib::Path &source, sys::ProgressTask *task = nullptr) override; /// @brief Downloads a file from Google Drive. /// @param file Pointer to the item containing data to download the file. @@ -50,22 +51,22 @@ namespace remote private: /// @brief Google client ID. - std::string m_clientId; + std::string m_clientId{}; /// @brief Google client secret. - std::string m_clientSecret; + std::string m_clientSecret{}; /// @brief Authentication token. - std::string m_token; + std::string m_token{}; /// @brief Token used for refreshing token when it expires. - std::string m_refreshToken; + std::string m_refreshToken{}; /// @brief This is to save the authentication header string instead of recreating it over and over. - std::string m_authHeader; + std::string m_authHeader{}; /// @brief This is the calculate time when the auth token expires. - std::time_t m_tokenExpires; + std::time_t m_tokenExpires{}; /// @brief Uses V2 of Drive's API to get the root directory ID from Google. bool get_root_id(); diff --git a/include/remote/Storage.hpp b/include/remote/Storage.hpp index 45c953e..926ad86 100644 --- a/include/remote/Storage.hpp +++ b/include/remote/Storage.hpp @@ -2,6 +2,8 @@ #include "curl/curl.hpp" #include "fslib.hpp" #include "remote/Item.hpp" +#include "system/ProgressTask.hpp" + #include #include #include @@ -17,8 +19,8 @@ namespace remote /// @brief This makes writing some stuff for these classes way easier. using List = std::vector; - /// @brief This just allocates the curl::Handle. - Storage(); + /// @brief This just allocates the curl::Handle. Never mind. + Storage(std::string_view prefix, bool supportsUtf8 = false); /// @brief Returns whether or not the Storage type was successfully. initialized. bool is_initialized() const; @@ -33,11 +35,11 @@ namespace remote /// @brief This allows the root to be set to something other than what it originally was at construction. /// @param root Item to be used as the new root. - void set_root_directory(remote::Item *root); + void set_root_directory(const remote::Item *root); /// @brief Changes the current parent directory. /// @param Item Item to use as the current parent directory. - void change_directory(remote::Item *item); + void change_directory(const remote::Item *item); /// @brief Creates a directory in the current parent directory. /// @param name Name of the directory to create. @@ -59,12 +61,12 @@ namespace remote /// @brief Uploads a file from the SD card to the remote. /// @param source Path to the file to upload. - virtual bool upload_file(const fslib::Path &source) = 0; + virtual bool upload_file(const fslib::Path &source, sys::ProgressTask *task = nullptr) = 0; /// @brief Patches or updates a file on the remote. /// @param item Item to be updated. /// @param source Path to the file to update with. - virtual bool patch_file(remote::Item *file, const fslib::Path &source) = 0; + virtual bool patch_file(remote::Item *file, const fslib::Path &source, sys::ProgressTask *task = nullptr) = 0; /// @brief Downloads a file from the remote. /// @param item Item to download. @@ -94,26 +96,26 @@ namespace remote /// @brief This is the size used for uploads. static constexpr size_t SIZE_UPLOAD_BUFFER = 0x10000; - /// @brief This allows JKSV to know whether or not the storage type supports UTF-8. - bool m_utf8Paths = false; - - /// @brief This stores whether or not the instance was initialized successfully. - bool m_isInitialized = false; - - /// @brief This is the root directory of the remote storage. - std::string m_root; - - /// @brief This stores the current parent. - std::string m_parent; - /// @brief Curl handle. curl::Handle m_curl; - /// @brief This is the main remote listing. - Storage::List m_list; + /// @brief This allows JKSV to know whether or not the storage type supports UTF-8. + bool m_utf8Paths{}; /// @brief This is the prefix used for menus. - std::string m_prefix; + std::string m_prefix{}; + + /// @brief This stores whether or not the instance was initialized successfully. + bool m_isInitialized{}; + + /// @brief This is the root directory of the remote storage. + std::string m_root{}; + + /// @brief This stores the current parent. + std::string m_parent{}; + + /// @brief This is the main remote listing. + Storage::List m_list{}; /// @brief Searches the list for a directory matching name and the current parent. /// @param name Name to search for. diff --git a/include/remote/WebDav.hpp b/include/remote/WebDav.hpp index 5930493..58bb6ab 100644 --- a/include/remote/WebDav.hpp +++ b/include/remote/WebDav.hpp @@ -1,6 +1,7 @@ #pragma once #include "remote/Storage.hpp" #include "remote/URL.hpp" + #include namespace remote @@ -17,12 +18,12 @@ namespace remote /// @brief Uploads a file to the webdav server. File name is retrieved from the path. /// @param source Local path of the file to upload. - bool upload_file(const fslib::Path &source) override; + bool upload_file(const fslib::Path &source, sys::ProgressTask *task = nullptr) override; /// @brief Patches or updates a file on the WebDav server. /// @param file Pointer to the file to update. /// @param source Path of the source file to update with. - bool patch_file(remote::Item *file, const fslib::Path &source) override; + bool patch_file(remote::Item *file, const fslib::Path &source, sys::ProgressTask *task = nullptr) override; /// @brief Downloads the passed file from the WebDav server. /// @param file Pointer to the file to download. @@ -35,13 +36,13 @@ namespace remote private: /// @brief Origin or server address. - std::string m_origin; + std::string m_origin{}; /// @brief Username for curl requests. - std::string m_username; + std::string m_username{}; /// @brief Password for curl requests. - std::string m_password; + std::string m_password{}; /// @brief Appends the username and password to a WebDav curl request. void append_credentials(); diff --git a/include/remote/remote.hpp b/include/remote/remote.hpp index a290a19..a78074d 100644 --- a/include/remote/remote.hpp +++ b/include/remote/remote.hpp @@ -1,12 +1,13 @@ #pragma once #include "remote/Storage.hpp" + #include namespace remote { // Both of these are needed in two different places. 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"; + static constexpr std::string_view PATH_WEBDAV_CONFIG = "sdmc:/config/JKSV/webdav.json"; /// @brief Initializes the Storage instance to Google Drive. void initialize_google_drive(); diff --git a/include/strings.hpp b/include/strings.hpp index 2d8a741..a332cef 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -23,6 +23,7 @@ namespace strings static constexpr std::string_view GOOGLE_DRIVE = "GoogleDriveStrings"; static constexpr std::string_view HOLDING_STRINGS = "HoldingStrings"; static constexpr std::string_view IO_STATUSES = "IOStatuses"; + static constexpr std::string_view IO_POPS = "IOPops"; static constexpr std::string_view KEYBOARD = "KeyboardStrings"; static constexpr std::string_view ON_OFF = "OnOff"; static constexpr std::string_view SAVECREATE_POPS = "SaveCreatePops"; diff --git a/include/system/Task.hpp b/include/system/Task.hpp index 565b1cd..f7634a5 100644 --- a/include/system/Task.hpp +++ b/include/system/Task.hpp @@ -12,7 +12,8 @@ namespace sys /// @brief Constructs a new task. /// @param function Function for task to run. /// @param args Arguments forwarded to thread. - /// @note Functions passed to this class must follow the following signature: void function(sys::Task *, ) + /// @note Functions passed to this class must follow the following signature: void function(sys::Task *, + /// ) template Task(void (*function)(sys::Task *, Args...), Args... args) { @@ -42,9 +43,7 @@ namespace sys void finished(); /// @brief Sets the task/threads current status string. Thread safe. - /// @param format Format of string. - /// @param args Arguments for string. - void set_status(const char *format, ...); + void set_status(std::string_view status); /// @brief Returns the status string. Thread safe. /// @return Copy of the status string. diff --git a/include/system/defines.hpp b/include/system/defines.hpp new file mode 100644 index 0000000..501af09 --- /dev/null +++ b/include/system/defines.hpp @@ -0,0 +1,3 @@ +#pragma once + +using byte = unsigned char; diff --git a/include/tasks/backup.hpp b/include/tasks/backup.hpp index 6573a83..691e7ab 100644 --- a/include/tasks/backup.hpp +++ b/include/tasks/backup.hpp @@ -12,7 +12,8 @@ namespace tasks data::User *user, data::TitleInfo *titleInfo, fslib::Path target, - BackupMenuState *spawningState); + BackupMenuState *spawningState, + bool killTask = true); /// @brief Overwrites a pre-existing backup. void overwrite_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData); diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index 04d2745..4c0aab2 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -14,11 +14,13 @@ "3: Error opening ZIP file for reading!", "4: Error occurred deleting backup!", "5: Error creating backup!", - "6: Writing to system is disabled!" + "6: Writing to system is disabled!", + "7: Unable to open zip for reading!" ], "BackupMenuStatus": [ "0: Processing save data meta file...", - "1: Uploading #%s# to remote storage..." + "1: Uploading #%s# to remote storage...", + "2: Updating #%s# on remote storage..." ], "ControlGuides": [ "0: [A] Select [Y] Dump All Saves [X] User Options", @@ -37,7 +39,7 @@ ], "ExtrasPops": [ "0: Data reinitialized!", - "1: Data reinitialization failed" + "1: Data reinitialization failed!" ], "GeneralPops": [ "0: Unable to exit JKSV while tasks are running!" @@ -58,6 +60,9 @@ "2: Decompressing #%s# from ZIP...", "3: Deleting #%s#..." ], + "IOPops": [ + "0: Error committing data to device!" + ], "KeyboardStrings": [ "0: Enter a new backup name.", "1: Enter cache index.", @@ -159,7 +164,9 @@ "6: This option is unavailable for system saves!", "7: Could not sanitize path for use!", "8: Output folder set to #%s#.", - "9: Error setting new output path!" + "9: Error setting new output path!", + "10: Save data successfully extended!", + "11: Save data extension failed!" ], "TitleOptionStatus": [ "0: Deleting all backups for #%s#.", diff --git a/source/JKSV.cpp b/source/JKSV.cpp index 6a609f7..becddac 100644 --- a/source/JKSV.cpp +++ b/source/JKSV.cpp @@ -1,6 +1,7 @@ #include "JKSV.hpp" #include "StateManager.hpp" +#include "appstates/FadeInState.hpp" #include "appstates/MainMenuState.hpp" #include "colors.hpp" #include "config.hpp" @@ -45,19 +46,11 @@ static bool initialize_service(Result (*function)(Args...), const char *serviceN // This can't really have an initializer list since it sets everything up. JKSV::JKSV() { - // Start with this. appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); - - ABORT_ON_FAILURE(JKSV::initialize_filesystem()); - ABORT_ON_FAILURE(JKSV::initialize_services()); + ABORT_ON_FAILURE(JKSV::initialize_filesystem()); logger::initialize(); - - // SDL - ABORT_ON_FAILURE(sdl::initialize("JKSV", 1280, 720)); - ABORT_ON_FAILURE(sdl::text::initialize()); - m_headerIcon = sdl::TextureManager::create_load_texture("HeaderIcon", "romfs:/Textures/HeaderIcon.png"); - JKSV::add_color_chars(); + ABORT_ON_FAILURE(JKSV::initialize_sdl()); ABORT_ON_FAILURE(curl::initialize()); ABORT_ON_FAILURE(strings::initialize()); // This is fatal now. @@ -172,6 +165,15 @@ bool JKSV::initialize_services() return serviceInit; } +bool JKSV::initialize_sdl() +{ + bool sdlInit = sdl::initialize("JKSV", 1280, 720); + sdlInit = sdlInit && sdl::text::initialize(); + m_headerIcon = sdl::TextureManager::create_load_texture("headerIcon", "romfs:/Textures/HeaderIcon.png"); + JKSV::add_color_chars(); + return sdlInit && m_headerIcon; +} + bool JKSV::create_directories() { // Working directory creation. diff --git a/source/StateManager.cpp b/source/StateManager.cpp index e2424fd..8fdb940 100644 --- a/source/StateManager.cpp +++ b/source/StateManager.cpp @@ -26,10 +26,7 @@ void StateManager::update() } // Check if the back has focus. It should always have it. - if (!instance.sm_stateVector.back()->has_focus()) - { - instance.sm_stateVector.back()->give_focus(); - } + if (!instance.sm_stateVector.back()->has_focus()) { instance.sm_stateVector.back()->give_focus(); } // Only call update on the back. instance.sm_stateVector.back()->update(); @@ -41,10 +38,7 @@ void StateManager::render() StateManager &instance = StateManager::get_instance(); // Loop and render all states. - for (std::shared_ptr &appState : instance.sm_stateVector) - { - appState->render(); - } + for (std::shared_ptr &appState : instance.sm_stateVector) { appState->render(); } } bool StateManager::back_is_closable() @@ -53,10 +47,7 @@ bool StateManager::back_is_closable() StateManager &instance = StateManager::get_instance(); // Not too sure how to handle this yet. - if (instance.sm_stateVector.empty()) - { - return false; - } + if (instance.sm_stateVector.empty()) { return false; } // Just return this. return instance.sm_stateVector.back()->is_closable(); @@ -68,10 +59,7 @@ void StateManager::push_state(std::shared_ptr newState) StateManager &instance = StateManager::get_instance(); // Take focus from the current back() - if (!instance.sm_stateVector.empty()) - { - instance.sm_stateVector.back()->take_focus(); - } + if (!instance.sm_stateVector.empty()) { instance.sm_stateVector.back()->take_focus(); } // Give the incoming state focus and then push it. newState->give_focus(); diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index 81361a4..21ef020 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -5,16 +5,17 @@ #include "appstates/ProgressState.hpp" #include "colors.hpp" #include "config.hpp" +#include "error.hpp" #include "fs/fs.hpp" #include "fslib.hpp" #include "input.hpp" #include "keyboard.hpp" -#include "logger.hpp" #include "remote/remote.hpp" #include "sdl.hpp" #include "strings.hpp" #include "stringutil.hpp" #include "system/system.hpp" +#include "tasks/backup.hpp" #include "ui/PopMessageManager.hpp" #include "ui/TextScroll.hpp" @@ -26,29 +27,13 @@ namespace constexpr size_t SIZE_NAME_LENGTH = 0x80; /// @brief This is just so there isn't random .zip comparisons everywhere. - const char *STRING_ZIP_EXTENSION = ".zip"; + constexpr const char *STRING_ZIP_EXT = ".zip"; // These make some things cleaner and easier to type. using TaskConfirm = ConfirmState; using ProgressConfirm = ConfirmState; } // namespace -// Declarations here. Definitions after class. -// Create new backup in targetPath -static void create_new_backup(sys::ProgressTask *task, - data::User *user, - data::TitleInfo *titleInfo, - fslib::Path targetPath, - BackupMenuState *spawningState); -// Overwrites and existing backup. -static void overwrite_backup(sys::ProgressTask *task, std::shared_ptr dataStruct); -// Restores a backup and requires confirmation to do so. Takes a shared_ptr to a TargetStruct. -static void restore_backup(sys::ProgressTask *task, std::shared_ptr dataStruct); -// Deletes a backup and requires confirmation to do so. Takes a shared_ptr to a TargetStruct. -static void delete_backup(sys::Task *task, std::shared_ptr dataStruct); -// Uploads a backup to the remote server. -static void upload_backup(sys::ProgressTask *task, std::shared_ptr dataStruct); - BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo) : m_user(user) , m_titleInfo(titleInfo) @@ -60,6 +45,7 @@ BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo) { BackupMenuState::initialize_static_members(); BackupMenuState::ensure_target_directory(); + BackupMenuState::initialize_remote_storage(); BackupMenuState::initialize_task_data(); BackupMenuState::initialize_info_string(); BackupMenuState::save_data_check(); @@ -130,6 +116,7 @@ void BackupMenuState::render() void BackupMenuState::refresh() { + remote::Storage *remote = remote::get_remote_storage(); m_directoryListing.open(m_directoryPath); if (!m_directoryListing) { return; } @@ -138,7 +125,23 @@ void BackupMenuState::refresh() sm_backupMenu->add_option(strings::get_by_name(strings::names::BACKUPMENU_MENU, 0)); m_menuEntries.push_back({MenuEntryType::Null, 0}); - for (int64_t i = 0; i < m_directoryListing.get_count(); i++) + + if (remote && remote->is_initialized()) + { + const std::string_view prefix = remote->get_prefix(); + remote::Storage::DirectoryListing listing = remote->get_directory_listing(); + int index{}; + for (const remote::Item *item : listing) + { + const std::string_view name = item->get_name(); + const std::string option = stringutil::get_formatted_string("%s %s", prefix.data(), name.data()); + sm_backupMenu->add_option(option); + m_menuEntries.push_back({MenuEntryType::Remote, index++}); + } + } + + const int64_t listingCount = m_directoryListing.get_count(); + for (int64_t i = 0; i < listingCount; i++) { sm_backupMenu->add_option(m_directoryListing[i]); m_menuEntries.push_back({MenuEntryType::Local, static_cast(i)}); @@ -150,103 +153,6 @@ void BackupMenuState::save_data_written() if (!m_saveHasData) { m_saveHasData = true; } } -void BackupMenuState::name_and_create_backup() -{ - const bool autoName = config::get_by_key(config::keys::AUTO_NAME_BACKUPS); - const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP); - const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); - 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. - char name[SIZE_NAME_LENGTH + 1] = {0}; - - std::snprintf(name, SIZE_NAME_LENGTH, "%s - %s", m_user->get_path_safe_nickname(), stringutil::get_date_string().c_str()); - - const bool named = autoNamed || keyboard::get_input(SwkbdType_QWERTY, name, keyboardHeader, name, SIZE_NAME_LENGTH); - if (!named) { return; } - - fslib::Path target{m_directoryPath / name}; - const bool hasZipExt = std::strstr(target.full_path(), ".zip"); // This might not be the best check. - if ((exportZip || autoUpload) && !hasZipExt) { target += ".zip"; } - else if (!exportZip && !autoUpload && !hasZipExt) - { - const bool targetExists = fslib::directory_exists(target); - const bool targetCreated = !targetExists && fslib::create_directory(target); - } - auto newBackupTask = std::make_shared(create_new_backup, m_user, m_titleInfo, target, this); - StateManager::push_state(newBackupTask); -} - -void BackupMenuState::confirm_overwrite() -{ - const int selected = sm_backupMenu->get_selected(); - const MenuEntry &entry = m_menuEntries.at(selected); - const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); - const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); - m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; - - const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); - auto confirm = std::make_shared(query, holdRequired, overwrite_backup, m_dataStruct); - 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_MESSAGE_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 bool isSystem = BackupMenuState::is_system_save_data(); - const bool allowSystem = config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM); - const bool isValidRestore = !isSystem || allowSystem; - if (!isValidRestore) - { - ui::PopMessageManager::push_message(popTicks, popSysNotAllowed); - return; - } - - 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); - if (!backupIsGood) - { - ui::PopMessageManager::push_message(popTicks, popBackupEmpty); - return; - } - - m_dataStruct->path = target; - const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); - auto confirm = std::make_shared(query, holdRequired, restore_backup, m_dataStruct); - StateManager::push_state(confirm); -} - -void BackupMenuState::confirm_delete() -{ - const int selected = sm_backupMenu->get_selected(); - const MenuEntry &entry = m_menuEntries.at(selected); - const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_DELETION); - const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 2); - m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; - - const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); - auto confirm = std::make_shared(query, holdRequired, delete_backup, m_dataStruct); - - StateManager::push_state(confirm); -} - -void BackupMenuState::upload_backup() {} - -void BackupMenuState::pop_save_empty() -{ - const int ticks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; - const char *popEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 0); - ui::PopMessageManager::push_message(ticks, popEmpty); -} - void BackupMenuState::initialize_static_members() { constexpr int SDL_TEX_FLAGS = SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET; @@ -287,256 +193,126 @@ void BackupMenuState::save_data_check() m_saveHasData = saveRoot.get_count() > 0; } -// This is the function to create new backups. -static void create_new_backup(sys::ProgressTask *task, - data::User *user, - data::TitleInfo *titleInfo, - fslib::Path targetPath, - BackupMenuState *spawningState) +void BackupMenuState::initialize_remote_storage() { - // SaveMeta - FsSaveDataInfo *saveInfo = user->get_save_info_by_id(titleInfo->get_application_id()); - if (!saveInfo) - { - logger::log("Error retrieving save data information for %016lX.", titleInfo->get_application_id()); - task->finished(); - return; - } - - // I got tired of typing out the cast. - fs::SaveMetaData saveMeta; - bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta); - - // This extension search is lazy and needs to be revised. - if (config::get_by_key(config::keys::EXPORT_TO_ZIP) || std::strstr(targetPath.full_path(), "zip")) - { - zipFile newBackup = zipOpen64(targetPath.full_path(), APPEND_STATUS_CREATE); - if (!newBackup) - { - // To do: Pop up. - logger::log("Error opening zip for backup."); - task->finished(); - return; - } - - if (hasMeta) - { - // Data for save meta. - zip_fileinfo saveMetaInfo; - fs::create_zip_fileinfo(saveMetaInfo); - - // Write meta to zip. - int zipError = zipOpenNewFileInZip64(newBackup, - fs::NAME_SAVE_META.data(), - &saveMetaInfo, - NULL, - 0, - NULL, - 0, - NULL, - Z_DEFLATED, - config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL), - 0); - if (zipError == ZIP_OK) - { - zipWriteInFileInZip(newBackup, &saveMeta, sizeof(fs::SaveMetaData)); - zipCloseFileInZip(newBackup); - } - } - fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, newBackup, task); - zipClose(newBackup, NULL); - } - else - { - { - fslib::Path saveMetaPath = targetPath / fs::NAME_SAVE_META; - fslib::File saveMetaOut(saveMetaPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData)); - if (saveMetaOut && hasMeta) { saveMetaOut.write(&saveMeta, sizeof(fs::SaveMetaData)); } - } - fs::copy_directory(fs::DEFAULT_SAVE_ROOT, targetPath, 0, {}, task); - } - - // Refresh. - spawningState->refresh(); - - task->finished(); -} - -static void overwrite_backup(sys::ProgressTask *task, std::shared_ptr dataStruct) -{ - // I hate typing this stuff over and over. - static const char *STRING_ERROR_PREFIX = "Error overwriting backup: %s"; - - // Might need this later. - FsSaveDataInfo *saveInfo = dataStruct->user->get_save_info_by_id(dataStruct->titleInfo->get_application_id()); - - // Wew this is a fun one to read, but it takes care of everything in one go. - if ((fslib::directory_exists(dataStruct->path) && !fslib::delete_directory_recursively(dataStruct->path)) || - (fslib::file_exists(dataStruct->path) && !fslib::delete_file(dataStruct->path))) - { - logger::log(STRING_ERROR_PREFIX, fslib::error::get_string()); - task->finished(); - return; - } - - // Gonna need a new save meta. - fs::SaveMetaData meta; - bool hasMeta = fs::fill_save_meta_data(saveInfo, meta); - - if (std::strstr(STRING_ZIP_EXTENSION, dataStruct->path.full_path())) - { - zipFile backupZip = zipOpen64(dataStruct->path.full_path(), APPEND_STATUS_CREATE); - if (!backupZip) - { - logger::log("Error overwriting backup: Couldn't create new zip!"); - task->finished(); - return; - } - - // Need the zip info for the meta. - if (hasMeta) - { - zip_fileinfo saveMetaInfo; - fs::create_zip_fileinfo(saveMetaInfo); - - int zipError = zipOpenNewFileInZip64(backupZip, - fs::NAME_SAVE_META.data(), - &saveMetaInfo, - NULL, - 0, - NULL, - 0, - NULL, - Z_DEFLATED, - config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL), - 0); - if (zipError == ZIP_OK) - { - zipWriteInFileInZip(backupZip, &meta, sizeof(fs::SaveMetaData)); - zipCloseFileInZip(backupZip); - } - } - - fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, backupZip, task); - zipClose(backupZip, NULL); - } // I hope this check works for making sure this is a folder - else if (dataStruct->path.get_extension() == nullptr && fslib::create_directory(dataStruct->path)) - { - // Write this quick. - { - fslib::Path metaPath = dataStruct->path / fs::NAME_SAVE_META; - fslib::File metaFile(metaPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData)); - if (metaFile && hasMeta) { metaFile.write(&meta, sizeof(fs::SaveMetaData)); } - } - - fs::copy_directory(fs::DEFAULT_SAVE_ROOT, dataStruct->path, 0, {}, task); - } - task->finished(); -} - -static void restore_backup(sys::ProgressTask *task, std::shared_ptr dataStruct) -{ - // Going to need this later. - FsSaveDataInfo *saveInfo = dataStruct->user->get_save_info_by_id(dataStruct->titleInfo->get_application_id()); - if (!saveInfo) - { - // To do: Log this. - task->finished(); - return; - } - - // Wipe the save root first. Forgot to commit the changes before. Oops. - if (!fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT) || - !fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)) - { - logger::log("Error restoring save: %s", fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::BACKUPMENU_POPS, 2)); - task->finished(); - return; - } - - if (fslib::directory_exists(dataStruct->path)) - { - { - // Process the save meta if it's there. - fs::SaveMetaData meta = {0}; - fslib::Path saveMetaPath = dataStruct->path / fs::NAME_SAVE_META; - fslib::File saveMetaFile(saveMetaPath, FsOpenMode_Read); - if (saveMetaFile && saveMetaFile.read(&meta, sizeof(fs::SaveMetaData)) == sizeof(fs::SaveMetaData)) - { - // Set this so at least the user knows something is going on. Extending saves can take a decent chunk of - // time. - task->set_status(strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0)); - fs::process_save_meta_data(saveInfo, meta); - } - } - - fs::copy_directory(dataStruct->path, fs::DEFAULT_SAVE_ROOT, 0, fs::DEFAULT_SAVE_MOUNT, task); - } - else if (std::strstr(dataStruct->path.full_path(), ".zip") != NULL) - { - unzFile targetZip = unzOpen64(dataStruct->path.full_path()); - if (!targetZip) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::BACKUPMENU_POPS, 3)); - logger::log("Error opening zip for reading."); - task->finished(); - return; - } - - { - // I'm not sure if this is risky or not. Guess we'll find out... - fs::SaveMetaData meta = {0}; - // The locate_file_in_zip should pinpoint the meta file. - if (fs::locate_file_in_zip(targetZip, fs::NAME_SAVE_META) && unzOpenCurrentFile(targetZip) == UNZ_OK && - unzReadCurrentFile(targetZip, &meta, sizeof(fs::SaveMetaData)) == sizeof(fs::SaveMetaData)) - { - task->set_status(strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0)); - fs::process_save_meta_data(saveInfo, meta); - } - } - - fs::copy_zip_to_directory(targetZip, fs::DEFAULT_SAVE_ROOT, 0, fs::DEFAULT_SAVE_MOUNT, task); - unzClose(targetZip); - } - else { fs::copy_file(dataStruct->path, fs::DEFAULT_SAVE_ROOT, 0, fs::DEFAULT_SAVE_MOUNT, task); } - - // Update this just in case. - dataStruct->spawningState->save_data_written(); - - task->finished(); -} - -static void delete_backup(sys::Task *task, std::shared_ptr dataStruct) -{ - if (task) { task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 3), dataStruct->path.full_path()); } - - if (fslib::directory_exists(dataStruct->path) && !fslib::delete_directory_recursively(dataStruct->path)) - { - logger::log("Error deleting folder backup: %s", fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::BACKUPMENU_POPS, 4)); - } - else if (fslib::file_exists(dataStruct->path) && !fslib::delete_file(dataStruct->path)) - { - logger::log("Error deleting backup: %s", fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::BACKUPMENU_POPS, 4)); - } - dataStruct->spawningState->refresh(); - task->finished(); -} - -static void upload_backup(sys::ProgressTask *task, std::shared_ptr dataStruct) -{ - if (task) { task->set_status(strings::get_by_name(strings::names::BACKUPMENU_STATUS, 1), dataStruct->path.get_filename()); } - - // To do: This but flashier. remote::Storage *remote = remote::get_remote_storage(); + if (error::is_null(remote) || !remote->is_initialized()) { return; } - remote->upload_file(dataStruct->path); + const bool supportsUtf8 = remote->supports_utf8(); + const std::string_view remoteTitle = supportsUtf8 ? m_titleInfo->get_title() : m_titleInfo->get_path_safe_title(); + const bool remoteDirExists = remote->directory_exists(remoteTitle); + const bool remoteDirCreated = !remoteDirExists && remote->create_directory(remoteTitle); + if (!remoteDirExists && !remoteDirCreated) { return; } - task->finished(); + const remote::Item *remoteDir = remote->get_directory_by_name(remoteTitle); + if (!remoteDir) { return; } + + remote->change_directory(remoteDir); +} + +void BackupMenuState::name_and_create_backup() +{ + const bool autoName = config::get_by_key(config::keys::AUTO_NAME_BACKUPS); + const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP) || config::get_by_key(config::keys::AUTO_UPLOAD); + 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. + char name[SIZE_NAME_LENGTH + 1] = {0}; + + std::snprintf(name, SIZE_NAME_LENGTH, "%s - %s", m_user->get_path_safe_nickname(), stringutil::get_date_string().c_str()); + + const bool named = autoNamed || keyboard::get_input(SwkbdType_QWERTY, name, keyboardHeader, name, SIZE_NAME_LENGTH); + if (!named) { return; } + + fslib::Path target{m_directoryPath / name}; + const bool hasZipExt = std::strstr(target.full_path(), STRING_ZIP_EXT); // This might not be the best check. + if (exportZip && !hasZipExt) { target += STRING_ZIP_EXT; } + else if (!exportZip && !hasZipExt) + { + const bool targetExists = fslib::directory_exists(target); + const bool targetCreated = !targetExists && fslib::create_directory(target); + } + auto newBackupTask = + std::make_shared(tasks::backup::create_new_backup, m_user, m_titleInfo, target, this, true); + StateManager::push_state(newBackupTask); +} + +void BackupMenuState::confirm_overwrite() +{ + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); + const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); + m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; + + const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); + auto confirm = std::make_shared(query, holdRequired, tasks::backup::overwrite_backup, m_dataStruct); + 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_MESSAGE_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 bool isSystem = BackupMenuState::is_system_save_data(); + const bool allowSystem = config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM); + const bool isValidRestore = !isSystem || allowSystem; + if (!isValidRestore) + { + ui::PopMessageManager::push_message(popTicks, popSysNotAllowed); + return; + } + + 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); + if (!backupIsGood) + { + ui::PopMessageManager::push_message(popTicks, popBackupEmpty); + return; + } + + m_dataStruct->path = target; + const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); + auto confirm = std::make_shared(query, holdRequired, tasks::backup::restore_backup, m_dataStruct); + StateManager::push_state(confirm); +} + +void BackupMenuState::confirm_delete() +{ + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_DELETION); + const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 2); + m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; + + const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); + auto confirm = std::make_shared(query, holdRequired, tasks::backup::delete_backup, m_dataStruct); + + StateManager::push_state(confirm); +} + +void BackupMenuState::upload_backup() +{ + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + if (entry.type != BackupMenuState::MenuEntryType::Local) { return; } + + m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; + + auto upload = std::make_shared(tasks::backup::upload_backup, m_dataStruct); + StateManager::push_state(upload); +} + +void BackupMenuState::pop_save_empty() +{ + const int ticks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + const char *popEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 0); + ui::PopMessageManager::push_message(ticks, popEmpty); } diff --git a/source/appstates/FadeInState.cpp b/source/appstates/FadeInState.cpp new file mode 100644 index 0000000..28c98ba --- /dev/null +++ b/source/appstates/FadeInState.cpp @@ -0,0 +1,28 @@ +#include "appstates/FadeInState.hpp" + +#include "StateManager.hpp" +#include "sdl.hpp" + +FadeInState::FadeInState(std::shared_ptr nextState) + : m_nextState(nextState) +{ + m_fadeTimer.start(1); +} + +void FadeInState::update() +{ + if (m_alpha == 0x00) + { + StateManager::push_state(m_nextState); + BaseState::deactivate(); + } + else if (m_fadeTimer.is_triggered()) { m_alpha -= 15; } +} + +void FadeInState::render() +{ + m_nextState->render(); + const uint32_t rawColor = 0x000000 | m_alpha; + const sdl::Color fadeColor{rawColor}; + sdl::render_rect_fill(nullptr, 0, 0, 1280, 720, fadeColor); +} diff --git a/source/appstates/SaveCreateState.cpp b/source/appstates/SaveCreateState.cpp index dc0ef1a..eeddcc4 100644 --- a/source/appstates/SaveCreateState.cpp +++ b/source/appstates/SaveCreateState.cpp @@ -3,10 +3,12 @@ #include "StateManager.hpp" #include "appstates/TaskState.hpp" #include "data/data.hpp" +#include "error.hpp" #include "fs/fs.hpp" #include "input.hpp" #include "logger.hpp" #include "strings.hpp" +#include "stringutil.hpp" #include "system/Task.hpp" #include "ui/PopMessageManager.hpp" @@ -92,8 +94,14 @@ static void create_save_data(sys::Task *task, data::TitleInfo *titleInfo, SaveCreateState *spawningState) { - // Set status. We'll just borrow the string from the other group. - task->set_status(strings::get_by_name(strings::names::USEROPTION_STATUS, 0), titleInfo->get_title()); + if (error::is_null(task)) { return; } + + const char *statusTemplate = strings::get_by_name(strings::names::USEROPTION_STATUS, 0); + + { + const std::string status = stringutil::get_formatted_string(statusTemplate, titleInfo->get_title()); + task->set_status(status); + } if (fs::create_save_data_for(targetUser, titleInfo)) { diff --git a/source/appstates/SettingsState.cpp b/source/appstates/SettingsState.cpp index ff5cbb4..ae993d5 100644 --- a/source/appstates/SettingsState.cpp +++ b/source/appstates/SettingsState.cpp @@ -175,6 +175,5 @@ const char *SettingsState::get_status_text(uint8_t value) const char *SettingsState::get_sort_type_text(uint8_t value) { if (value > 2) { return nullptr; } - logger::log("return string: %s", m_sortTypes[value]); return m_sortTypes[value]; } diff --git a/source/appstates/TitleOptionState.cpp b/source/appstates/TitleOptionState.cpp index 99f37e5..d73ed54 100644 --- a/source/appstates/TitleOptionState.cpp +++ b/source/appstates/TitleOptionState.cpp @@ -6,6 +6,7 @@ #include "appstates/TitleInfoState.hpp" #include "colors.hpp" #include "config.hpp" +#include "error.hpp" #include "fs/fs.hpp" #include "fslib.hpp" #include "input.hpp" @@ -33,9 +34,6 @@ namespace EXTEND_CONTAINER, EXPORT_SVI }; - - // Error string template thingies. - static const char *ERROR_RESETTING_SAVE = "Error resetting save data: %s"; } // namespace // Declarations. Definitions after class. Some of these are only here to be compatible with confirmations. @@ -73,10 +71,10 @@ TitleOptionState::TitleOptionState(data::User *user, data::TitleInfo *titleInfo, } // Fill this out. - m_dataStruct->m_user = m_user; - m_dataStruct->m_titleInfo = m_titleInfo; - m_dataStruct->m_spawningState = this; - m_dataStruct->m_titleSelect = m_titleSelect; + m_dataStruct->user = m_user; + m_dataStruct->titleInfo = m_titleInfo; + m_dataStruct->spawningState = this; + m_dataStruct->titleSelect = m_titleSelect; } void TitleOptionState::update() @@ -260,20 +258,21 @@ void TitleOptionState::refresh_required() { m_refreshRequired = true; } static void blacklist_title(sys::Task *task, std::shared_ptr dataStruct) { - // Gonna need this a lot. - uint64_t applicationID = dataStruct->m_titleInfo->get_application_id(); + if (error::is_null(task)) { return; } + + data::TitleInfo *titleInfo = dataStruct->titleInfo; + TitleOptionState *spawningState = dataStruct->spawningState; + const uint64_t applicationID = titleInfo->get_application_id(); - // We're not gonna bother with a status for this. It'll flicker, but be barely noticeable. config::add_remove_blacklist(applicationID); - // Now we need to remove it from all of the users. This doesn't just apply to the active one. 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. - dataStruct->m_spawningState->refresh_required(); - dataStruct->m_spawningState->close_on_update(); + spawningState->refresh_required(); + spawningState->close_on_update(); task->finished(); } @@ -320,11 +319,18 @@ static void change_output_path(data::TitleInfo *targetTitle) static void delete_all_backups_for_title(sys::Task *task, std::shared_ptr dataStruct) { - // Get the path. - fslib::Path titlePath = config::get_working_directory() / dataStruct->m_titleInfo->get_path_safe_title(); + if (error::is_null(task)) { return; } - // Set the status. - task->set_status(strings::get_by_name(strings::names::TITLEOPTION_STATUS, 0), dataStruct->m_titleInfo->get_title()); + data::TitleInfo *titleInfo = dataStruct->titleInfo; + + const char *statusTemplate = strings::get_by_name(strings::names::TITLEOPTION_STATUS, 0); + + { + const std::string status = stringutil::get_formatted_string(statusTemplate, titleInfo->get_title()); + task->set_status(status); + } + + fslib::Path titlePath = config::get_working_directory() / titleInfo->get_path_safe_title(); // Just call this and nuke the folder. if (!fslib::delete_directory_recursively(titlePath)) @@ -336,130 +342,112 @@ static void delete_all_backups_for_title(sys::Task *task, std::shared_ptrm_titleInfo->get_title()); + titleInfo->get_title()); } task->finished(); } static void reset_save_data(sys::Task *task, std::shared_ptr dataStruct) { - // To do: Make this not as hard to read. - // Attempt to mount save. - if (!fslib::open_save_data_with_save_info( - fs::DEFAULT_SAVE_MOUNT, - *dataStruct->m_user->get_save_info_by_id(dataStruct->m_titleInfo->get_application_id()))) + 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_MESSAGE_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 bool mountFailed = error::fslib(fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)); + if (mountFailed) { - logger::log(ERROR_RESETTING_SAVE, fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 2)); + ui::PopMessageManager::push_message(popTicks, popFailed); task->finished(); return; } - // Wipe the root. - if (!fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)) - { - fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - logger::log(ERROR_RESETTING_SAVE, fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 2)); - 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); } - // Attempt commit. - if (!fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)) - { - fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - logger::log(ERROR_RESETTING_SAVE, fslib::error::get_string()); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 2)); - task->finished(); - return; - } - - // Should be good to go. fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLEOPTION_POPS, 3)); task->finished(); } static void delete_save_data_from_system(sys::Task *task, std::shared_ptr dataStruct) { - // Set the status in case this takes a little while. - task->set_status(strings::get_by_name(strings::names::TITLEOPTION_STATUS, 2), - dataStruct->m_user->get_nickname(), - dataStruct->m_titleInfo->get_title()); + 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; } - // Grab the save data info pointer. - uint64_t applicationID = dataStruct->m_titleInfo->get_application_id(); - FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(applicationID); - if (saveInfo == nullptr) { - logger::log("Error deleting save data for user. Target save data null?"); + 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(); return; } - if (!fs::delete_save_data(saveInfo)) - { - // Just cleanup, I guess? - task->finished(); - return; - } - - // Erase the info from the user since it should have been deleted. - dataStruct->m_user->erase_save_info_by_id(applicationID); - - // Refresh - dataStruct->m_titleSelect->refresh(); - - // Signal to close, because this save is no long valid. - dataStruct->m_spawningState->close_on_update(); - - // Done? + 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) { - // Grab this stuff to make stuff easier to read and type. - data::TitleInfo *titleInfo = dataStruct->m_titleInfo; - FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(titleInfo->get_application_id()); + 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_MESSAGE_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; } - if (!saveInfo) { - logger::log("Error retrieving save data info to extend!"); - task->finished(); - return; + 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); } - // Set the status. - task->set_status(strings::get_by_name(strings::names::TITLEOPTION_STATUS, 3), - dataStruct->m_user->get_nickname(), - dataStruct->m_titleInfo->get_title()); - - // This is the header string. - std::string_view keyboardString = strings::get_by_name(strings::names::KEYBOARD, 8); - - // Get how much to extend. - char buffer[5] = {0}; - // No default. Maybe change this later? - if (!keyboard::get_input(SwkbdType_NumPad, {}, keyboardString, buffer, 5)) + 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(); return; } - // Convert input to number and multiply it by 1MB. To do: Check if this is valid before continuing? - int64_t size = std::strtoll(buffer, NULL, 10) * 0x100000; - - // Grab the journal size. - int64_t journalSize = titleInfo->get_journal_size(saveInfo->save_data_type); - - // To do: Check this and toast message. - fs::extend_save_data(saveInfo, size, journalSize); + 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(); } diff --git a/source/appstates/UserOptionState.cpp b/source/appstates/UserOptionState.cpp index ce3def8..c1f584c 100644 --- a/source/appstates/UserOptionState.cpp +++ b/source/appstates/UserOptionState.cpp @@ -8,6 +8,7 @@ #include "appstates/TaskState.hpp" #include "config.hpp" #include "data/data.hpp" +#include "error.hpp" #include "fs/fs.hpp" #include "fslib.hpp" #include "input.hpp" @@ -54,8 +55,8 @@ UserOptionState::UserOptionState(data::User *user, TitleSelectCommon *titleSelec } // Fill this is. - m_dataStruct->m_user = m_user; - m_dataStruct->m_spawningState = this; + m_dataStruct->user = m_user; + m_dataStruct->spawningState = this; } void UserOptionState::update() @@ -162,135 +163,138 @@ void UserOptionState::data_and_view_refresh_required() { m_refreshRequired = tru static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr dataStruct) { - data::User *targetUser = dataStruct->m_user; + if (error::is_null(task)) { return; } - for (size_t i = 0; i < targetUser->get_total_data_entries(); i++) + data::User *user = dataStruct->user; + UserOptionState *spawningState = dataStruct->spawningState; + + const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP) || config::get_by_key(config::keys::AUTO_UPLOAD); + const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); + const size_t titleCount = user->get_total_data_entries(); + for (size_t i = 0; i < titleCount; i++) { - // This should be safe like this.... - FsSaveDataInfo *currentSaveInfo = targetUser->get_save_info_at(i); - data::TitleInfo *currentTitle = data::get_title_info_by_id(currentSaveInfo->application_id); + const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); + data::TitleInfo *titleInfo = data::get_title_info_by_id(saveInfo->application_id); + if (error::is_null(saveInfo) || error::is_null(titleInfo)) { continue; } - if (!currentSaveInfo || !currentTitle) { continue; } + const fslib::Path targetDir{config::get_working_directory() / titleInfo->get_path_safe_title()}; + const bool targetExists = fslib::directory_exists(targetDir); + const bool targetFailed = !targetExists && error::fslib(fslib::create_directories_recursively(targetDir)); + if (!targetExists && targetFailed) { continue; } - // Try to create target game folder. - fslib::Path gameFolder = config::get_working_directory() / currentTitle->get_path_safe_title(); - if (!fslib::directory_exists(gameFolder) && !fslib::create_directory(gameFolder)) { continue; } - - // Try to mount save data. - bool saveMounted = fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *targetUser->get_save_info_at(i)); - - // Check to make sure the save actually has data to avoid blanks. + const bool mountFailed = error::fslib(fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)); + const bool validSave = !mountFailed && fs::directory_has_contents(fs::DEFAULT_SAVE_ROOT); + if (mountFailed || !validSave) { - fslib::Directory saveCheck(fs::DEFAULT_SAVE_ROOT); - if (saveMounted && saveCheck.get_count() <= 0) + fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); + continue; + } + + fs::SaveMetaData saveMeta{}; + const bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta); + + fslib::Path backupPath{targetDir / "AUTO - " + user->get_path_safe_nickname() + " - " + stringutil::get_date_string()}; + if (exportZip) + { + backupPath += ".zip"; + fs::MiniZip targetZip{backupPath}; + if (!targetZip.is_open()) { - // Gonna borrow these messages. No point in repeating them. - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::BACKUPMENU_POPS, 0)); fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); continue; } - } - if (currentTitle && saveMounted && config::get_by_key(config::keys::EXPORT_TO_ZIP)) - { - fslib::Path targetPath = - config::get_working_directory() / currentTitle->get_path_safe_title() / targetUser->get_path_safe_nickname() + - " - " + stringutil::get_date_string() + ".zip"; - - zipFile targetZip = zipOpen64(targetPath.full_path(), APPEND_STATUS_CREATE); - if (!targetZip) + if (hasMeta && targetZip.open_new_file(fs::NAME_SAVE_META)) { - logger::log("Error creating zip: %s", fslib::error::get_string()); - continue; + targetZip.write(&saveMeta, sizeof(fs::SaveMetaData)); + targetZip.close_current_file(); } fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, targetZip, task); - zipClose(targetZip, NULL); } - else if (currentTitle && saveMounted) + else { - fslib::Path targetPath = - config::get_working_directory() / currentTitle->get_path_safe_title() / targetUser->get_path_safe_nickname() + - " - " + stringutil::get_date_string(); - - if (!fslib::create_directory(targetPath)) { - logger::log("Error creating backup directory: %s", fslib::error::get_string()); - continue; + fslib::File metaFile{backupPath / fs::NAME_SAVE_META, FsOpenMode_Create | FsOpenMode_Write}; + if (hasMeta && metaFile.is_open()) { metaFile.write(&saveMeta, sizeof(fs::SaveMetaData)); } + fs::copy_directory(fs::DEFAULT_SAVE_ROOT, backupPath, task); } - fs::copy_directory(fs::DEFAULT_SAVE_ROOT, targetPath, 0, {}, task); } - - if (saveMounted) { fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); } } task->finished(); } static void create_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct) { - data::User *targetUser = dataStruct->m_user; + if (error::is_null(task)) { return; } - // Get title info map. - auto &titleInfoMap = data::get_title_info_map(); + data::User *user = dataStruct->user; + UserOptionState *spawningState = dataStruct->spawningState; + + auto &titleInfoMap = data::get_title_info_map(); + const int popTicks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + const char *statusTemplate = strings::get_by_name(strings::names::USEROPTION_STATUS, 0); + const char *popFailure = strings::get_by_name(strings::names::SAVECREATE_POPS, 0); + const FsSaveDataType saveType = user->get_account_save_type(); - // Iterate through it. for (auto &[applicationID, titleInfo] : titleInfoMap) { - if (!titleInfo.has_save_data_type(targetUser->get_account_save_type())) { continue; } + const bool hasType = titleInfo.has_save_data_type(saveType); + if (!hasType) { return; } - // Set status. - task->set_status(strings::get_by_name(strings::names::USEROPTION_STATUS, 0), titleInfo.get_title()); - - if (!fs::create_save_data_for(targetUser, &titleInfo)) { - // Function should log error. - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::SAVECREATE_POPS, 0)); + const std::string status = stringutil::get_formatted_string(statusTemplate, titleInfo.get_title()); + task->set_status(status); } + + const bool saveCreated = fs::create_save_data_for(user, &titleInfo); + if (!saveCreated) { ui::PopMessageManager::push_message(popTicks, popFailure); } } - - // This needs to be updated on the next update loop. - dataStruct->m_spawningState->data_and_view_refresh_required(); - + spawningState->data_and_view_refresh_required(); task->finished(); } static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct) { - // This just makes things easier to type. - data::User *targetUser = dataStruct->m_user; + if (error::is_null(task)) { return; } - // This is to keep track of what's deleted. Erasing on every loop throws the vector out of whack. + data::User *user = dataStruct->user; + UserOptionState *spawningState = dataStruct->spawningState; + const char *statusTemplate = strings::get_by_name(strings::names::USEROPTION_STATUS, 1); // Borrowed. No duplication. + const char *popFailed = strings::get_by_name(strings::names::SAVECREATE_POPS, 2); + const int popTicks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + const size_t totalDataEntries = user->get_total_data_entries(); std::vector applicationIDs; - for (size_t i = 0; i < targetUser->get_total_data_entries(); i++) + // Check this quick just in case. + if (user->get_account_save_type() == FsSaveDataType_System) { - // Grab title for title. - const char *targetTitle = data::get_title_info_by_id(targetUser->get_application_id_at(i))->get_title(); + task->finished(); + return; + } - // Update thread task. - task->set_status(strings::get_by_name(strings::names::USEROPTION_STATUS, 1), targetTitle); + for (size_t i = 0; i < totalDataEntries; i++) + { + const FsSaveDataInfo *saveInfo = user->get_save_info_at(i); + if (error::is_null(saveInfo)) { continue; } - // Grab a pointer quick. - FsSaveDataInfo *saveInfo = targetUser->get_save_info_at(i); - - // We don't want to let people nuke their entire system, basically. - if (saveInfo->save_data_type != FsSaveDataType_System && !fs::delete_save_data(targetUser->get_save_info_at(i))) { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::SAVECREATE_POPS, 2)); - continue; + const uint64_t applicationID = user->get_application_id_at(i); + data::TitleInfo *titleInfo = data::get_title_info_by_id(applicationID); + const char *title = titleInfo->get_title(); + const std::string status = stringutil::get_formatted_string(statusTemplate, title); + task->set_status(status); } - // Push the application ID back. + const bool saveDeleted = fs::delete_save_data(saveInfo); + if (!saveDeleted) + { + ui::PopMessageManager::push_message(popTicks, popFailed); + continue; + } applicationIDs.push_back(saveInfo->application_id); } - // Loop through the IDs and purge them all. - for (uint64_t &applicationID : applicationIDs) { targetUser->erase_save_info_by_id(applicationID); } - - // Signal the main thread to update~ - dataStruct->m_spawningState->data_and_view_refresh_required(); - + for (uint64_t &applicationID : applicationIDs) { user->erase_save_info_by_id(applicationID); } + spawningState->data_and_view_refresh_required(); task->finished(); } diff --git a/source/config.cpp b/source/config.cpp index a1e28ee..265b16c 100644 --- a/source/config.cpp +++ b/source/config.cpp @@ -1,7 +1,9 @@ #include "config.hpp" + #include "JSON.hpp" #include "logger.hpp" #include "stringutil.hpp" + #include #include #include @@ -45,10 +47,7 @@ static void read_array_to_vector(std::vector &vector, json_object *arr for (size_t i = 0; i < arrayLength; i++) { json_object *arrayEntry = json_object_array_get_idx(array, i); - if (!arrayEntry) - { - continue; - } + if (!arrayEntry) { continue; } vector.push_back(std::strtoull(json_object_get_string(arrayEntry), NULL, 16)); } } @@ -71,10 +70,10 @@ void config::initialize() } json_object_iterator configIterator = json_object_iter_begin(configJSON.get()); - json_object_iterator configEnd = json_object_iter_end(configJSON.get()); + json_object_iterator configEnd = json_object_iter_end(configJSON.get()); while (!json_object_iter_equal(&configIterator, &configEnd)) { - const char *keyName = json_object_iter_peek_name(&configIterator); + const char *keyName = json_object_iter_peek_name(&configIterator); json_object *configValue = json_object_iter_peek_value(&configIterator); // These are exemptions. @@ -86,18 +85,9 @@ void config::initialize() { s_uiAnimationScaling = json_object_get_double(configValue); } - else if (std::strcmp(keyName, config::keys::FAVORITES.data()) == 0) - { - read_array_to_vector(s_favorites, configValue); - } - else if (std::strcmp(keyName, config::keys::BLACKLIST.data()) == 0) - { - read_array_to_vector(s_blacklist, configValue); - } - else - { - s_configMap[keyName] = json_object_get_uint64(configValue); - } + else if (std::strcmp(keyName, config::keys::FAVORITES.data()) == 0) { read_array_to_vector(s_favorites, configValue); } + else if (std::strcmp(keyName, config::keys::BLACKLIST.data()) == 0) { read_array_to_vector(s_blacklist, configValue); } + else { s_configMap[keyName] = json_object_get_uint64(configValue); } json_object_iter_next(&configIterator); } @@ -109,18 +99,15 @@ void config::initialize() } json::Object pathsJSON = json::new_object(json_object_from_file, PATH_PATHS_PATH.data()); - if (!pathsJSON) - { - return; - } + if (!pathsJSON) { return; } json_object_iterator pathsIterator = json_object_iter_begin(pathsJSON.get()); - json_object_iterator pathsEnd = json_object_iter_end(pathsJSON.get()); + json_object_iterator pathsEnd = json_object_iter_end(pathsJSON.get()); while (!json_object_iter_equal(&pathsIterator, &pathsEnd)) { // Grab these uint64_t applicationID = std::strtoull(json_object_iter_peek_name(&pathsIterator), NULL, 16); - json_object *path = json_object_iter_peek_value(&pathsIterator); + json_object *path = json_object_iter_peek_value(&pathsIterator); // Map em. s_pathMap[applicationID] = json_object_get_string(path); @@ -131,24 +118,24 @@ void config::initialize() void config::reset_to_default() { - s_workingDirectory = PATH_DEFAULT_WORK_DIR; - s_configMap[config::keys::INCLUDE_DEVICE_SAVES.data()] = 0; - s_configMap[config::keys::AUTO_BACKUP_ON_RESTORE.data()] = 1; - s_configMap[config::keys::AUTO_NAME_BACKUPS.data()] = 0; - s_configMap[config::keys::AUTO_UPLOAD.data()] = 0; - s_configMap[config::keys::HOLD_FOR_DELETION.data()] = 1; - s_configMap[config::keys::HOLD_FOR_RESTORATION.data()] = 1; - s_configMap[config::keys::HOLD_FOR_OVERWRITE.data()] = 1; - s_configMap[config::keys::ONLY_LIST_MOUNTABLE.data()] = 1; - s_configMap[config::keys::LIST_ACCOUNT_SYS_SAVES.data()] = 0; + s_workingDirectory = PATH_DEFAULT_WORK_DIR; + s_configMap[config::keys::INCLUDE_DEVICE_SAVES.data()] = 0; + s_configMap[config::keys::AUTO_BACKUP_ON_RESTORE.data()] = 1; + s_configMap[config::keys::AUTO_NAME_BACKUPS.data()] = 0; + s_configMap[config::keys::AUTO_UPLOAD.data()] = 0; + s_configMap[config::keys::HOLD_FOR_DELETION.data()] = 1; + s_configMap[config::keys::HOLD_FOR_RESTORATION.data()] = 1; + s_configMap[config::keys::HOLD_FOR_OVERWRITE.data()] = 1; + s_configMap[config::keys::ONLY_LIST_MOUNTABLE.data()] = 1; + s_configMap[config::keys::LIST_ACCOUNT_SYS_SAVES.data()] = 0; s_configMap[config::keys::ALLOW_WRITING_TO_SYSTEM.data()] = 0; - s_configMap[config::keys::EXPORT_TO_ZIP.data()] = 1; - s_configMap[config::keys::ZIP_COMPRESSION_LEVEL.data()] = 6; - s_configMap[config::keys::TITLE_SORT_TYPE.data()] = 0; - s_configMap[config::keys::JKSM_TEXT_MODE.data()] = 0; - s_configMap[config::keys::FORCE_ENGLISH.data()] = 0; - s_configMap[config::keys::ENABLE_TRASH_BIN.data()] = 0; - s_uiAnimationScaling = 2.5f; + s_configMap[config::keys::EXPORT_TO_ZIP.data()] = 1; + s_configMap[config::keys::ZIP_COMPRESSION_LEVEL.data()] = 6; + s_configMap[config::keys::TITLE_SORT_TYPE.data()] = 0; + s_configMap[config::keys::JKSM_TEXT_MODE.data()] = 0; + s_configMap[config::keys::FORCE_ENGLISH.data()] = 0; + s_configMap[config::keys::ENABLE_TRASH_BIN.data()] = 0; + s_uiAnimationScaling = 2.5f; } void config::save() @@ -176,8 +163,7 @@ void config::save() for (uint64_t &titleID : s_favorites) { // Need to do it like this or json-c does decimal instead of hex. - json_object *newFavorite = - json_object_new_string(stringutil::get_formatted_string("%016lX", titleID).c_str()); + json_object *newFavorite = json_object_new_string(stringutil::get_formatted_string("%016lX", titleID).c_str()); json_object_array_add(favoritesArray, newFavorite); } json::add_object(configJSON, config::keys::FAVORITES.data(), favoritesArray); @@ -186,8 +172,7 @@ void config::save() json_object *blacklistArray = json_object_new_array(); for (uint64_t &titleID : s_blacklist) { - json_object *newBlacklist = - json_object_new_string(stringutil::get_formatted_string("%016lX", titleID).c_str()); + json_object *newBlacklist = json_object_new_string(stringutil::get_formatted_string("%016lX", titleID).c_str()); json_object_array_add(blacklistArray, newBlacklist); } json::add_object(configJSON, config::keys::BLACKLIST.data(), blacklistArray); @@ -196,10 +181,7 @@ void config::save() fslib::File configFile(PATH_CONFIG_FILE, FsOpenMode_Create | FsOpenMode_Write, std::strlen(json_object_get_string(configJSON.get()))); - if (configFile) - { - configFile << json_object_get_string(configJSON.get()); - } + if (configFile) { configFile << json_object_get_string(configJSON.get()); } } if (!s_pathMap.empty()) @@ -221,10 +203,7 @@ void config::save() fslib::File pathsFile(PATH_PATHS_PATH, FsOpenMode_Create | FsOpenMode_Write, std::strlen(json_object_get_string(pathsJSON.get()))); - if (pathsFile) - { - pathsFile << json_object_get_string(pathsJSON.get()); - } + if (pathsFile) { pathsFile << json_object_get_string(pathsJSON.get()); } } } @@ -232,67 +211,40 @@ uint8_t config::get_by_key(std::string_view key) { // See if the key can be found. auto findKey = s_configMap.find(key.data()); - if (findKey == s_configMap.end()) - { - return 0; - } + if (findKey == s_configMap.end()) { return 0; } return findKey->second; } void config::toggle_by_key(std::string_view key) { auto findKey = s_configMap.find(key.data()); - if (findKey == s_configMap.end()) - { - return; - } + if (findKey == s_configMap.end()) { return; } findKey->second = findKey->second ? 0 : 1; } void config::set_by_key(std::string_view key, uint8_t value) { auto findKey = s_configMap.find(key.data()); - if (findKey == s_configMap.end()) - { - return; - } + if (findKey == s_configMap.end()) { return; } findKey->second = value; } -fslib::Path config::get_working_directory() -{ - return s_workingDirectory; -} +fslib::Path config::get_working_directory() { return s_workingDirectory; } -double config::get_animation_scaling() -{ - return s_uiAnimationScaling; -} +double config::get_animation_scaling() { return s_uiAnimationScaling; } -void config::set_animation_scaling(double newScale) -{ - s_uiAnimationScaling = newScale; -} +void config::set_animation_scaling(double newScale) { s_uiAnimationScaling = newScale; } void config::add_remove_favorite(uint64_t applicationID) { auto findTitle = std::find(s_favorites.begin(), s_favorites.end(), applicationID); - if (findTitle == s_favorites.end()) - { - s_favorites.push_back(applicationID); - } - else - { - s_favorites.erase(findTitle); - } + if (findTitle == s_favorites.end()) { s_favorites.push_back(applicationID); } + else { s_favorites.erase(findTitle); } } bool config::is_favorite(uint64_t applicationID) { - if (std::find(s_favorites.begin(), s_favorites.end(), applicationID) == s_favorites.end()) - { - return false; - } + if (std::find(s_favorites.begin(), s_favorites.end(), applicationID) == s_favorites.end()) { return false; } return true; } @@ -300,22 +252,13 @@ bool config::is_favorite(uint64_t applicationID) void config::add_remove_blacklist(uint64_t applicationID) { auto findTitle = std::find(s_blacklist.begin(), s_blacklist.end(), applicationID); - if (findTitle == s_blacklist.end()) - { - s_blacklist.push_back(applicationID); - } - else - { - s_blacklist.erase(findTitle); - } + if (findTitle == s_blacklist.end()) { s_blacklist.push_back(applicationID); } + else { s_blacklist.erase(findTitle); } } bool config::is_blacklisted(uint64_t applicationID) { - if (std::find(s_blacklist.begin(), s_blacklist.end(), applicationID) == s_blacklist.end()) - { - return false; - } + if (std::find(s_blacklist.begin(), s_blacklist.end(), applicationID) == s_blacklist.end()) { return false; } return true; } @@ -327,20 +270,14 @@ void config::add_custom_path(uint64_t applicationID, std::string_view customPath bool config::has_custom_path(uint64_t applicationID) { - if (s_pathMap.find(applicationID) == s_pathMap.end()) - { - return false; - } + if (s_pathMap.find(applicationID) == s_pathMap.end()) { return false; } return true; } void config::get_custom_path(uint64_t applicationID, char *pathOut, size_t pathOutSize) { - if (s_pathMap.find(applicationID) == s_pathMap.end()) - { - return; - } + if (s_pathMap.find(applicationID) == s_pathMap.end()) { return; } std::memcpy(pathOut, s_pathMap[applicationID].c_str(), s_pathMap[applicationID].length()); } diff --git a/source/curl/curl.cpp b/source/curl/curl.cpp index af74922..399ca0b 100644 --- a/source/curl/curl.cpp +++ b/source/curl/curl.cpp @@ -1,4 +1,6 @@ #include "curl/curl.hpp" + +#include "error.hpp" #include "logger.hpp" #include "stringutil.hpp" @@ -8,15 +10,9 @@ namespace constexpr size_t SIZE_UPLOAD_BUFFER = 0x10000; } // namespace -bool curl::initialize() -{ - return curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK; -} +bool curl::initialize() { return curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK; } -void curl::exit() -{ - curl_global_cleanup(); -} +void curl::exit() { curl_global_cleanup(); } bool curl::perform(curl::Handle &handle) { @@ -33,14 +29,20 @@ void curl::append_header(curl::HeaderList &list, std::string_view header) { // This is the only real way to accomplish this since slist is a linked list. curl_slist *head = list.release(); - head = curl_slist_append(head, header.data()); + head = curl_slist_append(head, header.data()); list.reset(head); } -size_t curl::read_data_from_file(char *buffer, size_t size, size_t count, fslib::File *target) +size_t curl::read_data_from_file(char *buffer, size_t size, size_t count, curl::UploadStruct *upload) { - // This should be good enough. - return target->read(buffer, size * count); + if (error::is_null(upload)) { return -1; } + fslib::File *source = upload->source; + sys::ProgressTask *task = upload->task; + + ssize_t readSize = source->read(buffer, size * count); + if (task) { task->update_current(static_cast(source->tell())); } + + return readSize; } size_t curl::write_header_array(const char *buffer, size_t size, size_t count, curl::HeaderArray *array) @@ -65,17 +67,11 @@ bool curl::get_header_value(const curl::HeaderArray &array, std::string_view hea for (const std::string ¤tHeader : array) { size_t colonPos = currentHeader.find_first_of(':'); - if (colonPos == currentHeader.npos) - { - continue; - } + if (colonPos == currentHeader.npos) { continue; } // Get the substr. std::string headerName = currentHeader.substr(0, colonPos); - if (headerName != header) - { - continue; - } + if (headerName != header) { continue; } // Find the first thing after that isn't a space. size_t valueBegin = currentHeader.find_first_not_of(' ', colonPos + 1); @@ -105,10 +101,7 @@ long curl::get_response_code(curl::Handle &handle) bool curl::escape_string(curl::Handle &handle, std::string_view in, std::string &out) { char *escaped = curl_easy_escape(handle.get(), in.data(), in.length()); - if (!escaped) - { - return false; - } + if (!escaped) { return false; } out.assign(escaped); @@ -121,10 +114,7 @@ bool curl::unescape_string(curl::Handle &handle, std::string_view in, std::strin { int lengthOut{}; char *unescaped = curl_easy_unescape(handle.get(), in.data(), in.length(), &lengthOut); - if (!unescaped) - { - return false; - } + if (!unescaped) { return false; } out.assign(unescaped); diff --git a/source/fs/MiniUnzip.cpp b/source/fs/MiniUnzip.cpp index 244281c..2bd79fb 100644 --- a/source/fs/MiniUnzip.cpp +++ b/source/fs/MiniUnzip.cpp @@ -1,6 +1,7 @@ #include "fs/MiniUnzip.hpp" #include "error.hpp" +#include "logger.hpp" fs::MiniUnzip::MiniUnzip(const fslib::Path &path) { MiniUnzip::open(path); } @@ -12,7 +13,7 @@ bool fs::MiniUnzip::open(const fslib::Path &path) { MiniUnzip::close(); m_unz = unzOpen64(path.full_path()); - if (error::is_null(m_unz)) { return false; } + if (error::is_null(m_unz) || !MiniUnzip::reset()) { return false; } m_isOpen = true; return true; } @@ -26,13 +27,37 @@ void fs::MiniUnzip::close() bool fs::MiniUnzip::next_file() { - const bool end = unzGoToNextFile(m_unz) == UNZ_END_OF_LIST_OF_FILE; - const bool readInfo = - !end && unzGetCurrentFileInfo64(m_unz, &m_fileInfo, m_filename, FS_MAX_PATH, nullptr, 0, nullptr, 0) == UNZ_OK; - return !end && readInfo; + const bool notEnd = unzGoToNextFile(m_unz) == UNZ_OK; + const bool getInfo = unzGetCurrentFileInfo64(m_unz, &m_fileInfo, m_filename, FS_MAX_PATH, nullptr, 0, nullptr, 0) == UNZ_OK; + const bool opened = unzOpenCurrentFile(m_unz) == UNZ_OK; + return notEnd && getInfo && opened; } -ssize_t fs::MiniUnzip::read(void *buffer, size_t bufferSize) { return unzReadCurrentFile(buffer, buffer, bufferSize); } +bool fs::MiniUnzip::close_current_file() { return unzCloseCurrentFile(m_unz) == UNZ_OK; } + +bool fs::MiniUnzip::locate_file(std::string_view filename) +{ + if (!MiniUnzip::reset()) { return false; } + + do { + if (m_filename == filename) { return true; } + } while (MiniUnzip::next_file()); + + MiniUnzip::reset(); + return false; +} + +bool fs::MiniUnzip::reset() +{ + const bool firstFile = unzGoToFirstFile(m_unz) == UNZ_OK; + const bool getInfo = unzGetCurrentFileInfo64(m_unz, &m_fileInfo, m_filename, FS_MAX_PATH, nullptr, 0, nullptr, 0) == UNZ_OK; + const bool opened = firstFile && unzOpenCurrentFile(m_unz) == UNZ_OK; + return firstFile && getInfo && opened; +} + +ssize_t fs::MiniUnzip::read(void *buffer, size_t bufferSize) { return unzReadCurrentFile(m_unz, buffer, bufferSize); } + +const char *fs::MiniUnzip::get_filename() { return m_filename; } uint64_t fs::MiniUnzip::get_compressed_size() const { return m_fileInfo.compressed_size; } diff --git a/source/fs/SaveMetaData.cpp b/source/fs/SaveMetaData.cpp index b6dffd0..85f69c4 100644 --- a/source/fs/SaveMetaData.cpp +++ b/source/fs/SaveMetaData.cpp @@ -1,86 +1,63 @@ #include "fs/SaveMetaData.hpp" + +#include "error.hpp" #include "fs/directory_functions.hpp" #include "fs/save_data_functions.hpp" #include "fs/save_mount.hpp" #include "fslib.hpp" -#include "logger.hpp" namespace { - /// @brief This is the string template for errors here. - constexpr std::string_view STRING_ERROR_TEMPLATE = "Error processing save meta for %016llX: %s"; -} // namespace + constexpr size_t SIZE_EXTRA_DATA = sizeof(FsSaveDataExtraData); +} bool fs::fill_save_meta_data(const FsSaveDataInfo *saveInfo, fs::SaveMetaData &meta) { - // This struct will allow us to fill this all out in one shot. - FsSaveDataExtraData extraData; - if (R_FAILED(fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId( - &extraData, - sizeof(FsSaveDataExtraData), - static_cast(saveInfo->save_data_space_id), - saveInfo->save_data_id))) - { - logger::log("Error generating save meta: Failed to read save extra data!"); - return false; - } + const FsSaveDataSpaceId spaceID = static_cast(saveInfo->save_data_space_id); + const uint64_t saveID = saveInfo->save_data_id; - // Fill the struct. - meta = {.m_magic = fs::SAVE_META_MAGIC, - .m_revision = 0x00, - .m_applicationID = extraData.attr.application_id, - .m_accountID = extraData.attr.uid, - .m_systemSaveID = extraData.attr.system_save_data_id, - .m_saveDataType = extraData.attr.save_data_type, - .m_saveDataRank = extraData.attr.save_data_rank, - .m_saveDataIndex = extraData.attr.save_data_index, - .m_ownerID = extraData.owner_id, - .m_timestamp = extraData.timestamp, - .m_flags = extraData.flags, - .m_saveDataSize = extraData.data_size, - .m_journalSize = extraData.journal_size, - .m_commitID = extraData.commit_id}; + FsSaveDataExtraData extraData{}; + const bool readError = + error::libnx(fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(&extraData, SIZE_EXTRA_DATA, spaceID, saveID)); + if (readError) { return false; } + + meta = {.magic = fs::SAVE_META_MAGIC, + .revision = 0x00, + .applicationID = extraData.attr.application_id, + .accountID = extraData.attr.uid, + .systemSaveID = extraData.attr.system_save_data_id, + .saveDataType = extraData.attr.save_data_type, + .saveDataRank = extraData.attr.save_data_rank, + .saveDataIndex = extraData.attr.save_data_index, + .ownerID = extraData.owner_id, + .timestamp = extraData.timestamp, + .flags = extraData.flags, + .saveDataSize = extraData.data_size, + .journalSize = extraData.journal_size, + .commitID = extraData.commit_id}; - // Should be good. return true; } bool fs::process_save_meta_data(const FsSaveDataInfo *saveInfo, const SaveMetaData &meta) { - // We're going to grab this quick and use this to compare. - FsSaveDataExtraData extraData = {0}; - if (R_FAILED(fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId( - &extraData, - sizeof(FsSaveDataExtraData), - static_cast(saveInfo->save_data_space_id), - saveInfo->save_data_id))) - { - logger::log(STRING_ERROR_TEMPLATE.data(), saveInfo->application_id, fslib::error::get_string()); - return false; - } + const FsSaveDataSpaceId spaceID = static_cast(saveInfo->save_data_space_id); + const uint64_t saveID = saveInfo->save_data_id; - // We need to temporarily close the file system. - if (!fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT)) - { - logger::log(STRING_ERROR_TEMPLATE.data(), saveInfo->application_id, fslib::error::get_string()); - return false; - } + FsSaveDataExtraData extraData{}; + const bool readError = + error::libnx(fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(&extraData, SIZE_EXTRA_DATA, spaceID, saveID)); + if (readError) { return false; } - // To do: Other checks. - if (extraData.data_size < meta.m_saveDataSize && - !fs::extend_save_data(saveInfo, meta.m_saveDataSize, meta.m_journalSize)) - { - // The fs::extend_save_data function should log the error that occurred. - return false; - } + // We need to close this temporarily. To do: Look for a way to make this not needed? + const bool closeError = error::fslib(fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT)); + const bool needsExtend = extraData.data_size < meta.saveDataSize; + const bool extended = !closeError && needsExtend && fs::extend_save_data(saveInfo, meta.saveDataSize, meta.journalSize); + if (needsExtend && !extended) { return false; } - // Now reopen it. - if (!fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)) - { - logger::log(STRING_ERROR_TEMPLATE.data(), saveInfo->application_id, fslib::error::get_string()); - return false; - } + const bool reopenError = error::fslib(fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)); - // More later if needed. - return true; + // Maybe more later. + + return reopenError; } diff --git a/source/fs/directory_functions.cpp b/source/fs/directory_functions.cpp index 25ae810..580551f 100644 --- a/source/fs/directory_functions.cpp +++ b/source/fs/directory_functions.cpp @@ -1,35 +1,36 @@ #include "fs/directory_functions.hpp" +#include "fs/SaveMetaData.hpp" + uint64_t fs::get_directory_total_size(const fslib::Path &targetPath) { - fslib::Directory targetDir(targetPath); - if (!targetDir) - { - return 0; - } + fslib::Directory targetDir{targetPath}; + if (!targetDir) { return 0; } - uint64_t directorySize = 0; - for (int64_t i = 0; i < targetDir.get_count(); i++) + const int64_t itemCount = targetDir.get_count(); + uint64_t directorySize = 0; + for (int64_t i = 0; i < itemCount; i++) { if (targetDir.is_directory(i)) { fslib::Path newTarget = targetPath / targetDir[i]; directorySize += get_directory_total_size(newTarget); } - else - { - directorySize += targetDir.get_entry_size(i); - } + else { directorySize += targetDir.get_entry_size(i); } } return directorySize; } bool fs::directory_has_contents(const fslib::Path &directoryPath) { - fslib::Directory testDir(directoryPath); - if (!testDir) + fslib::Directory testDir{directoryPath}; + if (!testDir) { return false; } + + // We don't want the save meta to throw this off. + const int64_t itemCount = testDir.get_count(); + for (int64_t i = 0; i < itemCount; i++) { - return false; + if (testDir[i] != fs::NAME_SAVE_META) { return true; } } - return testDir.get_count() != 0; + return false; } diff --git a/source/fs/io.cpp b/source/fs/io.cpp index 22d7385..ff29974 100644 --- a/source/fs/io.cpp +++ b/source/fs/io.cpp @@ -1,7 +1,11 @@ #include "fs/io.hpp" -#include "logger.hpp" +#include "error.hpp" +#include "fslib.hpp" #include "strings.hpp" +#include "stringutil.hpp" +#include "system/defines.hpp" +#include "ui/PopMessageManager.hpp" #include #include @@ -13,166 +17,150 @@ namespace // Size of buffer shared between threads. // constexpr size_t FILE_BUFFER_SIZE = 0x600000; // This one is just for testing something. - constexpr size_t FILE_BUFFER_SIZE = 0x80000; + constexpr size_t SIZE_FILE_BUFFER = 0x200000; } // namespace -// Struct threads shared to read and write files. -typedef struct +// clang-format off +struct FileThreadStruct { - // Mutex to lock buffer. - std::mutex m_bufferLock; - // Conditional to wait on signals. - std::condition_variable m_bufferCondition; - // Bool to control signals. - bool m_bufferIsFull = false; - // Number of bytes read. - size_t m_readSize = 0; - // Shared (read) buffer. - std::unique_ptr m_readBuffer; -} FileTransferStruct; + std::mutex lock{}; + std::condition_variable condition{}; + bool bufferReady{}; + ssize_t readSize{}; + std::unique_ptr sharedBuffer{}; +}; +// clang-format on -// This function Reads into the buffer. The other thread writes. -static void readThreadFunction(fslib::File &sourceFile, std::shared_ptr sharedData) +static void readThreadFunction(fslib::File &sourceFile, std::shared_ptr sharedData) { - int64_t fileSize = sourceFile.get_size(); - for (int64_t readCount = 0; readCount < fileSize;) + 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;) { - // Read data to shared buffer. - sharedData->m_readSize = sourceFile.read(sharedData->m_readBuffer.get(), FILE_BUFFER_SIZE); - // Update local read count - readCount += sharedData->m_readSize; - // Signal to other thread buffer is full. - sharedData->m_bufferIsFull = true; - sharedData->m_bufferCondition.notify_one(); - // Wait for other thread to signal buffer is empty. Lock is released immediately, but it works and that's what matters. - std::unique_lock m_bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(m_bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull == false; }); + ssize_t localRead{}; + { + std::unique_lock bufferLock(lock); + condition.wait(bufferLock, [&]() { return bufferReady == false; }); + + readSize = sourceFile.read(sharedBuffer.get(), SIZE_FILE_BUFFER); + localRead = readSize; + + bufferReady = true; + condition.notify_one(); + } + if (localRead == -1) { break; } + i += localRead; } } void fs::copy_file(const fslib::Path &source, const fslib::Path &destination, + sys::ProgressTask *task, uint64_t journalSize, - std::string_view commitDevice, - sys::ProgressTask *task) + std::string_view commitDevice) { - fslib::File sourceFile(source, FsOpenMode_Read); - fslib::File destinationFile(destination, FsOpenMode_Create | FsOpenMode_Write, sourceFile.get_size()); - if (!sourceFile || !destinationFile) + const char *statusTemplate = strings::get_by_name(strings::names::IO_STATUSES, 0); + const char *popErrorCommitting = strings::get_by_name(strings::names::IO_POPS, 0); + const int popticks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + + fslib::File sourceFile{source, FsOpenMode_Read}; + fslib::File destFile{destination, FsOpenMode_Create | FsOpenMode_Write, sourceFile.get_size()}; + if (error::fslib(sourceFile.is_open()) || error::fslib(destFile.is_open())) { return; } + + const bool needsCommits = journalSize > 0 && !commitDevice.empty(); + const int64_t fileSize = sourceFile.get_size(); + if (task) { - logger::log("Error opening one of the files: %s", fslib::error::get_string()); - return; + const std::string status = stringutil::get_formatted_string(statusTemplate, source.full_path()); + task->set_status(status); + task->reset(static_cast(fileSize)); } - // Set status if task pointer was passed. - if (task) { task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 0), source.full_path()); } + auto sharedData = std::make_shared(); + sharedData->sharedBuffer = std::make_unique(SIZE_FILE_BUFFER); + auto localBuffer = std::make_unique(SIZE_FILE_BUFFER); - // Shared struct both threads use - std::shared_ptr sharedData(new FileTransferStruct); - sharedData->m_readBuffer = std::make_unique(FILE_BUFFER_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; - // To do: Static thread or pool to avoid reallocating thread. std::thread readThread(readThreadFunction, std::ref(sourceFile), sharedData); - // This thread has a local buffer so the read thread can continue while this one writes. - std::unique_ptr localBuffer(new unsigned char[FILE_BUFFER_SIZE]); - - // Get file size for loop and set goal. - int64_t fileSize = sourceFile.get_size(); - if (task) { task->reset(static_cast(fileSize)); } - - for (int64_t writeCount = 0, readCount = 0, journalCount = 0; writeCount < fileSize;) + int64_t journalCount{}; + for (int64_t i = 0; i < fileSize; i++) { + ssize_t localRead{}; { - // Wait for lock/signal. - std::unique_lock m_bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(m_bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull; }); + std::unique_lock bufferLock(lock); + condition.wait(bufferLock, [&]() { return bufferReady == true; }); - // Record read count. - readCount = sharedData->m_readSize; + localRead = readSize; + if (localRead == -1) { break; } - // Copy shared to local. - std::memcpy(localBuffer.get(), sharedData->m_readBuffer.get(), readCount); + std::memcpy(localBuffer.get(), sharedBuffer.get(), localRead); - // Signal buffer was copied and release mutex. - sharedData->m_bufferIsFull = false; - sharedData->m_bufferCondition.notify_one(); + bufferReady = false; + condition.notify_one(); } - // Journaling size check. Breathing room is given. - if (journalSize != 0 && (journalCount + readCount) >= static_cast(journalSize) - 0x100000) + const bool commitNeeded = needsCommits && (journalCount + localRead) >= static_cast(journalSize); + if (commitNeeded) { - // Reset journal count. - journalCount = 0; - // Close destination file, commit. - destinationFile.close(); - - // Need to try to commit before going over the journaling space limit. - if (!fslib::commit_data_to_file_system(commitDevice)) + destFile.close(); + const bool commitError = error::fslib(fslib::commit_data_to_file_system(commitDevice)); + if (commitError) { - logger::log(fslib::error::get_string()); - // I guess break the loop here? + ui::PopMessageManager::push_message(popticks, popErrorCommitting); break; } - // Reopen and seek to previous position since we created it with a size earlier. - destinationFile.open(destination, FsOpenMode_Append); + destFile.open(destination, FsOpenMode_Write); + destFile.seek(i, destFile.BEGINNING); + + journalCount = 0; } - - // Write to destination - destinationFile.write(localBuffer.get(), readCount); - - // Update write and journal count. - writeCount += readCount; - journalCount += readCount; - - // Update task if passed. - if (task) { task->update_current(static_cast(writeCount)); } + // This should be checked. Not sure how yet... + destFile.write(localBuffer.get(), localRead); + i += localRead; + journalCount += localRead; + if (task) { task->update_current(static_cast(i)); } } - // Close the destination for committing. - destinationFile.close(); - - // One last commit for good luck. - if (!fslib::commit_data_to_file_system(commitDevice)) { logger::log(fslib::error::get_string()); } - - // Wait for read thread and free it. readThread.join(); + destFile.close(); + + const bool commitError = needsCommits && error::fslib(fslib::commit_data_to_file_system(commitDevice)); + if (commitError) { ui::PopMessageManager::push_message(popticks, popErrorCommitting); } } void fs::copy_directory(const fslib::Path &source, const fslib::Path &destination, + sys::ProgressTask *task, uint64_t journalSize, - std::string_view commitDevice, - sys::ProgressTask *task) + std::string_view commitDevice) { - fslib::Directory sourceDir(source); - if (!sourceDir) - { - logger::log("Error opening directory for reading: %s", fslib::error::get_string()); - return; - } + fslib::Directory sourceDir{source}; + if (error::fslib(sourceDir.is_open())) { return; } - for (int64_t i = 0; i < sourceDir.get_count(); i++) + const int64_t dirCount = sourceDir.get_count(); + for (int64_t i = 0; i < dirCount; i++) { + const fslib::Path fullSource{source / sourceDir[i]}; + const fslib::Path fullDest{destination / sourceDir[i]}; if (sourceDir.is_directory(i)) { - fslib::Path newSource = source / sourceDir[i]; - fslib::Path newDestination = destination / sourceDir[i]; - // Try to create new destination folder and continue loop on failure. - if (!fslib::directory_exists(newDestination) && !fslib::create_directory(newDestination)) - { - logger::log("Error creating new destination directory: %s", fslib::error::get_string()); - continue; - } - - fs::copy_directory(newSource, newDestination, journalSize, commitDevice, task); - } - else - { - fslib::Path fullSource = source / sourceDir[i]; - fslib::Path fullDestination = destination / sourceDir[i]; - fs::copy_file(fullSource, fullDestination, journalSize, commitDevice, task); + const bool destExists = fslib::directory_exists(fullDest); + const bool createError = !destExists && fslib::create_directory(fullDest); + if (!destExists && createError) { continue; } + fs::copy_directory(fullSource, fullDest, task, journalSize, commitDevice); } + else { fs::copy_file(fullSource, fullDest, task, journalSize, commitDevice); } } } diff --git a/source/fs/save_data_functions.cpp b/source/fs/save_data_functions.cpp index 52c8247..25ac0df 100644 --- a/source/fs/save_data_functions.cpp +++ b/source/fs/save_data_functions.cpp @@ -1,80 +1,64 @@ #include "fs/save_data_functions.hpp" + +#include "error.hpp" #include "logger.hpp" bool fs::create_save_data_for(data::User *targetUser, data::TitleInfo *titleInfo) { - // Attributes. - FsSaveDataAttribute saveAttributes = {.application_id = titleInfo->get_application_id(), - .uid = targetUser->get_account_save_type() == FsSaveDataType_Account - ? targetUser->get_account_id() - : data::BLANK_ACCOUNT_ID, - .system_save_data_id = 0, - .save_data_type = targetUser->get_account_save_type(), - .save_data_rank = FsSaveDataRank_Primary, - .save_data_index = 0}; + const uint8_t saveType = targetUser->get_account_save_type(); + const uint64_t applicationID = titleInfo->get_application_id(); + const AccountUid accountID = saveType == FsSaveDataType_Account ? targetUser->get_account_id() : data::BLANK_ACCOUNT_ID; + const uint64_t ownerID = saveType == FsSaveDataType_Bcat ? 0x010000000000000C : titleInfo->get_save_data_owner_id(); + const int64_t saveSize = titleInfo->get_save_data_size(saveType); + const int64_t journalSize = titleInfo->get_journal_size(saveType); - // For just creating it, we're using the safe baseline values. - FsSaveDataCreationInfo saveCreation = { - .save_data_size = titleInfo->get_save_data_size(targetUser->get_account_save_type()), - .journal_size = titleInfo->get_journal_size(targetUser->get_account_save_type()), - .available_size = 0x4000, - .owner_id = targetUser->get_account_save_type() == FsSaveDataType_Bcat ? 0x010000000000000C - : titleInfo->get_save_data_owner_id(), - .flags = 0, - .save_data_space_id = FsSaveDataSpaceId_User}; + const FsSaveDataAttribute saveAttributes = {.application_id = applicationID, + .uid = accountID, + .system_save_data_id = 0, + .save_data_type = saveType, + .save_data_rank = FsSaveDataRank_Primary, + .save_data_index = 0}; - // Save meta - FsSaveDataMetaInfo saveMeta = {.size = 0x40060, .type = FsSaveDataMetaType_Thumbnail}; + const FsSaveDataCreationInfo saveCreation = {.save_data_size = saveSize, + .journal_size = journalSize, + .available_size = 0x4000, + .owner_id = ownerID, + .flags = 0, + .save_data_space_id = FsSaveDataSpaceId_User}; - Result fsError = fsCreateSaveDataFileSystem(&saveAttributes, &saveCreation, &saveMeta); - if (R_FAILED(fsError)) - { - logger::log("Error creating save data for %016llX: 0x%X.", titleInfo->get_application_id(), fsError); - return false; - } - return true; + const FsSaveDataMetaInfo saveMeta = {.size = 0x40060, .type = FsSaveDataMetaType_Thumbnail}; + + // I want this recorded. + const bool createError = error::libnx(fsCreateSaveDataFileSystem(&saveAttributes, &saveCreation, &saveMeta)); + return createError; } bool fs::delete_save_data(const FsSaveDataInfo *saveInfo) { - // I'm not allowing this at all. - if (saveInfo->save_data_type == FsSaveDataType_System || saveInfo->save_data_type == FsSaveDataType_SystemBcat) - { - logger::log("Error deleting save data: Deleting system save data is not allowed."); - return false; - } + const FsSaveDataType saveType = static_cast(saveInfo->save_data_type); + const FsSaveDataSpaceId spaceID = static_cast(saveInfo->save_data_space_id); + const bool isSystem = fs::is_system_save_data(saveInfo); + if (isSystem) { return false; } // Save attributes. - FsSaveDataAttribute saveAttributes = {.application_id = saveInfo->application_id, - .uid = saveInfo->uid, - .system_save_data_id = saveInfo->system_save_data_id, - .save_data_type = saveInfo->save_data_type, - .save_data_rank = saveInfo->save_data_rank, - .save_data_index = saveInfo->save_data_index}; + const FsSaveDataAttribute saveAttributes = {.application_id = saveInfo->application_id, + .uid = saveInfo->uid, + .system_save_data_id = saveInfo->system_save_data_id, + .save_data_type = saveInfo->save_data_type, + .save_data_rank = saveInfo->save_data_rank, + .save_data_index = saveInfo->save_data_index}; - Result fsError = - fsDeleteSaveDataFileSystemBySaveDataAttribute(static_cast(saveInfo->save_data_space_id), - &saveAttributes); - if (R_FAILED(fsError)) - { - logger::log("Error deleting save data: 0x%X.", fsError); - return false; - } - return true; + const bool deleteError = error::libnx(fsDeleteSaveDataFileSystemBySaveDataAttribute(spaceID, &saveAttributes)); + return deleteError; } bool fs::extend_save_data(const FsSaveDataInfo *saveInfo, int64_t size, int64_t journalSize) { - Result fsError = fsExtendSaveDataFileSystem(static_cast(saveInfo->save_data_space_id), - saveInfo->save_data_id, - size, - journalSize); - if (R_FAILED(fsError)) - { - logger::log("Error extending save data: 0x%0X.", fsError); - return false; - } - return true; + const FsSaveDataSpaceId spaceID = static_cast(saveInfo->save_data_space_id); + const uint64_t saveID = saveInfo->save_data_id; + + const bool extendError = error::libnx(fsExtendSaveDataFileSystem(spaceID, saveID, size, journalSize)); + return extendError; } bool fs::is_system_save_data(const FsSaveDataInfo *saveInfo) diff --git a/source/fs/zip.cpp b/source/fs/zip.cpp index 2e4ca75..a89dd46 100644 --- a/source/fs/zip.cpp +++ b/source/fs/zip.cpp @@ -1,9 +1,13 @@ #include "fs/zip.hpp" #include "config.hpp" +#include "error.hpp" #include "fs/SaveMetaData.hpp" #include "logger.hpp" #include "strings.hpp" +#include "stringutil.hpp" +#include "system/defines.hpp" +#include "ui/PopMessageManager.hpp" #include #include @@ -22,386 +26,233 @@ namespace } // namespace // Shared struct for Zip/File IO -typedef struct +// clang-format off +struct ZipIOStruct { - /// @brief Mutex for blocking the shared buffer. - std::mutex m_bufferLock; - - /// @brief Conditional for locking and unlocking. - std::condition_variable m_bufferCondition; - - /// @brief Bool that lets threads communicate when they are using the buffer. - bool m_bufferIsFull = false; - - /// @brief Number of bytes read from the file. - ssize_t m_readCount = 0; - - /// @brief Shared/reading buffer. - std::unique_ptr m_sharedBuffer; -} ZipIOStruct; + std::mutex lock{}; + std::condition_variable condition{}; + ssize_t readSize{}; + bool bufferReady{}; + std::unique_ptr sharedBuffer{}; +}; +// clang-format on // Function for reading files for Zipping. static void zipReadThreadFunction(fslib::File &source, std::shared_ptr sharedData) { - // Don't call this every loop. Not sure if compiler optimizes that out or not now. - 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(); - // Loop until the file is completely read. - for (int64_t readCount = 0; readCount < fileSize;) + for (int64_t i = 0; i < fileSize;) { - // Read into shared buffer. - sharedData->m_readCount = source.read(sharedData->m_sharedBuffer.get(), SIZE_ZIP_BUFFER); + ssize_t localRead{}; // This is a local variable to store the read size so we don't need to hold the other thread up. + { + std::unique_lock bufferLock(lock); + condition.wait(bufferLock, [&]() { return bufferReady == false; }); - // Update read count - readCount += sharedData->m_readCount; + readSize = source.read(sharedBuffer.get(), SIZE_ZIP_BUFFER); + localRead = readSize; - // Signal other thread buffer is ready to go. - sharedData->m_bufferIsFull = true; - sharedData->m_bufferCondition.notify_one(); - - // Wait for other thread to release lock on buffer so this thread can read again. - std::unique_lock bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull == false; }); + bufferReady = true; + condition.notify_one(); + } + if (readSize == -1) { break; } + i += localRead; } } // Function for reading data from Zip to buffer. -static void unzipReadThreadFunction(unzFile source, int64_t fileSize, std::shared_ptr sharedData) +static void unzipReadThreadFunction(fs::MiniUnzip &unzip, std::shared_ptr sharedData) { - for (int64_t readCount = 0; readCount < fileSize;) + 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;) { - // Read from zip file. - sharedData->m_readCount = unzReadCurrentFile(source, sharedData->m_sharedBuffer.get(), SIZE_UNZIP_BUFFER); + ssize_t localRead{}; + { + std::unique_lock bufferLock(lock); + condition.wait(bufferLock, [&]() { return bufferReady == false; }); - readCount += sharedData->m_readCount; + readSize = unzip.read(sharedBuffer.get(), SIZE_UNZIP_BUFFER); + localRead = readSize; - sharedData->m_bufferIsFull = true; - sharedData->m_bufferCondition.notify_one(); - - std::unique_lock bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull == false; }); + bufferReady = true; + condition.notify_one(); + } + if (localRead == -1) { break; } + i += localRead; } } -void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, sys::ProgressTask *task) +void fs::copy_directory_to_zip(const fslib::Path &source, fs::MiniZip &dest, sys::ProgressTask *task) { - fslib::Directory sourceDir(source); - if (!sourceDir) - { - logger::log("Error opening source directory: %s", fslib::error::get_string()); - return; - } + const char *ioStatus = strings::get_by_name(strings::names::IO_STATUSES, 1); - // Grab this here instead of calling the config function for every file. - int compressionLevel = config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL); + fslib::Directory sourceDir{source}; + if (error::fslib(sourceDir.is_open())) { return; } - for (int64_t i = 0; i < sourceDir.get_count(); i++) + const int64_t dirCount = sourceDir.get_count(); + for (int64_t i = 0; i < dirCount; i++) { - if (sourceDir.is_directory(i)) - { - fslib::Path newSource = source / sourceDir[i]; - fs::copy_directory_to_zip(newSource, destination, task); - } + const fslib::Path fullSource{source / sourceDir[i]}; + if (sourceDir.is_directory(i)) { fs::copy_directory_to_zip(fullSource, dest, task); } else { - // Open source file. - fslib::Path fullSource = source / sourceDir[i]; - fslib::File sourceFile(fullSource, FsOpenMode_Read); - if (!sourceFile) - { - logger::log("Error zipping file: %s", fslib::error::get_string()); - continue; - } + fslib::File sourceFile{fullSource, FsOpenMode_Read}; + const bool newZipFile = dest.open_new_file(fullSource.full_path()); + if (error::fslib(sourceFile.is_open()) || !newZipFile) { continue; } - // Zip info - zip_fileinfo fileInfo; - fs::create_zip_fileinfo(fileInfo); + 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); - // Create new file in zip - const char *zipNameBegin = std::strchr(fullSource.get_path(), '/') + 1; - int zipError = zipOpenNewFileInZip64(destination, - zipNameBegin, - &fileInfo, - NULL, - 0, - NULL, - 0, - NULL, - Z_DEFLATED, - compressionLevel, - 0); - if (zipError != ZIP_OK) - { - logger::log("Error creating file in zip: %i.", zipError); - continue; - } - - // Shared data for thread. - std::shared_ptr sharedData(new ZipIOStruct); - sharedData->m_sharedBuffer = std::make_unique(SIZE_ZIP_BUFFER); - - // Local buffer for writing. - std::unique_ptr localBuffer(new unsigned char[SIZE_ZIP_BUFFER]); - - // Update task if passed. if (task) { - task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 1), fullSource.full_path()); - task->reset(static_cast(sourceFile.get_size())); + const std::string status = stringutil::get_formatted_string(ioStatus, fullSource.full_path()); + task->set_status(status); + task->reset(static_cast(fileSize)); } - // To do: Thread pool to avoid spawning threads like this. + // I just like doing this. It makes things easier to type. + std::mutex &lock = sharedData->lock; + std::condition_variable &condition = sharedData->condition; + ssize_t &readSize = sharedData->readSize; + bool &bufferReady = sharedData->bufferReady; + auto &sharedBuffer = sharedData->sharedBuffer; + std::thread readThread(zipReadThreadFunction, std::ref(sourceFile), sharedData); - - int64_t fileSize = sourceFile.get_size(); - for (int64_t writeCount = 0, readCount = 0; writeCount < fileSize;) + for (int64_t i = 0; i < fileSize;) { + ssize_t localRead{}; { - // Wait for buffer signal - std::unique_lock m_bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(m_bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull; }); + std::unique_lock bufferLock(lock); + condition.wait(bufferLock, [&]() { return bufferReady == true; }); - // Save read count, copy shared to local. - readCount = sharedData->m_readCount; - std::memcpy(localBuffer.get(), sharedData->m_sharedBuffer.get(), readCount); + localRead = readSize; - // Signal copy was good and release lock. - sharedData->m_bufferIsFull = false; - sharedData->m_bufferCondition.notify_one(); + std::memcpy(localBuffer.get(), sharedBuffer.get(), localRead); + + bufferReady = false; + condition.notify_one(); } + const bool writeGood = localRead != -1 && dest.write(localBuffer.get(), localRead); + if (!writeGood) { break; } - // Write - zipError = zipWriteInFileInZip(destination, localBuffer.get(), readCount); - if (zipError != ZIP_OK) { logger::log("Error writing data to zip: %i.", zipError); } + i += localRead; - // Update count and status - writeCount += readCount; - if (task) { task->update_current(static_cast(writeCount)); } + if (task) { task->update_current(static_cast(i)); } } - // Wait for thread + + dest.close_current_file(); readThread.join(); - // Close file in zip - zipCloseFileInZip(destination); } } } -void fs::copy_zip_to_directory(unzFile source, - const fslib::Path &destination, +void fs::copy_zip_to_directory(fs::MiniUnzip &unzip, + const fslib::Path &dest, uint64_t journalSize, std::string_view commitDevice, sys::ProgressTask *task) { - // With the new save meta, this might never fail... Need to figure this out some time. - int zipError = unzGoToFirstFile(source); - if (zipError != UNZ_OK) - { - logger::log("Error unzipping file: Zip is empty!"); - return; - } + if (!unzip.reset()) { return; } + const int popTicks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + const char *popCommitFailed = strings::get_by_name(strings::names::IO_POPS, 0); + const char *statusTemplate = strings::get_by_name(strings::names::IO_STATUSES, 2); + const bool needCommits = journalSize > 0 && !commitDevice.empty(); do { - // Get file information. - unz_file_info64 currentFileInfo; - char filename[FS_MAX_PATH] = {0}; + if (unzip.get_filename() == fs::NAME_SAVE_META) { continue; } - if (unzGetCurrentFileInfo64(source, ¤tFileInfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK || - unzOpenCurrentFile(source) != UNZ_OK) - { - logger::log("Error getting information for or opening file for reading in zip!"); - continue; - } + fslib::Path fullDest{dest / unzip.get_filename()}; + const size_t lastDir = fullDest.find_last_of('/'); + if (lastDir == fullDest.NOT_FOUND) { continue; } - // Save meta file filter. - if (filename == fs::NAME_SAVE_META) { continue; } + const fslib::Path dirPath{fullDest.sub_path(lastDir)}; + const bool dirExists = fslib::directory_exists(dirPath); + const bool dirFailed = !dirExists && dirPath.is_valid() && error::fslib(fslib::create_directories_recursively(dirPath)); + if (!dirExists && dirFailed) { continue; } - // Create full path to item, make sure directories are created if needed. - fslib::Path fullDestination = destination / filename; + const int64_t fileSize = unzip.get_uncompressed_size(); + fslib::File destFile{fullDest, FsOpenMode_Create | FsOpenMode_Write, fileSize}; + if (!destFile.is_open()) { return; } - fslib::Path directories = fullDestination.sub_path(fullDestination.find_last_of('/')); - - // To do: Make FsLib handle this correctly. First condition is a workaround for now... - if (directories.is_valid() && !fslib::create_directories_recursively(directories)) - { - logger::log("Error creating zip file path \"%s\": %s", directories.full_path(), fslib::error::get_string()); - continue; - } - - fslib::File destinationFile(fullDestination, FsOpenMode_Create | FsOpenMode_Write, currentFileInfo.uncompressed_size); - if (!destinationFile) - { - logger::log("Error creating file from zip: %s", fslib::error::get_string()); - continue; - } - - // Shared data for both threads - std::shared_ptr sharedData(new ZipIOStruct); - sharedData->m_sharedBuffer = std::make_unique(SIZE_UNZIP_BUFFER); - - // Local buffer - std::unique_ptr localBuffer(new unsigned char[SIZE_UNZIP_BUFFER]); - - // Spawn read thread. - std::thread readThread(unzipReadThreadFunction, source, currentFileInfo.uncompressed_size, sharedData); - - // Set status if (task) { - task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 2), filename); - task->reset(static_cast(currentFileInfo.uncompressed_size)); + const std::string status = stringutil::get_formatted_string(statusTemplate, unzip.get_filename()); + task->set_status(status); + task->reset(static_cast(fileSize)); } - for (int64_t writeCount = 0, readCount = 0, journalCount = 0; - writeCount < static_cast(currentFileInfo.uncompressed_size);) + auto sharedData = std::make_shared(); + 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::thread readThread(unzipReadThreadFunction, std::ref(unzip), sharedData); + int64_t journalCount{}; + for (int64_t i = 0; i < fileSize;) { + ssize_t localRead{}; { - // Wait for buffer. - std::unique_lock bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull; }); + std::unique_lock bufferLock(lock); + condition.wait(bufferLock, [&]() { return bufferReady == true; }); - // Save read count for later - readCount = sharedData->m_readCount; + localRead = readSize; + std::memcpy(localBuffer.get(), sharedBuffer.get(), localRead); - // Copy shared to local - std::memcpy(localBuffer.get(), sharedData->m_sharedBuffer.get(), readCount); - - // Signal this thread is done. - sharedData->m_bufferIsFull = false; - sharedData->m_bufferCondition.notify_one(); + bufferReady = false; + condition.notify_one(); } - // Journaling check - if (journalCount + readCount >= static_cast(journalSize)) + const bool commitNeeded = needCommits && journalCount + localRead >= journalSize; + if (commitNeeded) { - // Close. - destinationFile.close(); + destFile.close(); + const bool commitError = error::fslib(fslib::commit_data_to_file_system(commitDevice)); + if (commitError) { ui::PopMessageManager::push_message(popTicks, popCommitFailed); } // To do: How to recover? - // Commit - if (!fslib::commit_data_to_file_system(commitDevice)) - { - logger::log("Error committing data to save: %s", fslib::error::get_string()); - } - - // Reopen, seek to previous position. - destinationFile.open(fullDestination, FsOpenMode_Write); - destinationFile.seek(writeCount, destinationFile.BEGINNING); - - // Reset journal + destFile.open(fullDest, FsOpenMode_Write); + destFile.seek(i, destFile.BEGINNING); journalCount = 0; } - // Write data. - destinationFile.write(localBuffer.get(), readCount); + // To do: Same as above. + const bool goodWrite = localRead != -1 && destFile.write(localBuffer.get(), localRead); - // Update write and journal count - writeCount += readCount; - journalCount += readCount; - - // Update status - if (task) { task->update_current(writeCount); } + i += localRead; + journalCount += localRead; + if (task) { task->update_current(static_cast(i)); } } - - // Join the read thread. readThread.join(); + destFile.close(); - // Close file and commit again just for good measure. - destinationFile.close(); - - if (!fslib::commit_data_to_file_system(commitDevice)) - { - logger::log("Error performing final file commit: %s", fslib::error::get_string()); - } - } while (unzGoToNextFile(source) != UNZ_END_OF_LIST_OF_FILE); -} - -void fs::create_zip_fileinfo(zip_fileinfo &info) -{ - // Grab the current time. - std::time_t currentTime = std::time(NULL); - - // Get the local time. - std::tm *localTime = std::localtime(¤tTime); - - // Create struct to return. - info = {.tmz_date = {.tm_sec = localTime->tm_sec, - .tm_min = localTime->tm_min, - .tm_hour = localTime->tm_hour, - .tm_mday = localTime->tm_mday, - .tm_mon = localTime->tm_mon, - .tm_year = localTime->tm_year + 1900}, - .dosDate = 0, - .internal_fa = 0, - .external_fa = 0}; + const bool commitError = needCommits && error::fslib(fslib::commit_data_to_file_system(commitDevice)); + if (commitError) { ui::PopMessageManager::push_message(popTicks, popCommitFailed); } + } while (unzip.next_file()); } bool fs::zip_has_contents(const fslib::Path &zipPath) { - unzFile testZip = unzOpen(zipPath.full_path()); - if (!testZip) { return false; } + fs::MiniUnzip unzip{zipPath}; + if (!unzip.is_open()) { return false; } - int zipError = unzGoToFirstFile(testZip); - if (zipError != UNZ_OK) - { - unzClose(testZip); - return false; - } - unzClose(testZip); - return true; -} - -bool fs::locate_file_in_zip(unzFile zip, std::string_view name) -{ - // Go to the first file, first. - int zipError = unzGoToFirstFile(zip); - if (zipError != UNZ_OK) - { - logger::log("Error locating file: Zip is empty!"); - return false; - } - - // This should be a large enough buffer. - char filename[FS_MAX_PATH] = {0}; - // File info. - unz_file_info64 fileinfo = {0}; - - // Loop through files. If minizip has a better way of doing this, I couldn't find it. do { - // Grab this stuff. - zipError = unzGetCurrentFileInfo64(zip, &fileinfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0); - if (zipError != UNZ_OK) { continue; } - else if (filename == name) { return true; } - } while (unzGoToNextFile(zip) != UNZ_END_OF_LIST_OF_FILE); - - // Guess it wasn't found? + if (unzip.get_filename() != fs::NAME_SAVE_META) { return true; } + } while (unzip.next_file()); return false; } - -uint64_t fs::get_zip_total_size(unzFile zip) -{ - // First, first. - int zipError = unzGoToFirstFile(zip); - if (zipError != UNZ_OK) - { - logger::log("Error getting total zip file size: %i.", zipError); - return 0; - } - - // Size. - uint64_t zipSize = 0; - - // File's name and info buffers. - char filename[FS_MAX_PATH] = {0}; - unz_file_info64 fileinfo = {0}; - - do { - zipError = unzGetCurrentFileInfo64(zip, &fileinfo, filename, 0, NULL, 0, NULL, 0); - if (zipError != UNZ_OK) { continue; } - - // Add - zipSize += fileinfo.uncompressed_size; - } while (unzGoToNextFile(zip) != UNZ_END_OF_LIST_OF_FILE); - - // Reset. Maybe this should be error checked, but I don't see the point here? - unzGoToFirstFile(zip); - - return zipSize; -} diff --git a/source/gfxutil.cpp b/source/gfxutil.cpp index edf877a..ae53f74 100644 --- a/source/gfxutil.cpp +++ b/source/gfxutil.cpp @@ -9,7 +9,6 @@ namespace constexpr int SIZE_ICON_HEIGHT = 256; } // namespace - sdl::SharedTexture gfxutil::create_generic_icon(std::string_view text, int fontSize, sdl::Color background, diff --git a/source/keyboard.cpp b/source/keyboard.cpp index 9d65034..a7a4bee 100644 --- a/source/keyboard.cpp +++ b/source/keyboard.cpp @@ -1,4 +1,5 @@ #include "keyboard.hpp" + #include bool keyboard::get_input(SwkbdType keyboardType, @@ -16,20 +17,13 @@ bool keyboard::get_input(SwkbdType keyboardType, swkbdConfigSetGuideText(&keyboard, header.data()); swkbdConfigSetType(&keyboard, keyboardType); swkbdConfigSetStringLenMax(&keyboard, stringLength); - swkbdConfigSetKeySetDisableBitmask(&keyboard, - SwkbdKeyDisableBitmask_ForwardSlash | SwkbdKeyDisableBitmask_Backslash); + swkbdConfigSetKeySetDisableBitmask(&keyboard, SwkbdKeyDisableBitmask_ForwardSlash | SwkbdKeyDisableBitmask_Backslash); // If it fails, just return. - if (R_FAILED(swkbdShow(&keyboard, stringOut, stringLength))) - { - return false; - } + if (R_FAILED(swkbdShow(&keyboard, stringOut, stringLength))) { return false; } // If the string is empty, assume failure or cancel. - if (std::char_traits::length(stringOut) == 0) - { - return false; - } + if (std::char_traits::length(stringOut) == 0) { return false; } // I wish this was more like the 3DS keyboard cause that actually returned what button was pressed... return true; diff --git a/source/logger.cpp b/source/logger.cpp index e4a4b3d..e6e93b6 100644 --- a/source/logger.cpp +++ b/source/logger.cpp @@ -1,12 +1,17 @@ #include "logger.hpp" + #include "config.hpp" #include "fslib.hpp" + #include +#include namespace { /// @brief This is the path to the log file. - fslib::Path s_logFilePath; + fslib::Path s_logFilePath{}; + + std::mutex s_logLock{}; /// @brief This is the buffer size for log strings. constexpr size_t VA_BUFFER_SIZE = 0x1000; @@ -30,9 +35,8 @@ void logger::log(const char *format, ...) vsnprintf(vaBuffer, VA_BUFFER_SIZE, format, vaList); va_end(vaList); + std::scoped_lock logLock(s_logLock); fslib::File logFile(s_logFilePath, FsOpenMode_Append); logFile << vaBuffer << "\n"; - - // Always flush to guarantee output. logFile.flush(); } diff --git a/source/main.cpp b/source/main.cpp index 16b7104..b2f5a7b 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,5 +1,6 @@ #include "JKSV.hpp" #include "config.hpp" + #include int main() diff --git a/source/remote/Form.cpp b/source/remote/Form.cpp index f82fa13..5d0511e 100644 --- a/source/remote/Form.cpp +++ b/source/remote/Form.cpp @@ -1,9 +1,6 @@ #include "remote/Form.hpp" -remote::Form::Form(const remote::Form &form) -{ - m_form = form.m_form; -} +remote::Form::Form(const remote::Form &form) { m_form = form.m_form; } remote::Form::Form(remote::Form &&form) { @@ -26,20 +23,11 @@ remote::Form &remote::Form::operator=(remote::Form &&form) remote::Form &remote::Form::append_parameter(std::string_view param, std::string_view value) { - if (!m_form.empty() && m_form.back() != '&') - { - m_form.append("&"); - } + if (!m_form.empty() && m_form.back() != '&') { m_form.append("&"); } m_form.append(param).append("=").append(value); return *this; } -const char *remote::Form::get() const -{ - return m_form.c_str(); -} +const char *remote::Form::get() const { return m_form.c_str(); } -size_t remote::Form::length() const -{ - return m_form.length(); -} +size_t remote::Form::length() const { return m_form.length(); } diff --git a/source/remote/GoogleDrive.cpp b/source/remote/GoogleDrive.cpp index 744b6b4..611bf5a 100644 --- a/source/remote/GoogleDrive.cpp +++ b/source/remote/GoogleDrive.cpp @@ -46,16 +46,10 @@ namespace } // namespace remote::GoogleDrive::GoogleDrive() - : Storage() + : Storage("[GD]", true) { static const char *STRING_ERROR_READING_CONFIG = "Error reading Google Drive config: %s"; - // Google Drive doesn't really use directories, so UTF-8 is fine! - m_utf8Paths = true; - - // Google Drive menu prefix. - m_prefix = "[GD] "; - // Load the json file. json::Object clientJson = json::new_object(json_object_from_file, remote::PATH_GOOGLE_DRIVE_CONFIG.data()); if (!clientJson) @@ -149,9 +143,10 @@ bool remote::GoogleDrive::create_directory(std::string_view name) return true; } -bool remote::GoogleDrive::upload_file(const fslib::Path &source) +bool remote::GoogleDrive::upload_file(const fslib::Path &source, sys::ProgressTask *task) { if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } + const char *statusTemplate = strings::get_by_name(strings::names::BACKUPMENU_STATUS, 1); fslib::File sourceFile(source, FsOpenMode_Read); if (!sourceFile) @@ -172,7 +167,6 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) json::Object postJson = json::new_object(json_object_new_object); json_object *driveName = json_object_new_string(source.get_filename()); json::add_object(postJson, JSON_KEY_NAME, driveName); - // Append the parent. if (!m_parent.empty()) { json_object *parentArray = json_object_new_array(); @@ -181,9 +175,7 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) json::add_object(postJson, JSON_KEY_PARENTS, parentArray); } - // This requires reading a header to get the upload location. curl::HeaderArray headerArray; - curl::prepare_post(m_curl); curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); curl::set_option(m_curl, CURLOPT_HEADERFUNCTION, curl::write_header_array); @@ -201,12 +193,20 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } + if (task) + { + std::string status = stringutil::get_formatted_string(statusTemplate, source.full_path()); + task->set_status(status); + task->reset(static_cast(sourceFile.get_size())); + } + std::string response; + curl::UploadStruct uploadData = {.source = &sourceFile, .task = task}; // This is the actual upload. This doesn't need the authentication header to work for some reason? curl::prepare_upload(m_curl); curl::set_option(m_curl, CURLOPT_URL, location.c_str()); curl::set_option(m_curl, CURLOPT_READFUNCTION, curl::read_data_from_file); - curl::set_option(m_curl, CURLOPT_READDATA, &sourceFile); + curl::set_option(m_curl, CURLOPT_READDATA, &uploadData); curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); @@ -240,11 +240,12 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return true; } -bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &source) +bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &source, sys::ProgressTask *task) { static const char *STRING_PATCH_ERROR = "Error patching file: %s"; if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } + const char *statusTemplate = strings::get_by_name(strings::names::BACKUPMENU_STATUS, 2); fslib::File sourceFile(source, FsOpenMode_Read); if (!sourceFile) @@ -280,11 +281,20 @@ bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &sour return false; } + if (task) + { + const std::string status = stringutil::get_formatted_string(statusTemplate, source.full_path()); + task->set_status(status); + task->reset(static_cast(sourceFile.get_size())); + } + // For some reason, this doesn't need the auth header. + curl::UploadStruct uploadData = {.source = &sourceFile, .task = task}; + curl::prepare_upload(m_curl); curl::set_option(m_curl, CURLOPT_URL, location.c_str()); curl::set_option(m_curl, CURLOPT_READFUNCTION, curl::read_data_from_file); - curl::set_option(m_curl, CURLOPT_READDATA, &sourceFile); + curl::set_option(m_curl, CURLOPT_READDATA, &uploadData); if (!curl::perform(m_curl)) { return false; } diff --git a/source/remote/Item.cpp b/source/remote/Item.cpp index a7b651f..b2c8348 100644 --- a/source/remote/Item.cpp +++ b/source/remote/Item.cpp @@ -3,54 +3,28 @@ #include "logger.hpp" remote::Item::Item(std::string_view name, std::string_view id, std::string_view parent, size_t size, bool directory) - : m_name(name), m_id(id), m_parent(parent), m_size(size), m_isDirectory(directory) {}; + : m_name(name) + , m_id(id) + , m_parent(parent) + , m_size(size) + , m_isDirectory(directory) {}; -std::string_view remote::Item::get_name() const -{ - return m_name; -} +std::string_view remote::Item::get_name() const { return m_name; } -std::string_view remote::Item::get_id() const -{ - return m_id; -} +std::string_view remote::Item::get_id() const { return m_id; } -std::string_view remote::Item::get_parent_id() const -{ - return m_parent; -} +std::string_view remote::Item::get_parent_id() const { return m_parent; } -size_t remote::Item::get_size() const -{ - return m_size; -} +size_t remote::Item::get_size() const { return m_size; } -bool remote::Item::is_directory() const -{ - return m_isDirectory; -} +bool remote::Item::is_directory() const { return m_isDirectory; } -void remote::Item::set_name(std::string_view name) -{ - m_name = name; -} +void remote::Item::set_name(std::string_view name) { m_name = name; } -void remote::Item::set_id(std::string_view id) -{ - m_id = id; -} +void remote::Item::set_id(std::string_view id) { m_id = id; } -void remote::Item::set_parent_id(std::string_view parent) -{ - m_parent = parent; -} +void remote::Item::set_parent_id(std::string_view parent) { m_parent = parent; } -void remote::Item::set_size(size_t size) -{ - m_size = size; -} +void remote::Item::set_size(size_t size) { m_size = size; } -void remote::Item::set_is_directory(bool directory) -{ - m_isDirectory = directory; -} +void remote::Item::set_is_directory(bool directory) { m_isDirectory = directory; } diff --git a/source/remote/Storage.cpp b/source/remote/Storage.cpp index 2901f7e..fb64b08 100644 --- a/source/remote/Storage.cpp +++ b/source/remote/Storage.cpp @@ -1,41 +1,27 @@ #include "remote/Storage.hpp" + #include // Declarations here. Defined at bottom. -remote::Storage::Storage() : m_curl(curl::new_handle()) {}; +remote::Storage::Storage(std::string_view prefix, bool supportsUtf8) + : m_curl(curl::new_handle()) + , m_utf8Paths(supportsUtf8) + , m_prefix(prefix) {}; -bool remote::Storage::is_initialized() const -{ - return m_isInitialized; -} +bool remote::Storage::is_initialized() const { return m_isInitialized; } -bool remote::Storage::directory_exists(std::string_view name) -{ - return Storage::find_directory_by_name(name) != m_list.end(); -} +bool remote::Storage::directory_exists(std::string_view name) { return Storage::find_directory_by_name(name) != m_list.end(); } -void remote::Storage::return_to_root() -{ - m_parent = m_root; -} +void remote::Storage::return_to_root() { m_parent = m_root; } -void remote::Storage::set_root_directory(remote::Item *root) -{ - m_root = root->get_id(); -} +void remote::Storage::set_root_directory(const remote::Item *root) { m_root = root->get_id(); } -void remote::Storage::change_directory(remote::Item *item) -{ - m_parent = item->get_id(); -} +void remote::Storage::change_directory(const remote::Item *item) { m_parent = item->get_id(); } remote::Item *remote::Storage::get_directory_by_name(std::string_view name) { auto findDirectory = Storage::find_directory_by_name(name); - if (findDirectory == m_list.end()) - { - return nullptr; - } + if (findDirectory == m_list.end()) { return nullptr; } return &(*findDirectory); } @@ -44,51 +30,42 @@ remote::Storage::DirectoryListing remote::Storage::get_directory_listing() remote::Storage::DirectoryListing listing; Storage::List::iterator current = m_list.begin(); - while ((current = std::find_if(current, m_list.end(), [this](const Item &item) { - return item.get_parent_id() == this->m_parent; - })) != m_list.end()) + while ((current = std::find_if(current, + m_list.end(), + [&](const Item &item) { return item.get_parent_id() == this->m_parent; })) != m_list.end()) { listing.push_back(&(*current)); + ++current; } return listing; } -bool remote::Storage::file_exists(std::string_view name) -{ - return Storage::find_file_by_name(name) != m_list.end(); -} +bool remote::Storage::file_exists(std::string_view name) { return Storage::find_file_by_name(name) != m_list.end(); } remote::Item *remote::Storage::get_file_by_name(std::string_view name) { auto findFile = Storage::find_file_by_name(name); - if (findFile == m_list.end()) - { - return nullptr; - } + if (findFile == m_list.end()) { return nullptr; } return &(*findFile); } -bool remote::Storage::supports_utf8() const -{ - return m_utf8Paths; -} +bool remote::Storage::supports_utf8() const { return m_utf8Paths; } -std::string_view remote::Storage::get_prefix() const -{ - return m_prefix; -} +std::string_view remote::Storage::get_prefix() const { return m_prefix; } remote::Storage::List::iterator remote::Storage::find_directory_by_name(std::string_view name) { - return std::find_if(m_list.begin(), m_list.end(), [name, this](const Item &item) { - return item.is_directory() && item.get_parent_id() == this->m_parent && item.get_name() == name; - }); + return std::find_if(m_list.begin(), + m_list.end(), + [&](const Item &item) + { return item.is_directory() && item.get_parent_id() == this->m_parent && item.get_name() == name; }); } remote::Storage::List::iterator remote::Storage::find_file_by_name(std::string_view name) { - return std::find_if(m_list.begin(), m_list.end(), [name, this](const Item &item) { - return !item.is_directory() && item.get_parent_id() == this->m_parent && item.get_name() == name; - }); + return std::find_if(m_list.begin(), + m_list.end(), + [&](const Item &item) + { return !item.is_directory() && item.get_parent_id() == this->m_parent && item.get_name() == name; }); } diff --git a/source/remote/URL.cpp b/source/remote/URL.cpp index c6aa917..bf37a69 100644 --- a/source/remote/URL.cpp +++ b/source/remote/URL.cpp @@ -1,11 +1,9 @@ #include "remote/URL.hpp" -remote::URL::URL(std::string_view base) : m_url(base) {}; +remote::URL::URL(std::string_view base) + : m_url(base) {}; -remote::URL::URL(const URL &url) -{ - m_url = url.m_url; -} +remote::URL::URL(const URL &url) { m_url = url.m_url; } remote::URL::URL(URL &&url) { @@ -40,16 +38,10 @@ remote::URL &remote::URL::set_base(std::string_view base) remote::URL &remote::URL::append_path(std::string_view path) { // Check both just to be sure because this makes WebDav easier to tackle. - if (m_url.back() != '/' && path.front() != '/') - { - m_url.append("/"); - } + if (m_url.back() != '/' && path.front() != '/') { m_url.append("/"); } // This is here to make WebDav easier to read and deal with in case of blank basepaths. - if (path.empty()) - { - return *this; - } + if (path.empty()) { return *this; } m_url.append(path); @@ -65,26 +57,14 @@ remote::URL &remote::URL::append_parameter(std::string_view param, std::string_v remote::URL &remote::URL::append_slash() { - if (m_url.back() != '/') - { - m_url.append("/"); - } + if (m_url.back() != '/') { m_url.append("/"); } return *this; } -const char *remote::URL::get() const -{ - return m_url.c_str(); -} +const char *remote::URL::get() const { return m_url.c_str(); } void remote::URL::append_separator() { - if (m_url.find('?') == m_url.npos) - { - m_url.append("?"); - } - else - { - m_url.append("&"); - } + if (m_url.find('?') == m_url.npos) { m_url.append("?"); } + else { m_url.append("&"); } } diff --git a/source/remote/WebDav.cpp b/source/remote/WebDav.cpp index 14e4b91..ca9ce89 100644 --- a/source/remote/WebDav.cpp +++ b/source/remote/WebDav.cpp @@ -1,9 +1,11 @@ #include "remote/WebDav.hpp" + #include "JSON.hpp" #include "curl/curl.hpp" #include "logger.hpp" #include "remote/remote.hpp" #include "stringutil.hpp" + #include namespace @@ -27,16 +29,11 @@ static std::string_view get_tag_begin(std::string_view tag); /// @note This seemed like a better alternative than relying on servers having displayname. static std::string slice_name_from_href(curl::Handle &handle, std::string_view href); -remote::WebDav::WebDav() : Storage() +remote::WebDav::WebDav() + : Storage("[WD]") { static const char *STRING_CONFIG_READ_ERROR = "Error initializing WebDav: %s"; - // WebDav has problems with these. - m_utf8Paths = false; - - // WebDav prefix for menus. - m_prefix = "[WD] "; - json::Object config = json::new_object(json_object_from_file, remote::PATH_WEBDAV_CONFIG.data()); if (!config) { @@ -45,7 +42,7 @@ remote::WebDav::WebDav() : Storage() } // Let's just get this all out of the way at once. - json_object *origin = json::get_object(config, "origin"); + json_object *origin = json::get_object(config, "origin"); json_object *basepath = json::get_object(config, "basepath"); json_object *username = json::get_object(config, "username"); json_object *password = json::get_object(config, "password"); @@ -62,19 +59,13 @@ remote::WebDav::WebDav() : Storage() { // The root is both in the beginning. I want this to work as closely as the original just not as poorly written // or thought out as the original JKSV dav code. - m_root = stringutil::get_formatted_string("/%s/", json_object_get_string(basepath)); + m_root = stringutil::get_formatted_string("/%s/", json_object_get_string(basepath)); m_parent = m_root; } - if (username) - { - m_username = json_object_get_string(username); - } + if (username) { m_username = json_object_get_string(username); } - if (password) - { - m_password = json_object_get_string(password); - } + if (password) { m_password = json_object_get_string(password); } // This is the starting point. This will read the entire basepath listing in one go. remote::URL url{m_origin}; @@ -109,10 +100,7 @@ bool remote::WebDav::create_directory(std::string_view name) curl::set_option(m_curl, CURLOPT_URL, url.get()); curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "MKCOL"); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } if (curl::get_response_code(m_curl) != 201) { @@ -127,12 +115,12 @@ bool remote::WebDav::create_directory(std::string_view name) return true; } -bool remote::WebDav::upload_file(const fslib::Path &source) +bool remote::WebDav::upload_file(const fslib::Path &source, sys::ProgressTask *task) { static const char *STRING_ERROR_UPLOADING = "Error uploading file: %s"; - fslib::File file(source, FsOpenMode_Read); - if (!file) + fslib::File sourceFile{source, FsOpenMode_Read}; + if (!sourceFile.is_open()) { logger::log(STRING_ERROR_UPLOADING, fslib::error::get_string()); return false; @@ -148,25 +136,24 @@ bool remote::WebDav::upload_file(const fslib::Path &source) remote::URL url{m_origin}; url.append_path(m_parent).append_path(escapedName); + curl::UploadStruct uploadData = {.source = &sourceFile, .task = task}; + curl::reset_handle(m_curl); WebDav::append_credentials(); curl::set_option(m_curl, CURLOPT_URL, url.get()); curl::set_option(m_curl, CURLOPT_UPLOAD, 1L); curl::set_option(m_curl, CURLOPT_UPLOAD_BUFFERSIZE, Storage::SIZE_UPLOAD_BUFFER); curl::set_option(m_curl, CURLOPT_READFUNCTION, curl::read_data_from_file); - curl::set_option(m_curl, CURLOPT_READDATA, &file); + curl::set_option(m_curl, CURLOPT_READDATA, &uploadData); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } - m_list.emplace_back(source.get_filename(), escapedName, m_parent, file.get_size(), false); + m_list.emplace_back(source.get_filename(), escapedName, m_parent, sourceFile.get_size(), false); return true; } -bool remote::WebDav::patch_file(remote::Item *item, const fslib::Path &source) +bool remote::WebDav::patch_file(remote::Item *item, const fslib::Path &source, sys::ProgressTask *task) { static const char *STRING_ERROR_PATCHING = "Error patching file: %s"; @@ -177,7 +164,6 @@ bool remote::WebDav::patch_file(remote::Item *item, const fslib::Path &source) return false; } - remote::URL url{m_origin}; url.append_path(m_parent).append_path(item->get_id()); @@ -189,10 +175,7 @@ bool remote::WebDav::patch_file(remote::Item *item, const fslib::Path &source) curl::set_option(m_curl, CURLOPT_READFUNCTION, curl::read_data_from_file); curl::set_option(m_curl, CURLOPT_READDATA, &file); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } // Just update the size this time. item->set_size(file.get_size()); @@ -221,10 +204,7 @@ bool remote::WebDav::download_file(const remote::Item *item, const fslib::Path & curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_data_to_file); curl::set_option(m_curl, CURLOPT_WRITEDATA, &file); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } return true; } @@ -235,20 +215,14 @@ bool remote::WebDav::delete_item(const remote::Item *item) remote::URL url{m_origin}; url.append_path(m_parent).append_path(item->get_id()); - if (item->is_directory()) - { - url.append_slash(); - } + if (item->is_directory()) { url.append_slash(); } curl::reset_handle(m_curl); WebDav::append_credentials(); curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); curl::set_option(m_curl, CURLOPT_URL, url.get()); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } if (curl::get_response_code(m_curl) != 204) { @@ -261,18 +235,11 @@ bool remote::WebDav::delete_item(const remote::Item *item) void remote::WebDav::append_credentials() { - if (!m_username.empty()) - { - curl::set_option(m_curl, CURLOPT_USERNAME, m_username.c_str()); - } + if (!m_username.empty()) { curl::set_option(m_curl, CURLOPT_USERNAME, m_username.c_str()); } - if (!m_password.empty()) - { - curl::set_option(m_curl, CURLOPT_PASSWORD, m_password.c_str()); - } + if (!m_password.empty()) { curl::set_option(m_curl, CURLOPT_PASSWORD, m_password.c_str()); } } - bool remote::WebDav::prop_find(const remote::URL &url, std::string &xml) { // Some servers block Depth: Infinity. @@ -315,17 +282,13 @@ bool remote::WebDav::process_listing(std::string_view xml) // There's no point in continuing if this fails. Just return true. tinyxml2::XMLElement *current = parent->NextSiblingElement(); - if (!current) - { - return true; - } + if (!current) { return true; } - do - { + do { // Parsing XML is actually annoying. Even with tinyxml2. - tinyxml2::XMLElement *href = get_element_by_name(current, TAG_XML_HREF); - tinyxml2::XMLElement *propstat = get_element_by_name(current, "propstat"); - tinyxml2::XMLElement *prop = get_element_by_name(propstat, "prop"); + tinyxml2::XMLElement *href = get_element_by_name(current, TAG_XML_HREF); + tinyxml2::XMLElement *propstat = get_element_by_name(current, "propstat"); + tinyxml2::XMLElement *prop = get_element_by_name(propstat, "prop"); tinyxml2::XMLElement *resourceType = get_element_by_name(prop, "resourcetype"); if (!href || !propstat || !prop || !resourceType) { @@ -375,17 +338,10 @@ bool remote::WebDav::process_listing(std::string_view xml) static tinyxml2::XMLElement *get_element_by_name(tinyxml2::XMLElement *parent, std::string_view name) { tinyxml2::XMLElement *current = parent->FirstChildElement(); - if (!current) - { - return nullptr; - } + if (!current) { return nullptr; } - do - { - if (get_tag_begin(current->Name()) == name) - { - return current; - } + do { + if (get_tag_begin(current->Name()) == name) { return current; } } while ((current = current->NextSiblingElement())); return nullptr; } @@ -393,10 +349,7 @@ static tinyxml2::XMLElement *get_element_by_name(tinyxml2::XMLElement *parent, s static std::string_view get_tag_begin(std::string_view tag) { size_t colon = tag.find_first_of(':'); - if (colon == tag.npos) - { - return tag; - } + if (colon == tag.npos) { return tag; } return tag.substr(colon + 1); } @@ -407,7 +360,7 @@ static std::string slice_name_from_href(curl::Handle &handle, std::string_view h // This means we're working with a directory. if (href.back() == '/') { - size_t end = href.find_last_of('/'); + size_t end = href.find_last_of('/'); size_t begin = href.find_last_of('/', end - 1); if (end == href.npos || begin == href.npos) { @@ -421,14 +374,8 @@ static std::string slice_name_from_href(curl::Handle &handle, std::string_view h { // File size_t begin = href.find_last_of('/'); - if (begin == href.npos) - { - name = href; - } - else - { - name = href.substr(begin + 1); - } + if (begin == href.npos) { name = href; } + else { name = href.substr(begin + 1); } } curl::unescape_string(handle, name, name); diff --git a/source/stringutil.cpp b/source/stringutil.cpp index 1d9abe0..dfc9c6d 100644 --- a/source/stringutil.cpp +++ b/source/stringutil.cpp @@ -1,4 +1,5 @@ #include "stringutil.hpp" + #include #include #include @@ -47,15 +48,12 @@ void stringutil::strip_character(char c, std::string &target) bool stringutil::sanitize_string_for_path(const char *stringIn, char *stringOut, size_t stringOutSize) { - uint32_t codepoint = 0; + uint32_t codepoint = 0; size_t stringLength = std::strlen(stringIn); for (size_t i = 0, stringOutOffset = 0; i < stringLength;) { ssize_t unitCount = decode_utf8(&codepoint, reinterpret_cast(&stringIn[i])); - if (unitCount <= 0 || i + unitCount >= stringOutSize) - { - break; - } + if (unitCount <= 0 || i + unitCount >= stringOutSize) { break; } if (codepoint < 0x20 || codepoint > 0x7E) { @@ -69,13 +67,11 @@ bool stringutil::sanitize_string_for_path(const char *stringIn, char *stringOut, { stringOut[stringOutOffset++] = 0x20; } - else if (codepoint == L'é') - { - stringOut[stringOutOffset++] = 'e'; - } + else if (codepoint == L'é') { stringOut[stringOutOffset++] = 'e'; } else { - // Just memcpy it over. This is a safety thing to be honest. Since it's only Ascii allowed, unitcount should only be 1. + // Just memcpy it over. This is a safety thing to be honest. Since it's only Ascii allowed, unitcount should only + // be 1. std::memcpy(&stringOut[stringOutOffset], &stringIn[i], static_cast(unitCount)); stringOutOffset += unitCount; } diff --git a/source/system/ProgressTask.cpp b/source/system/ProgressTask.cpp index bc5271e..8e07614 100644 --- a/source/system/ProgressTask.cpp +++ b/source/system/ProgressTask.cpp @@ -3,20 +3,15 @@ void sys::ProgressTask::reset(double goal) { m_current = 0; - m_goal = goal; + m_goal = goal; } -void sys::ProgressTask::update_current(double current) -{ - m_current = current; -} +void sys::ProgressTask::update_current(double current) { m_current = current; } -double sys::ProgressTask::get_goal() const -{ - return m_goal; -} +double sys::ProgressTask::get_goal() const { return m_goal; } double sys::ProgressTask::get_current() const { - return m_current / m_goal; + // Reminder: Never divide by zero. It ends badly every time! + return m_goal > 0 ? m_current / m_goal : 0; } diff --git a/source/system/Task.cpp b/source/system/Task.cpp index 1433343..5cb17ab 100644 --- a/source/system/Task.cpp +++ b/source/system/Task.cpp @@ -1,4 +1,5 @@ #include "system/Task.hpp" + #include namespace @@ -7,32 +8,16 @@ namespace constexpr size_t VA_BUFFER_SIZE = 0x1000; } // namespace -sys::Task::~Task() +sys::Task::~Task() { m_thread.join(); } + +bool sys::Task::is_running() const { return m_isRunning; } + +void sys::Task::finished() { m_isRunning = false; } + +void sys::Task::set_status(std::string_view status) { - m_thread.join(); -} - -bool sys::Task::is_running() const -{ - return m_isRunning; -} - -void sys::Task::finished() -{ - m_isRunning = false; -} - -void sys::Task::set_status(const char *format, ...) -{ - char vaBuffer[VA_BUFFER_SIZE] = {0}; - - std::va_list vaList; - va_start(vaList, format); - vsnprintf(vaBuffer, VA_BUFFER_SIZE, format, vaList); - va_end(vaList); - std::scoped_lock statusLock(m_statusLock); - m_status = vaBuffer; + m_status = status; } std::string sys::Task::get_status() diff --git a/source/system/Timer.cpp b/source/system/Timer.cpp index 1e04dd0..3cd252f 100644 --- a/source/system/Timer.cpp +++ b/source/system/Timer.cpp @@ -1,12 +1,10 @@ #include "system/Timer.hpp" -#include #include "logger.hpp" -sys::Timer::Timer(uint64_t triggerTicks) -{ - Timer::start(triggerTicks); -} +#include + +sys::Timer::Timer(uint64_t triggerTicks) { Timer::start(triggerTicks); } void sys::Timer::start(uint64_t triggerTicks) { @@ -22,10 +20,7 @@ bool sys::Timer::is_triggered() uint64_t currentTicks = SDL_GetTicks64(); // Nope - if (currentTicks - m_startingTicks < m_triggerTicks) - { - return false; - } + if (currentTicks - m_startingTicks < m_triggerTicks) { return false; } // Reset starting ticks. m_startingTicks = currentTicks; @@ -34,7 +29,4 @@ bool sys::Timer::is_triggered() return true; } -void sys::Timer::restart() -{ - m_startingTicks = SDL_GetTicks64(); -} +void sys::Timer::restart() { m_startingTicks = SDL_GetTicks64(); } diff --git a/source/tasks/backup.cpp b/source/tasks/backup.cpp index 68bfb1f..20a75e3 100644 --- a/source/tasks/backup.cpp +++ b/source/tasks/backup.cpp @@ -3,20 +3,33 @@ #include "config.hpp" #include "error.hpp" #include "fs/fs.hpp" +#include "logger.hpp" +#include "remote/remote.hpp" +#include "strings.hpp" +#include "stringutil.hpp" +#include "ui/PopMessageManager.hpp" #include +namespace +{ + constexpr const char *STRING_ZIP_EXT = ".zip"; +} + void tasks::backup::create_new_backup(sys::ProgressTask *task, data::User *user, data::TitleInfo *titleInfo, fslib::Path target, - BackupMenuState *spawningState) + BackupMenuState *spawningState, + bool killTask) { - const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP); - const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); - const bool hasZipExt = std::strstr(target.full_path(), ".zip"); - const const bool uint64_t applicationID = titleInfo->get_application_id(); - const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(applicationID); + if (error::is_null(task)) { return; } + static constexpr size_t SIZE_SAVE_META = sizeof(fs::SaveMetaData); + + const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); + 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(); @@ -25,4 +38,177 @@ void tasks::backup::create_new_backup(sys::ProgressTask *task, fs::SaveMetaData saveMeta{}; const bool hasValidMeta = fs::fill_save_meta_data(saveInfo, saveMeta); + 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; + } + + const bool openMeta = hasValidMeta && zip.open_new_file(fs::NAME_SAVE_META); + if (openMeta) + { + zip.write(&saveMeta, SIZE_SAVE_META); + zip.close_current_file(); + } + fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, zip, task); + } + else + { + if (hasValidMeta) + { + const fslib::Path saveMetaPath{target / fs::NAME_SAVE_META}; + fslib::File saveMetaFile{saveMetaPath, FsOpenMode_Create | FsOpenMode_Write, SIZE_SAVE_META}; + if (saveMetaFile.is_open()) { saveMetaFile.write(&saveMeta, SIZE_SAVE_META); } + } + fs::copy_directory(fs::DEFAULT_SAVE_ROOT, target, task); + } + spawningState->refresh(); + if (killTask) { task->finished(); } +} + +void tasks::backup::overwrite_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData) +{ + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + const fslib::Path &target = taskData->path; + BackupMenuState *spawningState = taskData->spawningState; + + 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) + { + task->finished(); + return; + } + create_new_backup(task, user, titleInfo, target, spawningState); +} + +void tasks::backup::restore_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData) +{ + static constexpr size_t SIZE_META = sizeof(fs::SaveMetaData); + + if (error::is_null(task)) { return; } + + data::User *user = taskData->user; + data::TitleInfo *titleInfo = taskData->titleInfo; + const fslib::Path &target = taskData->path; + BackupMenuState *spawningState = taskData->spawningState; + + 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); + + const bool autoBackup = config::get_by_key(config::keys::AUTO_BACKUP_ON_RESTORE); + const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP) || config::get_by_key(config::keys::AUTO_UPLOAD); + const bool isDir = fslib::directory_exists(target); + const bool hasZipExt = std::strstr(target.full_path(), STRING_ZIP_EXT); + + const int popTicks = ui::PopMessageManager::DEFAULT_MESSAGE_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); + + if (autoBackup) + { + fslib::Path autoTarget{}; + const size_t lastSlash = target.find_last_of('/'); + if (lastSlash == target.NOT_FOUND) + { + ui::PopMessageManager::push_message(popTicks, popErrorCreating); + task->finished(); + return; + } + + const char *safeNickname = user->get_path_safe_nickname(); + autoTarget = target.sub_path(lastSlash) / "AUTO - " + safeNickname + " - " + stringutil::get_date_string(); + if (exportZip) { autoTarget += ".zip"; }; + + tasks::backup::create_new_backup(task, user, titleInfo, autoTarget, spawningState, false); + } + + const bool resetError = error::fslib(fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT)); + const bool commitError = error::fslib(fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT)); + if (resetError || commitError) + { + ui::PopMessageManager::push_message(popTicks, popErrorResetting); + task->finished(); + return; + } + + if (!isDir && hasZipExt) + { + fs::MiniUnzip unzip{target}; + if (!unzip.is_open()) + { + ui::PopMessageManager::push_message(popTicks, popErrorOpenZip); + task->finished(); + return; + } + + { + fs::SaveMetaData metaData{}; + const bool hasMeta = unzip.locate_file(fs::NAME_SAVE_META); + const bool readMeta = hasMeta && unzip.read(&metaData, SIZE_META) == SIZE_META; + if (readMeta) { fs::process_save_meta_data(saveInfo, metaData); }; + } + // To do: Maybe use the meta instead of what the NACP has? + fs::copy_zip_to_directory(unzip, fs::DEFAULT_SAVE_ROOT, journalSize, fs::DEFAULT_SAVE_MOUNT, task); + } + else if (isDir) + { + { + fs::SaveMetaData metaData{}; + const fslib::Path metaPath{target / fs::NAME_SAVE_META}; + fslib::File metaFile{metaPath, FsOpenMode_Read}; + const bool metaRead = metaFile.is_open() && metaFile.read(&metaData, SIZE_META) == SIZE_META; + if (metaRead) { fs::process_save_meta_data(saveInfo, metaData); } + } + // To do: Same here? + fs::copy_directory(target, fs::DEFAULT_SAVE_ROOT, task, journalSize, fs::DEFAULT_SAVE_MOUNT); + } + else { fs::copy_file(target, fs::DEFAULT_SAVE_ROOT, task, journalSize, fs::DEFAULT_SAVE_MOUNT); } + + task->finished(); +} + +void tasks::backup::delete_backup(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_MESSAGE_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); + + { + const std::string status = stringutil::get_formatted_string(statusTemplate, 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); } + spawningState->refresh(); + task->finished(); +} + +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; } + + // The backup menu should've made sure the remote is pointing to the correct location. + const fslib::Path &path = taskData->path; + + remote->upload_file(path, task); + + task->finished(); } diff --git a/source/ui/ColorMod.cpp b/source/ui/ColorMod.cpp index f511f2d..426ed26 100644 --- a/source/ui/ColorMod.cpp +++ b/source/ui/ColorMod.cpp @@ -2,14 +2,8 @@ void ui::ColorMod::update() { - if (m_direction && (m_colorMod += 6) >= 0x72) - { - m_direction = false; - } - else if (!m_direction && (m_colorMod -= 3) <= 0x00) - { - m_direction = true; - } + if (m_direction && (m_colorMod += 6) >= 0x72) { m_direction = false; } + else if (!m_direction && (m_colorMod -= 3) <= 0x00) { m_direction = true; } } ui::ColorMod::operator sdl::Color() const diff --git a/source/ui/IconMenu.cpp b/source/ui/IconMenu.cpp index b7e8a25..796b044 100644 --- a/source/ui/IconMenu.cpp +++ b/source/ui/IconMenu.cpp @@ -1,20 +1,16 @@ #include "ui/IconMenu.hpp" + #include "colors.hpp" #include "ui/render_functions.hpp" -ui::IconMenu::IconMenu(int x, int y, int rendertargetHeight) : Menu(x, y, 152, 80, rendertargetHeight) {}; +ui::IconMenu::IconMenu(int x, int y, int rendertargetHeight) + : Menu(x, y, 152, 80, rendertargetHeight) {}; -void ui::IconMenu::update(bool hasFocus) -{ - Menu::update(hasFocus); -} +void ui::IconMenu::update(bool hasFocus) { Menu::update(hasFocus); } void ui::IconMenu::render(SDL_Texture *target, bool hasFocus) { - if (hasFocus) - { - m_colorMod.update(); - } + if (hasFocus) { m_colorMod.update(); } for (int i = 0, tempY = m_y; i < static_cast(m_options.size()); i++, tempY += m_optionHeight) { @@ -22,10 +18,7 @@ void ui::IconMenu::render(SDL_Texture *target, bool hasFocus) m_optionTarget->clear(colors::TRANSPARENT); if (i == m_selected) { - if (hasFocus) - { - ui::render_bounding_box(target, m_x - 8, tempY - 8, 152, 146, m_colorMod); - } + if (hasFocus) { ui::render_bounding_box(target, m_x - 8, tempY - 8, 152, 146, m_colorMod); } sdl::render_rect_fill(m_optionTarget->get(), 0, 0, 4, 130, {0x00FFC5FF}); } m_options.at(i)->render_stretched(m_optionTarget->get(), 8, 1, 128, 128); diff --git a/source/ui/Menu.cpp b/source/ui/Menu.cpp index 9dcf5b8..42bd04d 100644 --- a/source/ui/Menu.cpp +++ b/source/ui/Menu.cpp @@ -1,92 +1,70 @@ #include "ui/Menu.hpp" + #include "colors.hpp" #include "config.hpp" #include "input.hpp" #include "ui/render_functions.hpp" + #include ui::Menu::Menu(int x, int y, int width, int fontSize, int renderTargetHeight) - : m_x(x), m_y(y), m_optionHeight(std::ceil(static_cast(fontSize) * 1.8f)), m_originalY(y), m_targetY(y), - m_width(width), m_fontSize(fontSize), m_renderTargetHeight(renderTargetHeight) + : m_x(x) + , m_y(y) + , m_optionHeight(std::ceil(static_cast(fontSize) * 1.8f)) + , m_originalY(y) + , m_targetY(y) + , m_width(width) + , m_fontSize(fontSize) + , m_renderTargetHeight(renderTargetHeight) { // Create render target for options - static int MENU_ID = 0; + static int MENU_ID = 0; std::string menuTargetName = "MENU_" + std::to_string(MENU_ID++); - m_optionTarget = sdl::TextureManager::create_load_texture(menuTargetName, + m_optionTarget = sdl::TextureManager::create_load_texture(menuTargetName, m_width, m_optionHeight, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); // Calculate around how many options can be shown on the render target at once. m_maxDisplayOptions = (renderTargetHeight - m_originalY) / m_optionHeight; - m_scrollLength = std::floor(static_cast(m_maxDisplayOptions) / 2.0f); + m_scrollLength = std::floor(static_cast(m_maxDisplayOptions) / 2.0f); } void ui::Menu::update(bool hasFocus) { // Bail if there's nothing to update. - if (m_options.empty()) - { - return; - } + if (m_options.empty()) { return; } int optionsSize = m_options.size(); - if (input::button_pressed(HidNpadButton_AnyUp) && --m_selected < 0) - { - m_selected = optionsSize - 1; - } - else if (input::button_pressed(HidNpadButton_AnyDown) && ++m_selected >= optionsSize) - { - m_selected = 0; - } - else if (input::button_pressed(HidNpadButton_AnyLeft) && (m_selected -= m_scrollLength) < 0) - { - m_selected = 0; - } + if (input::button_pressed(HidNpadButton_AnyUp) && --m_selected < 0) { m_selected = optionsSize - 1; } + else if (input::button_pressed(HidNpadButton_AnyDown) && ++m_selected >= optionsSize) { m_selected = 0; } + else if (input::button_pressed(HidNpadButton_AnyLeft) && (m_selected -= m_scrollLength) < 0) { m_selected = 0; } else if (input::button_pressed(HidNpadButton_AnyRight) && (m_selected += m_scrollLength) >= optionsSize) { m_selected = optionsSize - 1; } - else if (input::button_pressed(HidNpadButton_L) && (m_selected -= m_scrollLength * 3) < 0) - { - m_selected = 0; - } + else if (input::button_pressed(HidNpadButton_L) && (m_selected -= m_scrollLength * 3) < 0) { m_selected = 0; } else if (input::button_pressed(HidNpadButton_R) && (m_selected += m_scrollLength * 3) >= optionsSize) { m_selected = optionsSize - 1; } // Don't bother continuing further if there's no reason to scroll. - if (static_cast(m_options.size()) <= m_maxDisplayOptions) - { - return; - } + if (static_cast(m_options.size()) <= m_maxDisplayOptions) { return; } - if (m_selected < m_scrollLength) - { - m_targetY = m_originalY; - } + if (m_selected < m_scrollLength) { m_targetY = m_originalY; } else if (m_selected >= static_cast(m_options.size()) - (m_maxDisplayOptions - m_scrollLength)) { m_targetY = m_originalY - ((m_options.size() - m_maxDisplayOptions) * m_optionHeight); } - else if (m_selected >= m_scrollLength) - { - m_targetY = m_originalY - ((m_selected - m_scrollLength) * m_optionHeight); - } + else if (m_selected >= m_scrollLength) { m_targetY = m_originalY - ((m_selected - m_scrollLength) * m_optionHeight); } - if (m_y != m_targetY) - { - m_y += std::ceil((m_targetY - m_y) / config::get_animation_scaling()); - } + if (m_y != m_targetY) { m_y += std::ceil((m_targetY - m_y) / config::get_animation_scaling()); } } void ui::Menu::render(SDL_Texture *target, bool hasFocus) { - if (m_options.empty()) - { - return; - } + if (m_options.empty()) { return; } m_colorMod.update(); @@ -95,10 +73,7 @@ void ui::Menu::render(SDL_Texture *target, bool hasFocus) SDL_QueryTexture(target, NULL, NULL, NULL, &targetHeight); for (int i = 0, tempY = m_y; i < static_cast(m_options.size()); i++, tempY += m_optionHeight) { - if (tempY < -m_fontSize) - { - continue; - } + if (tempY < -m_fontSize) { continue; } else if (tempY > m_renderTargetHeight) { // This is safe to break the loop for. @@ -131,38 +106,23 @@ void ui::Menu::render(SDL_Texture *target, bool hasFocus) } } -void ui::Menu::add_option(std::string_view newOption) -{ - m_options.push_back(newOption.data()); -} +void ui::Menu::add_option(std::string_view newOption) { m_options.push_back(newOption.data()); } void ui::Menu::edit_option(int index, std::string_view newOption) { - if (index < 0 || index >= static_cast(m_options.size())) - { - return; - } + if (index < 0 || index >= static_cast(m_options.size())) { return; } m_options[index] = newOption.data(); } -int ui::Menu::get_selected() const -{ - return m_selected; -} +int ui::Menu::get_selected() const { return m_selected; } -void ui::Menu::set_selected(int selected) -{ - m_selected = selected; -} +void ui::Menu::set_selected(int selected) { m_selected = selected; } -void ui::Menu::set_width(int width) -{ - m_width = width; -} +void ui::Menu::set_width(int width) { m_width = width; } void ui::Menu::reset() { m_selected = 0; - m_y = m_originalY; + m_y = m_originalY; m_options.clear(); } diff --git a/source/ui/PopMessageManager.cpp b/source/ui/PopMessageManager.cpp index ede4574..7db9776 100644 --- a/source/ui/PopMessageManager.cpp +++ b/source/ui/PopMessageManager.cpp @@ -1,9 +1,11 @@ #include "ui/PopMessageManager.hpp" + #include "colors.hpp" #include "config.hpp" #include "logger.hpp" #include "sdl.hpp" #include "ui/render_functions.hpp" + #include namespace @@ -25,11 +27,11 @@ void ui::PopMessageManager::update() for (auto &[displayTicks, currentMessage] : manager.m_messageQueue) { // New message. - manager.m_messages.push_back({.m_y = 720, + manager.m_messages.push_back({.m_y = 720, .m_targetY = 720, - .m_width = sdl::text::get_width(32, currentMessage.c_str()) + 32, + .m_width = sdl::text::get_width(32, currentMessage.c_str()) + 32, .m_message = currentMessage, - .m_timer = sys::Timer(displayTicks)}); + .m_timer = sys::Timer(displayTicks)}); } // Clear the queue. manager.m_messageQueue.clear(); @@ -37,7 +39,7 @@ void ui::PopMessageManager::update() // Update all the messages. // This is the first Y position a message should be displayed at.; - double currentY = 594.0f; + double currentY = 594.0f; double animationScaling = config::get_animation_scaling(); for (size_t i = 0; i < manager.m_messages.size(); i++) { @@ -52,10 +54,7 @@ void ui::PopMessageManager::update() } // Make sure Y coordinate is correct. - if (currentMessage.m_targetY != currentY) - { - currentMessage.m_targetY = currentY; - } + if (currentMessage.m_targetY != currentY) { currentMessage.m_targetY = currentY; } if (currentMessage.m_y != currentMessage.m_targetY) { @@ -76,13 +75,7 @@ void ui::PopMessageManager::render() // Render a dialog box around it. ui::render_dialog_box(NULL, 20, popMessage.m_y - 6, popMessage.m_width, 52); // Render the actual text. - sdl::text::render(NULL, - 36, - popMessage.m_y, - 32, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - popMessage.m_message.c_str()); + sdl::text::render(NULL, 36, popMessage.m_y, 32, sdl::text::NO_TEXT_WRAP, colors::WHITE, popMessage.m_message.c_str()); } } diff --git a/source/ui/SlideOutPanel.cpp b/source/ui/SlideOutPanel.cpp index 79dd9a6..8ead1e2 100644 --- a/source/ui/SlideOutPanel.cpp +++ b/source/ui/SlideOutPanel.cpp @@ -1,15 +1,19 @@ #include "ui/SlideOutPanel.hpp" + #include "colors.hpp" #include "config.hpp" + #include ui::SlideOutPanel::SlideOutPanel(int width, Side side) - : m_x(side == Side::Left ? -width : 1280), m_width(width), m_targetX(side == Side::Left ? 0 : 1280 - m_width), - m_side(side) + : m_x(side == Side::Left ? -width : 1280) + , m_width(width) + , m_targetX(side == Side::Left ? 0 : 1280 - m_width) + , m_side(side) { static int slidePanelTargetID = 0; - std::string panelTargetName = "PanelTarget_" + std::to_string(slidePanelTargetID++); - m_renderTarget = sdl::TextureManager::create_load_texture(panelTargetName, + std::string panelTargetName = "PanelTarget_" + std::to_string(slidePanelTargetID++); + m_renderTarget = sdl::TextureManager::create_load_texture(panelTargetName, width, 720, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); @@ -22,18 +26,15 @@ void ui::SlideOutPanel::update(bool hasFocus) // The first two conditions are just a workaround because my math keeps leaving two pixels. if (!m_isOpen && m_side == Side::Left && m_x >= -4) { - m_x = 0; + m_x = 0; m_isOpen = true; } else if (!m_isOpen && m_side == Side::Right && m_x - m_targetX <= 4) { - m_x = 1280 - m_width; + m_x = 1280 - m_width; m_isOpen = true; } - else if (!m_isOpen && m_side == Side::Left && m_x != m_targetX) - { - m_x -= std::ceil(m_x / scaling); - } + else if (!m_isOpen && m_side == Side::Left && m_x != m_targetX) { m_x -= std::ceil(m_x / scaling); } else if (!m_isOpen && m_side == Side::Right && m_x != m_targetX) { m_x += std::ceil((1280.0f - (static_cast(m_width)) - m_x) / scaling); @@ -42,61 +43,34 @@ void ui::SlideOutPanel::update(bool hasFocus) // I'm going to leave it to the individual elements whether they update if the state is active. if (m_isOpen) { - for (auto ¤tElement : m_elements) - { - currentElement->update(hasFocus); - } + for (auto ¤tElement : m_elements) { currentElement->update(hasFocus); } } } void ui::SlideOutPanel::render(SDL_Texture *Target, bool hasFocus) { - for (auto ¤tElement : m_elements) - { - currentElement->render(m_renderTarget->get(), hasFocus); - } + for (auto ¤tElement : m_elements) { currentElement->render(m_renderTarget->get(), hasFocus); } m_renderTarget->render(NULL, m_x, 0); } -void ui::SlideOutPanel::clear_target() -{ - m_renderTarget->clear(colors::SLIDE_PANEL_CLEAR); -} +void ui::SlideOutPanel::clear_target() { m_renderTarget->clear(colors::SLIDE_PANEL_CLEAR); } void ui::SlideOutPanel::reset() { - m_x = m_side == Side::Left ? -(m_width) : 1280.0f; - m_isOpen = false; + m_x = m_side == Side::Left ? -(m_width) : 1280.0f; + m_isOpen = false; m_closePanel = false; } -void ui::SlideOutPanel::close() -{ - m_closePanel = true; -} +void ui::SlideOutPanel::close() { m_closePanel = true; } -bool ui::SlideOutPanel::is_open() const -{ - return m_isOpen; -} +bool ui::SlideOutPanel::is_open() const { return m_isOpen; } -bool ui::SlideOutPanel::is_closed() const -{ - return m_closePanel && (m_side == Side::Left ? m_x > -(m_width) : m_x < 1280); -} +bool ui::SlideOutPanel::is_closed() const { return m_closePanel && (m_side == Side::Left ? m_x > -(m_width) : m_x < 1280); } -void ui::SlideOutPanel::push_new_element(std::shared_ptr newElement) -{ - m_elements.push_back(newElement); -} +void ui::SlideOutPanel::push_new_element(std::shared_ptr newElement) { m_elements.push_back(newElement); } -void ui::SlideOutPanel::clear_elements() -{ - m_elements.clear(); -} +void ui::SlideOutPanel::clear_elements() { m_elements.clear(); } -SDL_Texture *ui::SlideOutPanel::get_target() -{ - return m_renderTarget->get(); -} +SDL_Texture *ui::SlideOutPanel::get_target() { return m_renderTarget->get(); } diff --git a/source/ui/TextScroll.cpp b/source/ui/TextScroll.cpp index 18cc5b6..ebd6827 100644 --- a/source/ui/TextScroll.cpp +++ b/source/ui/TextScroll.cpp @@ -1,4 +1,5 @@ #include "ui/TextScroll.hpp" + #include "sdl.hpp" namespace @@ -10,27 +11,17 @@ namespace constexpr int SIZE_TEXT_GAP = 0; } // namespace -ui::TextScroll::TextScroll(std::string_view text, - int fontSize, - int availableWidth, - int y, - bool center, - sdl::Color color) +ui::TextScroll::TextScroll(std::string_view text, int fontSize, int availableWidth, int y, bool center, sdl::Color color) { TextScroll::create(text, fontSize, availableWidth, y, center, color); } -void ui::TextScroll::create(std::string_view text, - int fontSize, - int availableWidth, - int y, - bool center, - sdl::Color color) +void ui::TextScroll::create(std::string_view text, int fontSize, int availableWidth, int y, bool center, sdl::Color color) { // Copy the text and stuff. - m_text = text; - m_y = y; - m_fontSize = fontSize; + m_text = text; + m_y = y; + m_fontSize = fontSize; m_textColor = color; m_scrollTimer.start(TICKS_SCROLL_TRIGGER); @@ -39,7 +30,7 @@ void ui::TextScroll::create(std::string_view text, if (m_textWidth > availableWidth) { // Set the X coordinate to 8 and make sure this knows it needs to scroll. - m_x = 8; + m_x = 8; m_textScrolling = true; } else if (center) @@ -62,14 +53,11 @@ void ui::TextScroll::update(bool hasFocus) m_x -= 2; m_textScrollTriggered = true; } - else if (m_textScrollTriggered && m_x > -(m_textWidth + SIZE_TEXT_GAP)) - { - m_x -= 2; - } + else if (m_textScrollTriggered && m_x > -(m_textWidth + SIZE_TEXT_GAP)) { m_x -= 2; } else if (m_textScrollTriggered && m_x <= -(m_textWidth + SIZE_TEXT_GAP)) { // This will snap the text back to where it was, but the user won't even notice it. It just looks like it's scrolling. - m_x = 8; + m_x = 8; m_textScrollTriggered = false; m_scrollTimer.restart(); } @@ -86,12 +74,6 @@ void ui::TextScroll::render(SDL_Texture *target, bool hasFocus) { // We're going to render text twice so it looks like it's scrolling and doesn't end. Ever. sdl::text::render(target, m_x, m_y, m_fontSize, sdl::text::NO_TEXT_WRAP, m_textColor, m_text.c_str()); - sdl::text::render(target, - m_x + m_textWidth + 8, - m_y, - m_fontSize, - sdl::text::NO_TEXT_WRAP, - m_textColor, - m_text.c_str()); + sdl::text::render(target, m_x + m_textWidth + 8, m_y, m_fontSize, sdl::text::NO_TEXT_WRAP, m_textColor, m_text.c_str()); } } diff --git a/source/ui/TitleTile.cpp b/source/ui/TitleTile.cpp index b553250..7bd494b 100644 --- a/source/ui/TitleTile.cpp +++ b/source/ui/TitleTile.cpp @@ -1,7 +1,10 @@ #include "ui/TitleTile.hpp" + #include "colors.hpp" -ui::TitleTile::TitleTile(bool isFavorite, sdl::SharedTexture icon) : m_isFavorite(isFavorite), m_icon(icon) {}; +ui::TitleTile::TitleTile(bool isFavorite, sdl::SharedTexture icon) + : m_isFavorite(isFavorite) + , m_icon(icon) {}; void ui::TitleTile::update(bool isSelected) { @@ -32,16 +35,10 @@ void ui::TitleTile::render(SDL_Texture *target, int x, int y) void ui::TitleTile::reset() { - m_renderWidth = 128; + m_renderWidth = 128; m_renderHeight = 128; } -int ui::TitleTile::get_width() const -{ - return m_renderWidth; -} +int ui::TitleTile::get_width() const { return m_renderWidth; } -int ui::TitleTile::get_height() const -{ - return m_renderHeight; -} +int ui::TitleTile::get_height() const { return m_renderHeight; } diff --git a/source/ui/TitleView.cpp b/source/ui/TitleView.cpp index 5bcb474..e6a4f1b 100644 --- a/source/ui/TitleView.cpp +++ b/source/ui/TitleView.cpp @@ -1,9 +1,11 @@ #include "ui/TitleView.hpp" + #include "colors.hpp" #include "config.hpp" #include "input.hpp" #include "logger.hpp" #include "ui/render_functions.hpp" + #include namespace @@ -11,60 +13,34 @@ namespace constexpr int ICON_ROW_SIZE = 7; } -ui::TitleView::TitleView(data::User *user) : m_user(user) +ui::TitleView::TitleView(data::User *user) + : m_user(user) { TitleView::refresh(); } void ui::TitleView::update(bool hasFocus) { - if (m_titleTiles.empty()) - { - return; - } + if (m_titleTiles.empty()) { return; } // Update pulse - if (hasFocus) - { - m_colorMod.update(); - } + if (hasFocus) { m_colorMod.update(); } // Input. int totalTiles = m_titleTiles.size() - 1; - if (input::button_pressed(HidNpadButton_AnyUp) && (m_selected -= ICON_ROW_SIZE) < 0) - { - m_selected = 0; - } + if (input::button_pressed(HidNpadButton_AnyUp) && (m_selected -= ICON_ROW_SIZE) < 0) { m_selected = 0; } else if (input::button_pressed(HidNpadButton_AnyDown) && (m_selected += ICON_ROW_SIZE) > totalTiles) { m_selected = totalTiles; } - else if (input::button_pressed(HidNpadButton_AnyLeft) && m_selected > 0) - { - --m_selected; - } - else if (input::button_pressed(HidNpadButton_AnyRight) && m_selected < totalTiles) - { - ++m_selected; - } - else if (input::button_pressed(HidNpadButton_L) && (m_selected -= 21) < 0) - { - m_selected = 0; - } - else if (input::button_pressed(HidNpadButton_R) && (m_selected += 21) > totalTiles) - { - m_selected = totalTiles; - } + else if (input::button_pressed(HidNpadButton_AnyLeft) && m_selected > 0) { --m_selected; } + else if (input::button_pressed(HidNpadButton_AnyRight) && m_selected < totalTiles) { ++m_selected; } + else if (input::button_pressed(HidNpadButton_L) && (m_selected -= 21) < 0) { m_selected = 0; } + else if (input::button_pressed(HidNpadButton_R) && (m_selected += 21) > totalTiles) { m_selected = totalTiles; } double scaling = config::get_animation_scaling(); - if (m_selectedY > 388.0f) - { - m_y += std::ceil((388.0f - m_selectedY) / scaling); - } - else if (m_selectedY < 28.0f) - { - m_y += std::ceil((28.0f - m_selectedY) / scaling); - } + if (m_selectedY > 388.0f) { m_y += std::ceil((388.0f - m_selectedY) / scaling); } + else if (m_selectedY < 28.0f) { m_y += std::ceil((28.0f - m_selectedY) / scaling); } for (size_t i = 0; i < m_titleTiles.size(); i++) { @@ -74,20 +50,14 @@ void ui::TitleView::update(bool hasFocus) void ui::TitleView::render(SDL_Texture *target, bool hasFocus) { - if (m_titleTiles.empty()) - { - return; - } + if (m_titleTiles.empty()) { return; } for (int i = 0, tempY = m_y; i < static_cast(m_titleTiles.size()); tempY += 144) { int endRow = i + 7; for (int j = i, tempX = 32; j < endRow; j++, i++, tempX += 144) { - if (i >= static_cast(m_titleTiles.size())) - { - break; - } + if (i >= static_cast(m_titleTiles.size())) { break; } // Save the X and Y to render the selected tile over the rest. if (i == m_selected) @@ -111,10 +81,7 @@ void ui::TitleView::render(SDL_Texture *target, bool hasFocus) m_titleTiles.at(m_selected).render(target, m_selectedX, m_selectedY); } -int ui::TitleView::get_selected() const -{ - return m_selected; -} +int ui::TitleView::get_selected() const { return m_selected; } void ui::TitleView::refresh() { @@ -134,16 +101,10 @@ void ui::TitleView::refresh() } // Just to be sure. - if (m_selected > 0 && m_selected >= static_cast(m_titleTiles.size())) - { - m_selected = m_titleTiles.size() - 1; - } + if (m_selected > 0 && m_selected >= static_cast(m_titleTiles.size())) { m_selected = m_titleTiles.size() - 1; } } void ui::TitleView::reset() { - for (ui::TitleTile ¤tTile : m_titleTiles) - { - currentTile.reset(); - } + for (ui::TitleTile ¤tTile : m_titleTiles) { currentTile.reset(); } } diff --git a/source/ui/render_functions.cpp b/source/ui/render_functions.cpp index ca8acd9..ce2087a 100644 --- a/source/ui/render_functions.cpp +++ b/source/ui/render_functions.cpp @@ -1,9 +1,10 @@ #include "ui/render_functions.hpp" + #include "colors.hpp" namespace { - sdl::SharedTexture s_dialogCorners = nullptr; + sdl::SharedTexture s_dialogCorners = nullptr; sdl::SharedTexture s_menuBoundingCorners = nullptr; } // namespace @@ -11,8 +12,7 @@ void ui::render_dialog_box(SDL_Texture *target, int x, int y, int width, int hei { if (!s_dialogCorners) { - s_dialogCorners = - sdl::TextureManager::create_load_texture("DialogCorners", "romfs:/Textures/DialogCorners.png"); + s_dialogCorners = sdl::TextureManager::create_load_texture("DialogCorners", "romfs:/Textures/DialogCorners.png"); } // Top