From e27e0c8e51652b4bc281c65705d3a4e862ae77b8 Mon Sep 17 00:00:00 2001 From: J-D-K Date: Fri, 4 Jul 2025 13:06:47 -0400 Subject: [PATCH] remote::Storage just barely working. --- Libraries/FsLib | 2 +- Makefile | 2 +- include/JKSV.hpp | 8 +- include/StateManager.hpp | 10 +- include/appstates/AppState.hpp | 18 +- include/appstates/BackupMenuState.hpp | 32 +- include/appstates/BaseTask.hpp | 8 +- include/appstates/ConfirmState.hpp | 11 +- include/appstates/ExtrasMenuState.hpp | 8 +- include/appstates/MainMenuState.hpp | 12 +- include/appstates/ProgressState.hpp | 6 +- include/appstates/SaveCreateState.hpp | 8 +- include/appstates/SettingsState.hpp | 12 +- include/appstates/TaskState.hpp | 6 +- include/appstates/TextTitleSelectState.hpp | 8 +- include/appstates/TitleInfoState.hpp | 6 +- include/appstates/TitleOptionState.hpp | 10 +- include/appstates/TitleSelectCommon.hpp | 10 +- include/appstates/TitleSelectState.hpp | 8 +- include/appstates/UserOptionState.hpp | 8 +- include/config.hpp | 23 +- include/curl/curl.hpp | 59 +- include/data/TitleInfo.hpp | 21 +- include/data/User.hpp | 24 +- include/data/accountUID.hpp | 3 +- include/data/data.hpp | 2 +- include/input.hpp | 4 +- include/logger.hpp | 2 +- include/remote/Form.hpp | 43 ++ include/remote/GoogleDrive.hpp | 127 ++-- include/remote/Item.hpp | 10 +- include/remote/Storage.hpp | 139 ++-- include/remote/URL.hpp | 60 ++ include/remote/WebDav.hpp | 58 ++ include/remote/remote.hpp | 19 + include/strings.hpp | 3 +- include/system/ProgressTask.hpp | 4 +- include/system/Task.hpp | 6 +- include/system/Timer.hpp | 11 +- include/ui/ColorMod.hpp | 6 +- include/ui/Element.hpp | 2 +- include/ui/IconMenu.hpp | 2 +- include/ui/Menu.hpp | 4 +- include/ui/PopMessageManager.hpp | 8 +- include/ui/SlideOutPanel.hpp | 14 +- include/ui/TextScroll.hpp | 2 +- include/ui/TitleTile.hpp | 6 +- include/ui/TitleView.hpp | 6 +- romfs/Text/ENUS.json | 11 +- source/JKSV.cpp | 17 +- source/StateManager.cpp | 8 +- source/appstates/AppState.cpp | 12 +- source/appstates/BackupMenuState.cpp | 380 ++++++---- source/appstates/BaseTask.cpp | 6 +- source/appstates/ExtrasMenuState.cpp | 6 +- source/appstates/MainMenuState.cpp | 16 +- source/appstates/ProgressState.cpp | 4 +- source/appstates/SaveCreateState.cpp | 6 +- source/appstates/SettingsState.cpp | 53 +- source/appstates/TaskState.cpp | 4 +- source/appstates/TextTitleSelectState.cpp | 6 +- source/appstates/TitleInfoState.cpp | 4 +- source/appstates/TitleOptionState.cpp | 8 +- source/appstates/TitleSelectCommon.cpp | 4 +- source/appstates/TitleSelectState.cpp | 13 +- source/appstates/UserOptionState.cpp | 6 +- source/config.cpp | 118 +-- source/curl/curl.cpp | 64 +- source/data/TitleInfo.cpp | 22 +- source/data/User.cpp | 24 +- source/data/data.cpp | 22 +- source/input.cpp | 4 +- source/logger.cpp | 2 +- source/main.cpp | 2 +- source/remote/Form.cpp | 45 ++ source/remote/GoogleDrive.cpp | 799 +++++++++++++-------- source/remote/Item.cpp | 14 +- source/remote/Storage.cpp | 68 +- source/remote/URL.cpp | 90 +++ source/remote/WebDav.cpp | 390 ++++++++++ source/remote/remote.cpp | 140 ++++ source/strings.cpp | 2 +- source/system/ProgressTask.cpp | 4 +- source/system/Task.cpp | 6 +- source/system/Timer.cpp | 4 +- source/ui/ColorMod.cpp | 4 +- source/ui/IconMenu.cpp | 1 - source/ui/Menu.cpp | 4 +- source/ui/PopMessageManager.cpp | 4 +- source/ui/SlideOutPanel.cpp | 14 +- source/ui/TitleTile.cpp | 6 +- source/ui/TitleView.cpp | 6 +- 92 files changed, 2240 insertions(+), 1044 deletions(-) create mode 100644 include/remote/Form.hpp create mode 100644 include/remote/URL.hpp create mode 100644 include/remote/WebDav.hpp create mode 100644 include/remote/remote.hpp create mode 100644 source/remote/Form.cpp create mode 100644 source/remote/URL.cpp create mode 100644 source/remote/WebDav.cpp create mode 100644 source/remote/remote.cpp diff --git a/Libraries/FsLib b/Libraries/FsLib index bc0bf99..403a099 160000 --- a/Libraries/FsLib +++ b/Libraries/FsLib @@ -1 +1 @@ -Subproject commit bc0bf99b32ef89a7892093737407977b3e649e79 +Subproject commit 403a0993d5ed2bffcf0ac9b298be95279d0d3749 diff --git a/Makefile b/Makefile index 9df71b3..f0b47be 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 +SOURCES := source source/appstates source/ui source/data source/system source/fs source/curl source/remote 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 91c3ef0..5027b40 100644 --- a/include/JKSV.hpp +++ b/include/JKSV.hpp @@ -9,20 +9,20 @@ class JKSV { public: /// @brief Initializes JKSV. Initializes services. - JKSV(void); + JKSV(); /// @brief Exits services. ~JKSV(); /// @brief Returns if initializing was successful and JKSV is running. /// @return True or false. - bool is_running(void) const; + bool is_running() const; /// @brief Runs JKSV's update routine. - void update(void); + void update(); /// @brief Runs JKSV's render routine. - void render(void); + void render(); private: /// @brief Whether or not initialization was successful and JKSV is still running. diff --git a/include/StateManager.hpp b/include/StateManager.hpp index f736b2c..1dfdcb1 100644 --- a/include/StateManager.hpp +++ b/include/StateManager.hpp @@ -13,13 +13,13 @@ class StateManager StateManager &operator=(StateManager &&) = delete; /// @brief Runs the state update routine. - static void update(void); + static void update(); /// @brief Runs the state rendering routine(s); - static void render(void); + static void render(); /// @brief Returns whether the back of the vector is a closable state. - static bool back_is_closable(void); + static bool back_is_closable(); /// @brief Pushes a new state to the state vector. /// @param newState Shared_ptr to state to push. @@ -27,11 +27,11 @@ class StateManager private: /// @brief Private constructor so no constructing. - StateManager(void) = default; + StateManager() = default; /// @brief Returns a reference to the instance of StateManger. /// @return Reference to state manager. - static StateManager &get_instance(void); + static StateManager &get_instance(); /// @brief This is the vector that holds the pointers to the states. static inline std::vector> sm_stateVector; diff --git a/include/appstates/AppState.hpp b/include/appstates/AppState.hpp index f480c9d..6b48825 100644 --- a/include/appstates/AppState.hpp +++ b/include/appstates/AppState.hpp @@ -13,34 +13,34 @@ class AppState virtual ~AppState(); /// @brief Every derived class is required to have this function. - virtual void update(void) = 0; + virtual void update() = 0; /// @brief Every derived class is required to have this function. - virtual void render(void) = 0; + virtual void render() = 0; /// @brief Deactivates state and allows JKSV to purge it from the vector. - void deactivate(void); + void deactivate(); /// @brief Allows a state to be reactivated and pushed to the vector. - void reactivate(void); + void reactivate(); /// @brief Returns if the state is still active. /// @return Whether state is still active or can be purged. - bool is_active(void) const; + bool is_active() const; /// @brief Tells the state it's at the back of the vector and has focus. - void give_focus(void); + void give_focus(); /// @brief Takes the focus away and tells the state it's no long back(); - void take_focus(void); + void take_focus(); /// @brief Allows the state to know whether it has focus. /// @return Whether state has focus or not. - bool has_focus(void) const; + bool has_focus() const; /// @brief Returns whether or not JKSV should allow closing while state is active. /// @return True if closable. False if not. - bool is_closable(void) const; + bool is_closable() const; private: /// @brief Stores whether or not the state is currently active. diff --git a/include/appstates/BackupMenuState.hpp b/include/appstates/BackupMenuState.hpp index 799d2d6..44c8045 100644 --- a/include/appstates/BackupMenuState.hpp +++ b/include/appstates/BackupMenuState.hpp @@ -10,7 +10,7 @@ #include /// @brief This is the state where the user can backup and restore saves. -class BackupMenuState : public AppState +class BackupMenuState final : public AppState { public: /// @brief Creates a new backup selection state. @@ -23,34 +23,34 @@ class BackupMenuState : public AppState ~BackupMenuState(); /// @brief Required. Inherited virtual function from AppState. - void update(void) override; + void update() override; /// @brief Required. Inherited virtual function from AppState. - void render(void) override; + void render() override; /// @brief Refreshes the directory listing and menu. - void refresh(void); + void refresh(); /// @brief Allows a spawned task to tell this class that it wrote save data to the system. - void save_data_written(void); + void save_data_written(); /// @brief Struct used for passing data to functions. typedef struct { /// @brief Pointer to the target user. - data::User *m_user; + data::User *user; /// @brief Data for the target title. - data::TitleInfo *m_titleInfo; + data::TitleInfo *titleInfo; /// @brief Path of the target. - fslib::Path m_targetPath; + fslib::Path targetPath; /// @brief Journal size for when a commit is needed. - uint64_t m_journalSize; + uint64_t journalSize; /// @brief Pointer to >this spawning state. - BackupMenuState *m_spawningState; + BackupMenuState *spawningState; } DataStruct; private: @@ -92,4 +92,16 @@ class BackupMenuState : public AppState /// @brief The width of the panels. This is set according to the control guide text. static inline int sm_panelWidth = 0; + + /// @brief This is the function called when New Backup is selected. + void name_and_create_backup(); + + /// @brief This is the function called when a backup is selected to be overwritten. + void confirm_backup_overwrite(); + + /// @brief This function is called to confirm restoring a backup. + void confirm_restore(); + + /// @brief Function called to confirm deleting a backup. + void confirm_delete(); }; diff --git a/include/appstates/BaseTask.hpp b/include/appstates/BaseTask.hpp index eb98ffe..6dc577c 100644 --- a/include/appstates/BaseTask.hpp +++ b/include/appstates/BaseTask.hpp @@ -10,21 +10,21 @@ class BaseTask : public AppState { public: /// @brief Constructor. Starts the glyph timer and sets AppState to not allow closing. - BaseTask(void); + BaseTask(); /// @brief Virtual destructor. virtual ~BaseTask() {}; /// @brief Runs the update routine for rendering the loading glyph animation. /// @param - void update(void) override; + void update() override; /// @brief Virtual render function. - virtual void render(void) = 0; + virtual void render() = 0; /// @brief This function renders the loading glyph in the bottom left corner. /// @note This is mostly just so users don't think JKSV has frozen when operations take a long time. - void render_loading_glyph(void); + void render_loading_glyph(); private: /// @brief This is the current frame of the loading glyph animation. diff --git a/include/appstates/ConfirmState.hpp b/include/appstates/ConfirmState.hpp index 74bbd88..d09d7ad 100644 --- a/include/appstates/ConfirmState.hpp +++ b/include/appstates/ConfirmState.hpp @@ -24,7 +24,7 @@ namespace /// @tparam StateType The state type spawned on confirmation. Ex: TaskState, ProgressState /// @tparam StructType The type of struct passed to the state on confirmation. template -class ConfirmState : public AppState +class ConfirmState final : public AppState { public: /// @brief All functions passed to this state need to follow this signature: void function( *, std::shared_ptr<>) @@ -39,9 +39,8 @@ class ConfirmState : public AppState bool holdRequired, TaskFunction function, std::shared_ptr dataStruct) - : AppState(false), m_queryString(queryString.data()), - m_yesString(strings::get_by_name(strings::names::YES_NO, 0)), m_hold(holdRequired), m_function(function), - m_dataStruct(dataStruct) + : AppState(false), m_queryString(queryString), m_yesString(strings::get_by_name(strings::names::YES_NO, 0)), + m_hold(holdRequired), m_function(function), m_dataStruct(dataStruct) { // This is to make centering the Yes [A] string more accurate. m_yesX = YES_X_CENTER_COORDINATE - (sdl::text::get_width(22, m_yesString.c_str()) / 2); @@ -52,7 +51,7 @@ class ConfirmState : public AppState ~ConfirmState() {}; /// @brief Just updates the ConfirmState. - void update(void) override + void update() override { // This is to guard against the dialog being triggered right away. To do: Maybe figure out a better way to accomplish this? if (input::button_pressed(HidNpadButton_A) && !m_triggerGuard) @@ -109,7 +108,7 @@ class ConfirmState : public AppState } /// @brief Renders the state to screen. - void render(void) override + void render() override { // Dim background sdl::render_rect_fill(NULL, 0, 0, 1280, 720, colors::DIM_BACKGROUND); diff --git a/include/appstates/ExtrasMenuState.hpp b/include/appstates/ExtrasMenuState.hpp index 9e7857f..8ec4eeb 100644 --- a/include/appstates/ExtrasMenuState.hpp +++ b/include/appstates/ExtrasMenuState.hpp @@ -4,20 +4,20 @@ #include "ui/Menu.hpp" /// @brief Extras menu. -class ExtrasMenuState : public AppState +class ExtrasMenuState final : public AppState { public: /// @brief Constructor. - ExtrasMenuState(void); + ExtrasMenuState(); /// @brief Required even if nothing happens. ~ExtrasMenuState() {}; /// @brief Updates the menu. - void update(void) override; + void update() override; /// @brief Renders the menu to screen. - void render(void) override; + void render() override; private: /// @brief Menu diff --git a/include/appstates/MainMenuState.hpp b/include/appstates/MainMenuState.hpp index ecfc6f8..2c45ec9 100644 --- a/include/appstates/MainMenuState.hpp +++ b/include/appstates/MainMenuState.hpp @@ -6,26 +6,26 @@ #include /// @brief The main -class MainMenuState : public AppState +class MainMenuState final : public AppState { public: /// @brief Creates and initializes the main menu. - MainMenuState(void); + MainMenuState(); /// @brief Required even if it does nothing. ~MainMenuState() {}; /// @brief Runs update routine. - void update(void) override; + void update() override; /// @brief Renders menu to screen. - void render(void) override; + void render() override; /// @brief Signals to - static void initialize_view_states(void); + static void initialize_view_states(); /// @brief Calls refresh on on view states in the vector. - static void refresh_view_states(void); + static void refresh_view_states(); private: /// @brief Render target this state renders to. diff --git a/include/appstates/ProgressState.hpp b/include/appstates/ProgressState.hpp index 01132c7..2ed5429 100644 --- a/include/appstates/ProgressState.hpp +++ b/include/appstates/ProgressState.hpp @@ -5,7 +5,7 @@ #include /// @brief State that shows progress of a task. -class ProgressState : public BaseTask +class ProgressState final : public BaseTask { public: /// @brief Constructs a new ProgressState. @@ -20,10 +20,10 @@ class ProgressState : public BaseTask ~ProgressState() {}; /// @brief Checks if the thread is finished and deactivates this state. - void update(void) override; + void update() override; /// @brief Renders the current progress to screen. - void render(void) override; + void render() override; private: /// @brief Underlying task that has extra methods for tracking the progress of a task. diff --git a/include/appstates/SaveCreateState.hpp b/include/appstates/SaveCreateState.hpp index be96a72..0751fce 100644 --- a/include/appstates/SaveCreateState.hpp +++ b/include/appstates/SaveCreateState.hpp @@ -7,7 +7,7 @@ #include /// @brief This is the state that is spawned when CreateSaveData is selected from the user menu. -class SaveCreateState : public AppState +class SaveCreateState final : public AppState { public: /// @brief Constructs a new SaveCreateState. @@ -19,13 +19,13 @@ class SaveCreateState : public AppState ~SaveCreateState() {}; /// @brief Runs the update routine. - void update(void) override; + void update() override; /// @brief Runs the render routine. - void render(void) override; + void render() override; /// @brief This signals so data and the view can be refreshed on the next update() to avoid threading shenanigans. - void data_and_view_refresh_required(void); + void data_and_view_refresh_required(); private: /// @brief Pointer to target user. diff --git a/include/appstates/SettingsState.hpp b/include/appstates/SettingsState.hpp index 2198ec8..331facb 100644 --- a/include/appstates/SettingsState.hpp +++ b/include/appstates/SettingsState.hpp @@ -4,20 +4,20 @@ #include "ui/Menu.hpp" /// @brief The state for settings. -class SettingsState : public AppState +class SettingsState final : public AppState { public: /// @brief Constructs a new settings state. - SettingsState(void); + SettingsState(); /// @brief Required destructor. ~SettingsState() {}; /// @brief Runs the update routine. - void update(void) override; + void update() override; /// @brief Runs the render routine. - void render(void) override; + void render() override; private: /// @brief Menu for selecting and toggling settings. @@ -30,8 +30,8 @@ class SettingsState : public AppState int m_controlGuideX = 0; /// @brief Runs a routine to update the menu strings for the menu. - void update_menu_options(void); + void update_menu_options(); /// @brief Toggles or executes the code to changed the selected menu option. - void toggle_options(void); + void toggle_options(); }; diff --git a/include/appstates/TaskState.hpp b/include/appstates/TaskState.hpp index c5a8640..3c8d4d1 100644 --- a/include/appstates/TaskState.hpp +++ b/include/appstates/TaskState.hpp @@ -4,7 +4,7 @@ #include /// @brief State that spawns a task and allows updates to be printed to screen. -class TaskState : public BaseTask +class TaskState final : public BaseTask { public: /// @brief Constructs and spawns a new TaskState. @@ -19,11 +19,11 @@ class TaskState : public BaseTask ~TaskState() {}; /// @brief Runs update routine. Waits for thread function to signal finish and deactivates. - void update(void) override; + void update() override; /// @brief Run render routine. Prints m_task's status string to screen, basically. /// @param - void render(void) override; + void render() override; private: /// @brief Underlying task. diff --git a/include/appstates/TextTitleSelectState.hpp b/include/appstates/TextTitleSelectState.hpp index 0726256..bca702b 100644 --- a/include/appstates/TextTitleSelectState.hpp +++ b/include/appstates/TextTitleSelectState.hpp @@ -5,7 +5,7 @@ #include "ui/Menu.hpp" /// @brief Text menu title selection state. -class TextTitleSelectState : public TitleSelectCommon +class TextTitleSelectState final : public TitleSelectCommon { public: /// @brief Constructs new text menu title selection state. @@ -16,13 +16,13 @@ class TextTitleSelectState : public TitleSelectCommon ~TextTitleSelectState() {}; /// @brief Runs update routine. - void update(void) override; + void update() override; /// @brief Runs render routine. - void render(void) override; + void render() override; /// @brief Refreshes view for changes. - void refresh(void) override; + void refresh() override; private: /// @brief Pointer to user view "belongs" to. diff --git a/include/appstates/TitleInfoState.hpp b/include/appstates/TitleInfoState.hpp index f1296d8..5d1d24b 100644 --- a/include/appstates/TitleInfoState.hpp +++ b/include/appstates/TitleInfoState.hpp @@ -7,7 +7,7 @@ #include #include -class TitleInfoState : public AppState +class TitleInfoState final : public AppState { public: /// @brief Constructs a new title info state. @@ -19,10 +19,10 @@ class TitleInfoState : public AppState ~TitleInfoState(); /// @brief Runs update routine. - void update(void) override; + void update() override; /// @brief Runs render routine. - void render(void) override; + void render() override; private: /// @brief Pointer to user. diff --git a/include/appstates/TitleOptionState.hpp b/include/appstates/TitleOptionState.hpp index f37693c..c0023c6 100644 --- a/include/appstates/TitleOptionState.hpp +++ b/include/appstates/TitleOptionState.hpp @@ -6,7 +6,7 @@ #include "ui/SlideOutPanel.hpp" #include -class TitleOptionState : public AppState +class TitleOptionState final : public AppState { public: /// @brief Constructs a new title option state. @@ -18,16 +18,16 @@ class TitleOptionState : public AppState ~TitleOptionState() {}; /// @brief Runs update routine. - void update(void) override; + void update() override; /// @brief Runs the render routine. - void render(void) override; + void render() override; /// @brief This function allows tasks to signal to the spawning state to close itself on the next update() call. - void close_on_update(void); + void close_on_update(); /// @brief Signals to the main thread that a view refresh is required on the next update() call. - void refresh_required(void); + void refresh_required(); /// @brief This is the struct used to pass data to the thread functions. typedef struct diff --git a/include/appstates/TitleSelectCommon.hpp b/include/appstates/TitleSelectCommon.hpp index e65d981..1ff5ac6 100644 --- a/include/appstates/TitleSelectCommon.hpp +++ b/include/appstates/TitleSelectCommon.hpp @@ -6,22 +6,22 @@ class TitleSelectCommon : public AppState { public: /// @brief Constructs a new TitleSelectCommon. Basically just calculates the X coordinate of the control if it wasn't already. - TitleSelectCommon(void); + TitleSelectCommon(); /// @brief Required destructor. virtual ~TitleSelectCommon() {}; /// @brief Required, inherited. - virtual void update(void) = 0; + virtual void update() = 0; /// @brief Required, inherited. - virtual void render(void) = 0; + virtual void render() = 0; /// @brief Both derived classes need this function. - virtual void refresh(void) = 0; + virtual void refresh() = 0; /// @brief Renders the control guide string to the bottom right corner. - void render_control_guide(void); + void render_control_guide(); private: /// @brief X coordinate the control guide is rendered at. diff --git a/include/appstates/TitleSelectState.hpp b/include/appstates/TitleSelectState.hpp index c9acf4c..a60faf6 100644 --- a/include/appstates/TitleSelectState.hpp +++ b/include/appstates/TitleSelectState.hpp @@ -5,7 +5,7 @@ #include "ui/TitleView.hpp" /// @brief Title select state with icon tiles. -class TitleSelectState : public TitleSelectCommon +class TitleSelectState final : public TitleSelectCommon { public: /// @brief Constructs new title select state. @@ -16,13 +16,13 @@ class TitleSelectState : public TitleSelectCommon ~TitleSelectState() {}; /// @brief Runs the update routine. - void update(void) override; + void update() override; /// @brief Runs the render routine. - void render(void) override; + void render() override; /// @brief Refreshes the view. - void refresh(void) override; + void refresh() override; private: /// @brief Pointer to the user the view belongs to. diff --git a/include/appstates/UserOptionState.hpp b/include/appstates/UserOptionState.hpp index 1385658..1dda929 100644 --- a/include/appstates/UserOptionState.hpp +++ b/include/appstates/UserOptionState.hpp @@ -7,7 +7,7 @@ #include /// @brief State that allows certain actions to be taken for users. -class UserOptionState : public AppState +class UserOptionState final : public AppState { public: /// @brief Constructs a new UserOptionState. @@ -19,14 +19,14 @@ class UserOptionState : public AppState ~UserOptionState() {}; /// @brief Runs the render routine. - void update(void) override; + void update() override; /// @brief Runs the render routine. - void render(void) override; + void render() override; /// @brief Signals to the main update() function that a refresh is needed. /// @note Like this to prevent threading headaches. - void data_and_view_refresh_required(void); + void data_and_view_refresh_required(); /// @brief Struct used for passing data to functions/tasks. typedef struct diff --git a/include/config.hpp b/include/config.hpp index 688cc55..138054f 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -5,13 +5,13 @@ namespace config { /// @brief Attempts to load config from file. If it fails, loads defaults. - void initialize(void); + void initialize(); /// @brief Resets config to default values. - void reset_to_default(void); + void reset_to_default(); /// @brief Saves config to file. - void save(void); + void save(); /// @brief Retrieves the config value according to the key passed. /// @param key Key to retrieve. See config::keys @@ -27,26 +27,13 @@ namespace config /// @param value Value to set the key to. void set_by_key(std::string_view key, uint8_t value); - /// @brief Retrieves value of config at index. - /// @param index Index of value to retrieve. - uint8_t get_by_index(int index); - - /// @brief Toggles the key at index from 1 to 0 or vice-versa. - /// @param index Index of key to toggle. - void toggle_by_index(int index); - - /// @brief Sets the config value at index to value. - /// @param index Index of value to set. - /// @param value Value to set index to. - void set_by_index(int index, uint8_t value); - /// @brief Returns the working directory. /// @return Working directory. - fslib::Path get_working_directory(void); + fslib::Path get_working_directory(); /// @brief Returns the scaling speed of UI transitions and animations. /// @return Scaling variable. - double get_animation_scaling(void); + double get_animation_scaling(); /// @brief Sets the UI animation scaling. /// @param newScale New value to set the scaling to. diff --git a/include/curl/curl.hpp b/include/curl/curl.hpp index 2edc2d6..14cea02 100644 --- a/include/curl/curl.hpp +++ b/include/curl/curl.hpp @@ -1,6 +1,5 @@ #pragma once #include "fslib.hpp" -#include "logger.hpp" #include #include #include @@ -10,7 +9,7 @@ namespace curl { /// @brief JKSV's user agent string. - static constexpr std::string_view USER_AGENT_STRING = "JKSV"; + static const char *STRING_USER_AGENT = "JKSV"; /// @brief Self cleaning curl handle. using Handle = std::unique_ptr; @@ -23,10 +22,10 @@ namespace curl /// @brief Initializes lib curl. /// @return True on success. False on failure. - bool initialize(void); + bool initialize(); /// @brief Exits libcurl - void exit(void); + void exit(); /// @brief Inline templated function to wrap curl_easy_setopt and make using curl::Handle slightly easier. /// @tparam Option Templated type of the option. This is a headache so let the compiler figure it out. @@ -43,14 +42,14 @@ namespace curl /// @brief Inline function that returns a self cleaning curl handle. /// @return Curl handle. - static inline curl::Handle new_handle(void) + 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(void) + static inline curl::HeaderList new_header_list() { return curl::HeaderList(nullptr, curl_slist_free_all); } @@ -59,41 +58,18 @@ namespace curl /// @param curl curl::Handle to reset. static inline void reset_handle(curl::Handle &curl) { - // Reset the handle. curl_easy_reset(curl.get()); - // Set the user agent since basically everything needs it. - curl::set_option(curl, CURLOPT_USERAGENT, curl::USER_AGENT_STRING.data()); + curl::set_option(curl, CURLOPT_USERAGENT, curl::STRING_USER_AGENT); } /// @brief Logged inline wrapper function for curl_easy_perform. - /// @param curl Handle to perform. - /// @return True on success. False on failure. - static inline bool perform(curl::Handle &curl) - { - // CURLCode is just an int anyway. - CURLcode error = curl_easy_perform(curl.get()); - if (error != CURLE_OK) - { - logger::log("Error performing curl: %i.", error); - return false; - } - return true; - } + /// @param handle Handle to perform. + bool perform(curl::Handle &handle); /// @brief Inline wrapper function to make adding to HeaderList simpler. /// @param headerList Header list to append to. /// @param header Header to append. - static inline void append_header(curl::HeaderList &headerList, std::string_view header) - { - // Release the current list and save the pointer to the head of it. - curl_slist *list = headerList.release(); - - // Append to it. - curl_slist_append(list, header.data()); - - // Reassign the unique_ptr to the new head. - headerList.reset(list); - } + void append_header(curl::HeaderList &list, std::string_view header); /// @brief Curl callback function for reading data from a file. /// @param buffer Incoming buffer from curl to read to. @@ -131,9 +107,24 @@ namespace curl /// @param array Array of headers to search. /// @param header Header to search for. /// @param valueOut String to write the value to. - /// @return True if the header was found and the value was successfully extracted. bool get_header_value(const curl::HeaderArray &array, std::string_view header, std::string &valueOut); + /// @brief Gets the response code from the handle passed. + /// @param handle Handle to get the response code from. + long get_response_code(curl::Handle &handle); + + /// @brief Calls curl_easy_escape using the passed curl::Handle. + /// @param handle Handle to use. + /// @param in String to escape. + /// @param out String to write the escaped text to. + bool escape_string(curl::Handle &handle, std::string_view in, std::string &out); + + /// @brief Calls curl_easy_unescape using the passed curl::Handle. + /// @param handle Handle to use. + /// @param in String to unescape. + /// @param out String to write the escaped text to. + bool unescape_string(curl::Handle &handle, std::string_view in, std::string &out); + /// @brief Prepares the curl handle passed for a get request. /// @param curl Handle to prepare for a get request. void prepare_get(curl::Handle &curl); diff --git a/include/data/TitleInfo.hpp b/include/data/TitleInfo.hpp index f1e9758..c913dba 100644 --- a/include/data/TitleInfo.hpp +++ b/include/data/TitleInfo.hpp @@ -21,31 +21,30 @@ namespace data /// @brief Returns the application ID of the title. /// @return Title's application ID. - uint64_t get_application_id(void) const; + uint64_t get_application_id() const; /// @brief Returns a pointer to the control data for the title. /// @return Pointer to control data. - NsApplicationControlData *get_control_data(void); + NsApplicationControlData *get_control_data(); /// @brief Returns whether or not the title has control data. /// @return Whether or not the title has control data. - bool has_control_data(void) const; + bool has_control_data() const; /// @brief Returns the title of the title? /// @return Title directly from the NACP. - const char *get_title(void); + const char *get_title(); /// @brief Returns the path safe version of the title for file system usage. /// @return Path safe version of the title. - const char *get_path_safe_title(void); + const char *get_path_safe_title() const; /// @brief Returns the publisher of the title. /// @return Publisher string from NACP. - const char *get_publisher(void); + const char *get_publisher(); /// @brief Returns the owner ID of the save data. - /// @return Save data owner ID. - uint64_t get_save_data_owner_id(void) const; + uint64_t get_save_data_owner_id() const; /// @brief Returns the save data container's base size. /// @param saveType Type of save data to return. @@ -70,11 +69,11 @@ namespace data /// @brief Returns if a title uses the save type passed. /// @param saveType Save type to check for. /// @return True on success. False on failure. - bool has_save_data_type(uint8_t saveType); + bool has_save_data_type(uint8_t saveType) const; /// @brief Returns a pointer to the icon texture. /// @return Icon - sdl::SharedTexture get_icon(void) const; + sdl::SharedTexture get_icon() const; /// @brief Allows the path safe title to be set to a new path. /// @param newPathSafe Buffer containing the new safe path to use. @@ -101,6 +100,6 @@ namespace data sdl::SharedTexture m_icon = nullptr; /// @brief Private function to get/create the path safe title. - void get_create_path_safe_title(void); + void get_create_path_safe_title(); }; } // namespace data diff --git a/include/data/User.hpp b/include/data/User.hpp index f92e639..7cad2a6 100644 --- a/include/data/User.hpp +++ b/include/data/User.hpp @@ -39,34 +39,34 @@ namespace data void add_data(const FsSaveDataInfo *saveInfo, const PdmPlayStatistics *playStats); /// @brief Clears the user save info vector. - void clear_data_entries(void); + void clear_data_entries(); /// @brief Erases data at index. /// @param index Index of save data info to erase. void erase_data(int index); /// @brief Runs the sort algo on the vector. - void sort_data(void); + void sort_data(); /// @brief Returns the account ID of the user. /// @return AccountID - AccountUid get_account_id(void) const; + AccountUid get_account_id() const; /// @brief Returns the save data type the account uses. /// @return Save data type of the account. - FsSaveDataType get_account_save_type(void) const; + FsSaveDataType get_account_save_type() const; /// @brief Returns the account's nickname. /// @return Account nickname. - const char *get_nickname(void) const; + const char *get_nickname() const; /// @brief Returns the path safe version of the nickname. /// @return Path safe nickname. - const char *get_path_safe_nickname(void) const; + const char *get_path_safe_nickname() const; /// @brief Returns the total number of entries in the data vector. /// @return Total number of entries. - size_t get_total_data_entries(void) const; + size_t get_total_data_entries() const; /// @brief Returns the application ID of the title at index. /// @param index Index of title. @@ -90,7 +90,7 @@ namespace data /// @brief Returns a reference to the user save data info vector. /// @return Reference to the user save info vector. - data::UserSaveInfoList &get_user_save_info_list(void); + data::UserSaveInfoList &get_user_save_info_list(); /// @brief Returns a pointer to the play statistics of applicationID /// @param applicationID Application ID to search and fetch. @@ -99,18 +99,18 @@ namespace data /// @brief Returns raw SDL_Texture pointer of icon. /// @return SDL_Texture of icon. - SDL_Texture *get_icon(void); + SDL_Texture *get_icon(); /// @brief Returns the shared texture of icon. Increasing reference count of it. /// @return Shared icon texture. - sdl::SharedTexture get_shared_icon(void); + sdl::SharedTexture get_shared_icon(); /// @brief Erases a UserDataEntry according to the application ID passed. /// @param applicationID ID of the save to erase. void erase_save_info_by_id(uint64_t applicationID); /// @brief Loads the save data info and play statistics for the current user using the information passed to the constructor. - void load_user_data(void); + void load_user_data(); private: /// @brief Account's ID @@ -137,7 +137,7 @@ namespace data void load_account(AccountProfile &profile, AccountProfileBase &profileBase); /// @brief Creates a placeholder since something went wrong. - void create_account(void); + void create_account(); /// @brief Opens a save data info reader according to the data passed to the user. /// @param spaceID The FsSaveDataSpaceId to use when opening the reader. diff --git a/include/data/accountUID.hpp b/include/data/accountUID.hpp index cf7d8a6..d8d8fb1 100644 --- a/include/data/accountUID.hpp +++ b/include/data/accountUID.hpp @@ -24,5 +24,6 @@ static inline bool operator==(AccountUid accountIDA, AccountUid accountIDB) /// @note I'm not 100% sure which uint64_t in the AccountUid struct comes first. I don't know if it's [0][1] or [1][0]. To do: Figure that out. static inline bool operator==(AccountUid accountIDA, u128 accountIDB) { - return accountIDA.uid[0] == (accountIDB >> 64 & 0xFFFFFFFFFFFFFFFF) && accountIDA.uid[1] == (accountIDB & 0xFFFFFFFFFFFFFFFF); + return accountIDA.uid[0] == (accountIDB >> 64 & 0xFFFFFFFFFFFFFFFF) && + accountIDA.uid[1] == (accountIDB & 0xFFFFFFFFFFFFFFFF); } diff --git a/include/data/data.hpp b/include/data/data.hpp index 997eeac..af2da14 100644 --- a/include/data/data.hpp +++ b/include/data/data.hpp @@ -26,7 +26,7 @@ namespace data /// @brief Returns a reference to the title info map. /// @return Reference to TitleInfoMap. - std::unordered_map &get_title_info_map(void); + std::unordered_map &get_title_info_map(); /// @brief Uses the application ID passed to add/load a title to the map. /// @param applicationID Application/System save data ID to add. diff --git a/include/input.hpp b/include/input.hpp index 163bd4d..d8d2360 100644 --- a/include/input.hpp +++ b/include/input.hpp @@ -4,10 +4,10 @@ namespace input { /// @brief Initializes PadState and input. - void initialize(void); + void initialize(); /// @brief Updates the PadState. - void update(void); + void update(); /// @brief Returns if a button was pressed the current frame, but not the previous. /// @param button Button to check. diff --git a/include/logger.hpp b/include/logger.hpp index 1b0222a..653da8e 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -3,7 +3,7 @@ namespace logger { /// @brief Creates and empties the log file - void initialize(void); + void initialize(); /// @brief Logs a formatted string. /// @param format Format of string. diff --git a/include/remote/Form.hpp b/include/remote/Form.hpp new file mode 100644 index 0000000..c38d72d --- /dev/null +++ b/include/remote/Form.hpp @@ -0,0 +1,43 @@ +#pragma once +#include + +namespace remote +{ + /// @brief This is a class to build URL encoded form bodies and be more readable than snprintfs. + class Form + { + public: + Form() = default; + + /// @brief Copy constructor + /// @param form Form to copy from. + Form(const Form &form); + + /// @brief Move constructor. + /// @param form Form to copy from. + Form(Form &&form); + + /// @brief = Operator. + /// @param form Form to copy. + Form &operator=(const Form &form); + + /// @brief = Move operator. + /// @param form Form to rob of its life. + Form &operator=(Form &&form); + + /// @brief Appends a parameter to the form/URL encoded text. + /// @param param Parameter to append. + /// @param value Value to append. + Form &append_parameter(std::string_view param, std::string_view value); + + /// @brief Returns the C string of the form string. + const char *get() const; + + /// @brief Returns m_form.length() + size_t length() const; + + private: + /// @brief String containing the actual data posted. + std::string m_form{}; + }; +} // namespace remote diff --git a/include/remote/GoogleDrive.hpp b/include/remote/GoogleDrive.hpp index 8e9f0a4..e20ff5d 100644 --- a/include/remote/GoogleDrive.hpp +++ b/include/remote/GoogleDrive.hpp @@ -8,116 +8,85 @@ namespace remote class GoogleDrive final : public remote::Storage { public: - /// @brief Google Drive class constructor. Unlike PC prototype, the path to the client_secret is hardcoded. - /// @param - GoogleDrive(void); + /// @brief Loads the config from SD. + GoogleDrive(); - /// @brief Changes the current parent directory. - /// @param id ID of the parent to target. - void change_directory(std::string_view id) override; - - /// @brief Creates a new directory on Google Drive. + /// @brief Creates a directory on Google Drive. /// @param name Name of the directory to create. - /// @return True on success. False on failure. bool create_directory(std::string_view name) override; - /// @brief Deletes a directory from Google Drive. - /// @param id ID of the directory to delete. - /// @return True on success. False on failure. - bool delete_directory(std::string_view id) override; - - /// @brief Uploads a file to Google Drive. - /// @param source Source path. - /// @return True on success. False on failure. + /// @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; - /// @brief Patches or updates a file on Google Drive. - /// @param id ID of the file to patch. - /// @param source Source path of the updated file. - /// @return True on success. False on failure. - bool patch_file(std::string_view id, const fslib::Path &source) override; - - /// @brief Deletes a file from Google Drive. - /// @param id ID of the file to delete. - /// @return True on success. False on failure. - bool delete_file(std::string_view id) 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; /// @brief Downloads a file from Google Drive. - /// @param id ID of the file to download. - /// @param destination Path with the destination to save the file to. - /// @return True on success. False on failure. - bool download_file(std::string_view id, const fslib::Path &destination) override; + /// @param file Pointer to the item containing data to download the file. + /// @param destination Location to write the downloaded file to. + bool download_file(const remote::Item *file, const fslib::Path &destination) override; - // Sign in related functions. - /// @brief Returns if a sign in is required for using Google Drive. - /// @return True if sign in is required. False if it isn't. - bool sign_in_required(void) const; + /// @brief Deletes an item from Google Drive. + /// @param item Pointer to item containing data to delete the item. + bool delete_item(const remote::Item *item) override; - /// @brief Gets the data needed to display and sign into Google. - /// @param code String to write the sign in code to. - /// @param expires std::time_t to write the expiration time to. - /// @param wait Time in seconds while pinging server. - /// @return True on success. False on failure. - bool get_sign_in_data(std::string &code, std::time_t &expires, int &wait); + /// @brief Returns whether or not a sign in is required to use drive. AKA the refresh token is missing. + bool sign_in_required() const; - /// @brief This function waits for the response from the server that the user signed in. - /// @return True on success. False on failure. - bool sign_in(void); + /// @brief Requests the the necessary data from Google to login. + /// @param message String to store the message to. + /// @param code String to store the device code from Google. + /// @param expiration time_t to store when the sign in window closes. + /// @param wait Int to store the time in seconds between server pings. + bool get_sign_in_data(std::string &message, std::string &code, std::time_t &expiration, int &wait); + /// @brief This is the function that pings the server to see if the user entered the code yet. + /// @param code The code Google reponded with for verification. + /// @return If the user signs in, true. If not, false; + bool poll_sign_in(std::string_view code); private: - /// @brief Client ID. + /// @brief Google client ID. std::string m_clientId; - /// @brief Client secret. + /// @brief Google client secret. std::string m_clientSecret; - /// @brief Authorization token. + /// @brief Authentication token. std::string m_token; - /// @brief Token refresh token. + /// @brief Token used for refreshing token when it expires. std::string m_refreshToken; - /// @brief Authentication header string. + /// @brief This is to save the authentication header string instead of recreating it over and over. std::string m_authHeader; - /// @brief Calculated time the token expires in. + /// @brief This is the calculate time when the auth token expires. std::time_t m_tokenExpires; - /// @brief Gets and sets the root ID of Google Drive using V2 of the API. - /// @return True on success. False on failure. - bool get_set_root_id(void); + /// @brief Uses V2 of Drive's API to get the root directory ID from Google. + bool get_root_id(); - /// @brief Returns whether or not the token is still valid with a grace period of 10 seconds. - /// @return True if the token is still valid. False if it isn't. - bool token_is_valid(void) const; + /// @brief Returns whether or not the auth token is still valid for use or needs to be refreshed. + bool token_is_valid() const; - /// @brief Forces a refresh of the access token. - /// @return True on success. False on failure. - bool refresh_token(void); + /// @brief Attempts to refresh the auth token if needed. + bool refresh_token(); - /// @brief Requests a listing and processes it. - /// @return True on success. False on failure. - bool request_listing(void); + /// @brief Requests and processes the entire listing for JKSV. + bool request_listing(); - /// @brief Processes a file list from Google Drive. - /// @param json Json object to process. - /// @return True on success. False on failure. + /// @brief Processes and listing + /// @param json Json object to use for parsing. bool process_listing(json::Object &json); - /// @brief Tries to locate a directory according to its ID instead of its name. - /// @param id ID of the directory to search for. - /// @return Iterator to directory found. m_list.end() on failure. - remote::Storage::List::iterator find_directory_by_id(std::string_view id); - - /// @brief Tries to locate a file according to its ID instead of its name. - /// @param id ID of the file to locate. - /// @return Iterator to the file on success. m_list.end() on failure. - remote::Storage::List::iterator find_file_by_id(std::string_view id); - - /// @brief Checks for, logs, and returns if an error is detected within the json::Object passed. - /// @param json json::Object to check. - /// @return True if an error was found. False if one wasn't. - bool error_occurred(json::Object &json); + /// @brief Performs a quick check on the json object passed for errors. + /// @param json Json object to check. + /// @param log Whether or not to log the error. + /// @note This doesn't catch every error. Google's errors aren't consistent. + bool error_occurred(json::Object &json, bool log = true); }; } // namespace remote diff --git a/include/remote/Item.hpp b/include/remote/Item.hpp index 8681151..820ac29 100644 --- a/include/remote/Item.hpp +++ b/include/remote/Item.hpp @@ -16,23 +16,23 @@ namespace remote /// @brief Returns the name of the item. /// @return Name of the item. - std::string_view get_name(void) const; + std::string_view get_name() const; /// @brief Returns the id of the item. /// @return ID of the item. - std::string_view get_id(void) const; + std::string_view get_id() const; /// @brief Returns the parent id of the item. /// @return Parent ID of the item. - std::string_view get_parent_id(void) const; + std::string_view get_parent_id() const; /// @brief Gets the size of the item. /// @return Size of the item in bytes. - size_t get_size(void) const; + size_t get_size() const; /// @brief Returns whether or not the item is a directory. /// @return Whether or not the item is a directory. - bool is_directory(void) const; + bool is_directory() const; /// @brief Sets the name of the item. /// @param name New name of the item. diff --git a/include/remote/Storage.hpp b/include/remote/Storage.hpp index 07215f6..aeb5e0f 100644 --- a/include/remote/Storage.hpp +++ b/include/remote/Storage.hpp @@ -8,102 +8,113 @@ namespace remote { - /// @brief This is the base storage class. class Storage { public: - // Definition for remote file listing. + /// @brief Definition to make things easier to type. + using DirectoryListing = std::vector; + + /// @brief This makes writing some stuff for these classes way easier. using List = std::vector; - /// @brief Default storage constructor. - Storage(void) = default; + /// @brief This just allocates the curl::Handle. + Storage(); - /// @brief Returns whether or not the storage type/driver was initialized successfully. - /// @return True if it was. False if it wasn't. - bool is_initialized(void) const; + /// @brief Returns whether or not the Storage type was successfully. initialized. + bool is_initialized() const; + // Directory functions. /// @brief Returns whether or not a directory with name exists within the current parent. - /// @param name Name of the directory. - /// @return True if one was found. False if one wasn't. + /// @param name Name of the directory to search for. bool directory_exists(std::string_view name); - /// @brief Tries to locate and get the ID of a directory within the current parent. - /// @param name Name of the directory to get. - /// @param idOut String to write the ID to if it's found. - /// @return True on success. False on failure. - bool get_directory_id(std::string_view name, std::string &idOut); + /// @brief Returns the parent to the root directory. + void return_to_root(); - /// @brief Returns whether or not a file exists within the current parent directory. - /// @param name Name of the file to search for. - /// @return True if one is found. False if one isn't. - bool file_exists(std::string_view name); + /// @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); - /// @brief Tries to locate and get the ID of a file within the current parent directory. - /// @param name Name of the file to search for - /// @param idOut String it write the ID to. - /// @return True on success. False when no file is found. - bool get_file_id(std::string_view name, std::string &idOut); + /// @brief Changes the current parent directory. + /// @param Item Item to use as the current parent directory. + void change_directory(remote::Item *item); - /// @brief Virtual function to change the current parent/working directory. - /// @param name Name or ID of the directory to change to. - virtual void change_directory(std::string_view name) = 0; - - /// @brief Virtual function to create a new directory within the current parent directory. + /// @brief Creates a directory in the current parent directory. /// @param name Name of the directory to create. - /// @return True on success. False on failure. virtual bool create_directory(std::string_view name) = 0; - /// @brief Virtual function to delete a directory from within the current parent directory. - /// @param name Name or ID of the directory to delete. - /// @return True on success. False on failure. - virtual bool delete_directory(std::string_view name) = 0; + /// @brief Searches the list for a directory matching name and the current parent. + /// @param name Name of the directory to search for. + /// @return Pointer to the item representing the directory on success. nullptr on failure/not found. + remote::Item *get_directory_by_name(std::string_view name); - /// @brief Virtual function to upload a file to remote storage. - /// @param source fslib::Path of the source file to upload. - /// @return True on success. False on failure. + /// @brief Retrieves a listing of the items in the current parent directory. + /// @param out List to fill. + remote::Storage::DirectoryListing get_directory_listing(); + + // File functions. + /// @brief Returns whether a file with name exists within the current directory. + /// @param name Name of the file. + bool file_exists(std::string_view name); + + /// @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; - /// @brief Virtual function to patch or update a file. - /// @param name Name or ID of the file to update. - /// @param source Source path to upload from. - /// @return True on success. False on failure. - virtual bool patch_file(std::string_view name, const fslib::Path &source) = 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; - /// @brief Virtual function for downloading a file. - /// @param name Name or ID of the file. - /// @param destination fslib::Path containing the destination path. - /// @return True on success. False on failure. - virtual bool download_file(std::string_view name, const fslib::Path &destination) = 0; + /// @brief Downloads a file from the remote. + /// @param item Item to download. + /// @param destination Path to download the file to. + virtual bool download_file(const remote::Item *file, const fslib::Path &destination) = 0; - /// @brief Virtual function to delete a file from the remote storage. - /// @param name Name or ID of the file to delete. - /// @return True on success. False on failure. - virtual bool delete_file(std::string_view name) = 0; + /// @brief Searches the list for a file matching name and the current parent. + /// @param name Name of the file to search for. + /// @return Pointer to the item if located. nullptr if not. + remote::Item *get_file_by_name(std::string_view name); + + // General functions that apply to both. + /// @brief Deletes a file or folder from the remote. + /// @param item Item to delete. + virtual bool delete_item(const remote::Item *item) = 0; + + /// @brief Returns whether or not the remote storage type supports UTF-8 for names or requires path safe titles. + bool supports_utf8() const; protected: - /// @brief Whether or not initialization of the remote storage service was successful. + /// @brief This is the size of the buffers used for snprintf'ing URLs together. + static constexpr size_t SIZE_URL_BUFFER = 0x401; + + /// @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 The root directory of the remote storage. + /// @brief This is the root directory of the remote storage. std::string m_root; - /// @brief Current parent directory. + /// @brief This stores the current parent. std::string m_parent; - /// @brief Current storage listing. - Storage::List m_list; - - /// @brief CURL handle. + /// @brief Curl handle. curl::Handle m_curl; - /// @brief Searches for a directory in the current parent directory. - /// @param name Name of the directory to search for. - /// @return Iterator to found directory or m_list.end() on failure. - Storage::List::iterator find_directory(std::string_view name); + /// @brief This is the main remote listing. + Storage::List m_list; - /// @brief Searches for a file named file within the current parent directory. + /// @brief Searches the list for a directory matching name and the current parent. + /// @param name Name to search for. + Storage::List::iterator find_directory_by_name(std::string_view name); + + /// @brief Searches to find if a file with name exists within the current parent. /// @param name Name of the file to search for. - /// @return Iterator to the file found. m_list.end() on failure. - Storage::List::iterator find_file(std::string_view name); + Storage::List::iterator find_file_by_name(std::string_view name); }; } // namespace remote diff --git a/include/remote/URL.hpp b/include/remote/URL.hpp new file mode 100644 index 0000000..87d74ff --- /dev/null +++ b/include/remote/URL.hpp @@ -0,0 +1,60 @@ +#pragma once +#include + +namespace remote +{ + /// @brief This is a class to make URLs easier to build for Google Drive and WebDav. + /// @note Normally I don't go this route, but it makes things easier. + class URL final + { + public: + /// @brief Default + URL() = default; + + /// @brief Constructs a URL with a base URL already in place. + /// @param base String_view containing the base URL. + URL(std::string_view base); + + /// @brief Copy constructor. + /// @param url URL to copy. + URL(const URL &url); + + /// @brief Move constructor. + /// @param url URL to move. + URL(URL &&url); + + /// @brief Makes a copy of the URL passed. + /// @param url remote::URL instance to make a copy of. + URL &operator=(const URL &url); + + /// @brief Move operator. + /// @param url URL to move. + URL &operator=(URL &&url); + + /// @brief Sets the base URL. Basically resets the string back to square 0. + /// @param base Base URL to start with. + URL &set_base(std::string_view base); + + /// @brief Appends the string passed as a path to the URL + /// @param path Path to append; + URL &append_path(std::string_view path); + + /// @brief Appends a string parameter + /// @param param Parameter to append. + /// @param value Value of the parameter to append. + URL &append_parameter(std::string_view param, std::string_view value); + + /// @brief Appends a trailing slash if needed. + URL &append_slash(); + + /// @brief Returns the C string of the url string. + const char *get() const; + + private: + /// @brief This is where the actual URL is held. + std::string m_url; + + /// @brief This checks and appends the necessary separator to the URL string. + void append_separator(); + }; +} // namespace remote diff --git a/include/remote/WebDav.hpp b/include/remote/WebDav.hpp new file mode 100644 index 0000000..5930493 --- /dev/null +++ b/include/remote/WebDav.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "remote/Storage.hpp" +#include "remote/URL.hpp" +#include + +namespace remote +{ + class WebDav final : public remote::Storage + { + public: + /// @brief Loads the WebDav config from the SD card and loads the listing. + WebDav(); + + /// @brief Creates a new directory on the WebDav server. + /// @param name Name of the directory to create. + bool create_directory(std::string_view name) override; + + /// @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; + + /// @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; + + /// @brief Downloads the passed file from the WebDav server. + /// @param file Pointer to the file to download. + /// @param destination Path to write the downloaded data from. + bool download_file(const remote::Item *item, const fslib::Path &destination) override; + + /// @brief Deletes the target item from the WebDav server. + /// @param item Item to delete. + bool delete_item(const remote::Item *item) override; + + private: + /// @brief Origin or server address. + std::string m_origin; + + /// @brief Username for curl requests. + std::string m_username; + + /// @brief Password for curl requests. + std::string m_password; + + /// @brief Appends the username and password to a WebDav curl request. + void append_credentials(); + + /// @brief Requests PROPFIND to the url passed. + /// @param url URL to PROPFIND with. + /// @param xml String to record XML response to. + bool prop_find(const remote::URL &url, std::string &xml); + + /// @brief Processes a PROPFIND XML response. + /// @param xml XML response. + bool process_listing(std::string_view xml); + }; +} // namespace remote diff --git a/include/remote/remote.hpp b/include/remote/remote.hpp new file mode 100644 index 0000000..a290a19 --- /dev/null +++ b/include/remote/remote.hpp @@ -0,0 +1,19 @@ +#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"; + + /// @brief Initializes the Storage instance to Google Drive. + void initialize_google_drive(); + + /// @brief Initializes the Storage instance to WebDav + void initialize_webdav(); + + /// @brief Returns the pointer to the Storage instance. + remote::Storage *get_remote_storage(); +} // namespace remote diff --git a/include/strings.hpp b/include/strings.hpp index 40cbbce..699a528 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -4,7 +4,7 @@ namespace strings { // Attempts to load strings from file in RomFS. - bool initialize(void); + bool initialize(); // Returns string with name and index. Returns nullptr if string doesn't exist. const char *get_by_name(std::string_view name, int index); @@ -41,5 +41,6 @@ namespace strings static constexpr std::string_view POP_MESSAGES_SAVE_CREATE = "PopMessagesSaveCreate"; static constexpr std::string_view POP_MESSAGES_TITLE_OPTIONS = "PopMessagesTitleOptions"; static constexpr std::string_view GOOGLE_DRIVE_STRINGS = "GoogleDriveStrings"; + static constexpr std::string_view WEBDAV_STRINGS = "WebDavStrings"; } // namespace names } // namespace strings diff --git a/include/system/ProgressTask.hpp b/include/system/ProgressTask.hpp index 4295201..7bd001e 100644 --- a/include/system/ProgressTask.hpp +++ b/include/system/ProgressTask.hpp @@ -25,11 +25,11 @@ namespace sys /// @brief Returns the goal value. /// @return Goal - double get_goal(void) const; + double get_goal() const; /// @brief Returns the current progress. /// @return Current progress. - double get_current(void) const; + double get_current() const; private: // Current value and goal diff --git a/include/system/Task.hpp b/include/system/Task.hpp index 1e62312..565b1cd 100644 --- a/include/system/Task.hpp +++ b/include/system/Task.hpp @@ -35,11 +35,11 @@ namespace sys /// @brief Returns if the thread has signaled it's finished running. /// @return True if the thread is still running. False if it isn't. - bool is_running(void) const; + bool is_running() const; /// @brief Allows thread to signal it's finished. /// @note Spawned task threads must call this when their work is finished. - void finished(void); + void finished(); /// @brief Sets the task/threads current status string. Thread safe. /// @param format Format of string. @@ -48,7 +48,7 @@ namespace sys /// @brief Returns the status string. Thread safe. /// @return Copy of the status string. - std::string get_status(void); + std::string get_status(); private: // Whether task is still running. diff --git a/include/system/Timer.hpp b/include/system/Timer.hpp index 752e6b5..bd7c010 100644 --- a/include/system/Timer.hpp +++ b/include/system/Timer.hpp @@ -9,7 +9,7 @@ namespace sys { public: /// @brief Default constructor. - Timer(void) = default; + Timer() = default; /// @brief Constructs a new timer. /// @param triggerTicks Number of ticks the timer is triggered at. @@ -21,15 +21,16 @@ namespace sys /// @brief Updates and returns if the timer was triggered. /// @return True if timer is triggered. False if it isn't. - bool is_triggered(void); + bool is_triggered(); /// @brief Forces the timer to restart. - void restart(void); + void restart(); private: - // Beginning ticks. + /// @brief Tick count when the timer starts. uint64_t m_startingTicks; - // How many ticks to trigger the timer. + + /// @brief Number of ticks to trigger on. uint64_t m_triggerTicks; }; } // namespace sys diff --git a/include/ui/ColorMod.hpp b/include/ui/ColorMod.hpp index d01f5bb..2a6915e 100644 --- a/include/ui/ColorMod.hpp +++ b/include/ui/ColorMod.hpp @@ -9,14 +9,14 @@ namespace ui { public: /// @brief Default constructor. - ColorMod(void) = default; + ColorMod() = default; /// @brief Updates the color modification variable. - void update(void); + void update(); /// @brief Operator that allows using this as an sdl::Color directly. /// @note Since all of these pulse the same color, no sense in not doing this. - operator sdl::Color(void) const; + operator sdl::Color() const; private: /// @brief Whether we're adding or subtracting from the color value. diff --git a/include/ui/Element.hpp b/include/ui/Element.hpp index 6f13069..f4ed710 100644 --- a/include/ui/Element.hpp +++ b/include/ui/Element.hpp @@ -8,7 +8,7 @@ namespace ui { public: /// @brief Default constructor. - Element(void) = default; + Element() = default; /// @brief Virtual destructor. virtual ~Element() {}; diff --git a/include/ui/IconMenu.hpp b/include/ui/IconMenu.hpp index 70a0e3c..2dbfbcb 100644 --- a/include/ui/IconMenu.hpp +++ b/include/ui/IconMenu.hpp @@ -8,7 +8,7 @@ namespace ui { public: /// @brief Default constructor. - IconMenu(void) = default; + IconMenu() = default; /// @brief This constructor calls initialize. /// @param x X coordinate to render the menu to. diff --git a/include/ui/Menu.hpp b/include/ui/Menu.hpp index 59eb786..4425f65 100644 --- a/include/ui/Menu.hpp +++ b/include/ui/Menu.hpp @@ -41,7 +41,7 @@ namespace ui /// @brief Returns the index of the currently selected menu option. /// @return Index of currently selected option. - int get_selected(void) const; + int get_selected() const; /// @brief Sets the selected item. /// @param selected Value to set selected to. @@ -52,7 +52,7 @@ namespace ui void set_width(int width); /// @brief Resets the menu and returns it to an empty, default state. - void reset(void); + void reset(); protected: /// @brief X coordinate menu is rendered to. diff --git a/include/ui/PopMessageManager.hpp b/include/ui/PopMessageManager.hpp index 598dd5d..abc92a6 100644 --- a/include/ui/PopMessageManager.hpp +++ b/include/ui/PopMessageManager.hpp @@ -31,10 +31,10 @@ namespace ui PopMessageManager &operator=(PopMessageManager &&) = delete; /// @brief Updates and processes message queue. - static void update(void); + static void update(); /// @brief Renders messages to screen. - static void render(void); + static void render(); /// @brief Pushes a new message to the queue for processing. /// @param displayTicks Number of ticks for the message to be displayed until it is purged. @@ -47,9 +47,9 @@ namespace ui private: // Only one instance allowed. - PopMessageManager(void) = default; + PopMessageManager() = default; // Returns the only instance. - static PopMessageManager &get_instance(void) + static PopMessageManager &get_instance() { static PopMessageManager manager; return manager; diff --git a/include/ui/SlideOutPanel.hpp b/include/ui/SlideOutPanel.hpp index c933c6e..a5d5eb5 100644 --- a/include/ui/SlideOutPanel.hpp +++ b/include/ui/SlideOutPanel.hpp @@ -33,32 +33,32 @@ namespace ui void render(SDL_Texture *target, bool hasFocus); /// @brief Clears the target to a semi-transparent black. To do: Maybe not hard coded color. - void clear_target(void); + void clear_target(); /// @brief Resets the panel back to its default state. - void reset(void); + void reset(); /// @brief Closes the panel. - void close(void); + void close(); /// @brief Returns if the panel is fully open. /// @return If the panel is fully open. - bool is_open(void) const; + bool is_open() const; /// @brief Returns if the panel is fully closed. /// @return If the panel is fully closed. - bool is_closed(void) const; + bool is_closed() const; /// @brief Pushes a new element to the element vector. /// @param newElement New element to push. void push_new_element(std::shared_ptr newElement); /// @brief Clears the element vector, freeing them in the process. - void clear_elements(void); + void clear_elements(); /// @brief Returns a pointer to the render target of the panel. /// @return Raw SDL_Texture pointer to target. - SDL_Texture *get_target(void); + SDL_Texture *get_target(); private: /// @brief Bool for whether panel is fully open or not. diff --git a/include/ui/TextScroll.hpp b/include/ui/TextScroll.hpp index b6a74f9..9992a8b 100644 --- a/include/ui/TextScroll.hpp +++ b/include/ui/TextScroll.hpp @@ -10,7 +10,7 @@ namespace ui { public: /// @brief This is only here so I can get around the backup menu having static members. - TextScroll(void) = default; + TextScroll() = default; /// @brief Constructor for TextScroll. /// @param text Text to create the textscroll with. diff --git a/include/ui/TitleTile.hpp b/include/ui/TitleTile.hpp index 57e5458..9b9f11e 100644 --- a/include/ui/TitleTile.hpp +++ b/include/ui/TitleTile.hpp @@ -23,15 +23,15 @@ namespace ui void render(SDL_Texture *target, int x, int y); /// @brief Resets the width and height of the tile. - void reset(void); + void reset(); /// @brief Returns the render width in pixels. /// @return Render width. - int get_width(void) const; + int get_width() const; /// @brief Returns the render height in pixels. /// @return Render height. - int get_height(void) const; + int get_height() const; private: /// @brief Width in pixels to render icon at. diff --git a/include/ui/TitleView.hpp b/include/ui/TitleView.hpp index f0c6927..72bbcfe 100644 --- a/include/ui/TitleView.hpp +++ b/include/ui/TitleView.hpp @@ -30,13 +30,13 @@ namespace ui /// @brief Returns index of the currently selected tile. /// @return Index of currently selected tile. - int get_selected(void) const; + int get_selected() const; /// @brief Forces a refresh of the view. - void refresh(void); + void refresh(); /// @brief Resets the view to its default, empty state. - void reset(void); + void reset(); private: /// @brief Pointer to user passed. diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index d9fa82d..558af37 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -98,10 +98,11 @@ "Are you sure you really want to delete #%s#?" ], "BackupMenuStatus": [ - "Processing save data meta file..." + "Processing save data meta file...", + "Uploading #%s# to remote storage..." ], "BackupMenuPops": [ - "System save data cannot be restored to the system!" + "Writing to system data is disabled!" ], "DeletingFiles": [ "Deleting #%s#..." @@ -195,8 +196,12 @@ "Error setting new output path!" ], "GoogleDriveStrings": [ - "In order to use Google Drive, please go to #%s# on your phone or PC and type in the code >%s>.", + "To continue, go to #%s# and enter >%s>!", "Successfully signed in to Google Drive!", "Google Drive sign in failed!" + ], + "WebDavStrings": [ + "WebDav successfully started!", + "WebDav failed!" ] } diff --git a/source/JKSV.cpp b/source/JKSV.cpp index 8169818..6fe2305 100644 --- a/source/JKSV.cpp +++ b/source/JKSV.cpp @@ -8,6 +8,7 @@ #include "fslib.hpp" #include "input.hpp" #include "logger.hpp" +#include "remote/remote.hpp" #include "sdl.hpp" #include "strings.hpp" #include "ui/PopMessageManager.hpp" @@ -42,7 +43,7 @@ static bool initialize_service(Result (*function)(Args...), const char *serviceN return true; } -JKSV::JKSV(void) +JKSV::JKSV() { // Start with this. appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); @@ -122,6 +123,14 @@ JKSV::JKSV(void) // Push initial main menu state. StateManager::push_state(std::make_shared()); + if (fslib::file_exists(remote::PATH_GOOGLE_DRIVE_CONFIG)) + { + remote::initialize_google_drive(); + } + else if (fslib::file_exists(remote::PATH_WEBDAV_CONFIG)) + { + remote::initialize_webdav(); + } m_isRunning = true; } @@ -146,12 +155,12 @@ JKSV::~JKSV() appletSetCpuBoostMode(ApmCpuBoostMode_Normal); } -bool JKSV::is_running(void) const +bool JKSV::is_running() const { return m_isRunning; } -void JKSV::update(void) +void JKSV::update() { input::update(); @@ -167,7 +176,7 @@ void JKSV::update(void) ui::PopMessageManager::update(); } -void JKSV::render(void) +void JKSV::render() { sdl::frame_begin(colors::CLEAR_COLOR); diff --git a/source/StateManager.cpp b/source/StateManager.cpp index f5027c3..5a30ad3 100644 --- a/source/StateManager.cpp +++ b/source/StateManager.cpp @@ -1,6 +1,6 @@ #include "StateManager.hpp" -void StateManager::update(void) +void StateManager::update() { // Grab the instance. StateManager &instance = StateManager::get_instance(); @@ -35,7 +35,7 @@ void StateManager::update(void) instance.sm_stateVector.back()->update(); } -void StateManager::render(void) +void StateManager::render() { // Instance. StateManager &instance = StateManager::get_instance(); @@ -47,7 +47,7 @@ void StateManager::render(void) } } -bool StateManager::back_is_closable(void) +bool StateManager::back_is_closable() { // Instance. StateManager &instance = StateManager::get_instance(); @@ -78,7 +78,7 @@ void StateManager::push_state(std::shared_ptr newState) instance.sm_stateVector.push_back(newState); } -StateManager &StateManager::get_instance(void) +StateManager &StateManager::get_instance() { static StateManager instance; return instance; diff --git a/source/appstates/AppState.cpp b/source/appstates/AppState.cpp index 37e7889..c08fbc9 100644 --- a/source/appstates/AppState.cpp +++ b/source/appstates/AppState.cpp @@ -17,22 +17,22 @@ AppState::~AppState() } } -void AppState::deactivate(void) +void AppState::deactivate() { m_isActive = false; } -void AppState::reactivate(void) +void AppState::reactivate() { m_isActive = true; } -bool AppState::is_active(void) const +bool AppState::is_active() const { return m_isActive; } -void AppState::give_focus(void) +void AppState::give_focus() { m_hasFocus = true; } @@ -42,12 +42,12 @@ void AppState::take_focus() m_hasFocus = false; } -bool AppState::has_focus(void) const +bool AppState::has_focus() const { return m_hasFocus; } -bool AppState::is_closable(void) const +bool AppState::is_closable() const { return m_isClosable; } diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index 6403e3c..6d3bafb 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -9,6 +9,7 @@ #include "input.hpp" #include "keyboard.hpp" #include "logger.hpp" +#include "remote/remote.hpp" #include "sdl.hpp" #include "strings.hpp" #include "stringutil.hpp" @@ -23,7 +24,7 @@ namespace constexpr size_t SIZE_BACKUP_NAME_LENGTH = 0x80; /// @brief This is just so there isn't random .zip comparisons everywhere. - constexpr std::string_view STRING_ZIP_EXTENSION = ".zip"; + const char *STRING_ZIP_EXTENSION = ".zip"; } // namespace // Declarations here. Definitions after class. @@ -39,12 +40,16 @@ static void overwrite_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, FsSaveDataType saveType) : m_user(user), m_titleInfo(titleInfo), m_saveType(saveType), m_directoryPath(config::get_working_directory() / m_titleInfo->get_path_safe_title()), m_dataStruct(std::make_shared()) { + static const char *STRING_ERROR_CONSTRUCTING = "Error constructing BackupMenuState: %s"; + if (!sm_isInitialized) { sm_panelWidth = sdl::text::get_width(22, strings::get_by_name(strings::names::CONTROL_GUIDES, 2)) + 64; @@ -59,11 +64,20 @@ BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo, F sm_isInitialized = true; } + // Check to make sure the directory exists. + fslib::Path targetPath = config::get_working_directory() / titleInfo->get_path_safe_title(); + // This failing will immediately deactivate the state. The user *probably* won't even notice it! + if (!fslib::directory_exists(targetPath) && !fslib::create_directory(targetPath)) + { + logger::log(STRING_ERROR_CONSTRUCTING, "Error creating target directory on SD!"); + AppState::deactivate(); + } + // Fill this out. Target path is not set here. - m_dataStruct->m_user = m_user; - m_dataStruct->m_titleInfo = m_titleInfo; - m_dataStruct->m_journalSize = m_titleInfo->get_journal_size(m_saveType); - m_dataStruct->m_spawningState = this; + m_dataStruct->user = m_user; + m_dataStruct->titleInfo = m_titleInfo; + m_dataStruct->journalSize = m_titleInfo->get_journal_size(m_saveType); + m_dataStruct->spawningState = this; // String for the top of the panel. std::string panelString = @@ -78,6 +92,30 @@ BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo, F // This just fills out the menu. BackupMenuState::refresh(); + + // This is here so I can bail if the remote isn't setup without causing anymore problems. + remote::Storage *remote = remote::get_remote_storage(); + if (!remote || !remote->is_initialized()) + { + return; + } + + // First make sure the directory exists. + std::string_view remoteTitle = remote->supports_utf8() ? titleInfo->get_title() : titleInfo->get_path_safe_title(); + if (!remote->directory_exists(remoteTitle) && !remote->create_directory(remoteTitle)) + { + logger::log(STRING_ERROR_CONSTRUCTING, "Error creating directory for remote storage!"); + return; + } + + // Now set it as the parent. + remote::Item *parent = remote->get_directory_by_name(remoteTitle); + if (!parent) + { + logger::log(STRING_ERROR_CONSTRUCTING, "Error getting parent for remote! This shouldn't be able to happen..."); + return; + } + remote->change_directory(parent); } BackupMenuState::~BackupMenuState() @@ -86,54 +124,22 @@ BackupMenuState::~BackupMenuState() fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); // Close the panel. sm_slidePanel->clear_elements(); + + // Return the remote to root. + remote::Storage *remote = remote::get_remote_storage(); + if (remote && remote->is_initialized()) + { + remote->return_to_root(); + } } -void BackupMenuState::update(void) +void BackupMenuState::update() { bool hasFocus = AppState::has_focus(); if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && m_saveHasData) { - // get name for backup. - char backupName[SIZE_BACKUP_NAME_LENGTH + 1] = {0}; - - // Set backup to default. - std::snprintf(backupName, - SIZE_BACKUP_NAME_LENGTH, - "%s - %s", - m_user->get_path_safe_nickname(), - stringutil::get_date_string().c_str()); - - if (!input::button_held(HidNpadButton_ZR) && - !keyboard::get_input(SwkbdType_QWERTY, - backupName, - strings::get_by_name(strings::names::KEYBOARD_STRINGS, 0), - backupName, - SIZE_BACKUP_NAME_LENGTH)) - { - return; - } - // To do: This isn't a good way to check for this... Check to make sure zip has zip extension. - if (config::get_by_key(config::keys::EXPORT_TO_ZIP) && std::strstr(backupName, ".zip") == NULL) - { - // I guess this is a safer way to accomplish this? - std::strncat(backupName, ".zip", SIZE_BACKUP_NAME_LENGTH); - } - else if (!config::get_by_key(config::keys::EXPORT_TO_ZIP) && !std::strstr(backupName, ".zip") && - !fslib::directory_exists(m_directoryPath / backupName) && - !fslib::create_directory(m_directoryPath / backupName)) - { - return; - } - - fslib::Path targetPath = m_directoryPath / backupName; - - // Create the state. - auto createNewBackup = - std::make_shared(create_new_backup, m_user, m_titleInfo, targetPath, this); - - // Push the task. - StateManager::push_state(createNewBackup); + BackupMenuState::name_and_create_backup(); } else if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && !m_saveHasData) { @@ -143,89 +149,29 @@ void BackupMenuState::update(void) } else if (input::button_pressed(HidNpadButton_A) && m_saveHasData && sm_backupMenu->get_selected() > 0) { - int selected = sm_backupMenu->get_selected() - 1; - - std::string queryString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 0), - m_directoryListing[selected]); - - // Set the target path quick. - m_dataStruct->m_targetPath = m_directoryPath / m_directoryListing[selected]; - - auto confirm = std::make_shared>( - queryString, - config::get_by_key(config::keys::HOLD_FOR_OVERWRITE), - overwrite_backup, - m_dataStruct); - - StateManager::push_state(confirm); + BackupMenuState::confirm_backup_overwrite(); } else if (input::button_pressed(HidNpadButton_A) && !m_saveHasData && sm_backupMenu->get_selected() > 0) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 0)); } - else if (input::button_pressed(HidNpadButton_Y) && sm_backupMenu->get_selected() > 0 && - (m_saveType != FsSaveDataType_System || config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM))) + else if (input::button_pressed(HidNpadButton_Y) && sm_backupMenu->get_selected() > 0) { - // Need to account for new at the top. - int selected = sm_backupMenu->get_selected() - 1; - - // Gonna need to test this quick. - fslib::Path targetPath = m_directoryPath / m_directoryListing[selected]; - - // This is a quick check to avoid restoring blanks. - if (fslib::directory_exists(targetPath) && !fs::directory_has_contents(targetPath)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 1)); - return; - } - else if (fslib::file_exists(targetPath) && std::strcmp("zip", targetPath.get_extension()) == 0 && - !fs::zip_has_contents(targetPath)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 1)); - return; - } - - // Set target path - m_dataStruct->m_targetPath = m_directoryPath / m_directoryListing[selected]; - - std::string queryString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 1), - m_directoryListing[selected]); - - auto confirm = std::make_shared>( - queryString, - config::get_by_key(config::keys::HOLD_FOR_RESTORATION), - restore_backup, - m_dataStruct); - - StateManager::push_state(confirm); + BackupMenuState::confirm_restore(); } else if (input::button_pressed(HidNpadButton_X) && sm_backupMenu->get_selected() > 0) { - // Selected needs to be offset by one to account for New - int selected = sm_backupMenu->get_selected() - 1; - - // Set path quick. - m_dataStruct->m_targetPath = m_directoryPath / m_directoryListing[selected]; - - // get the string. - std::string queryString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 2), - m_directoryListing[selected]); - - auto confirm = std::make_shared>( - queryString, - config::get_by_key(config::keys::HOLD_FOR_DELETION), - delete_backup, - m_dataStruct); - - // Create/push new state. - StateManager::push_state(confirm); + BackupMenuState::confirm_delete(); } + else if (input::button_pressed(HidNpadButton_ZR) && sm_backupMenu->get_selected() > 0) + { + int selected = sm_backupMenu->get_selected() - 1; + m_dataStruct->targetPath = m_directoryPath / m_directoryListing[selected]; + auto upload = std::make_shared(upload_backup, m_dataStruct); + StateManager::push_state(upload); + } + else if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); @@ -244,7 +190,7 @@ void BackupMenuState::update(void) sm_backupMenu->update(hasFocus); } -void BackupMenuState::render(void) +void BackupMenuState::render() { // Save this locally. bool hasFocus = AppState::has_focus(); @@ -278,7 +224,7 @@ void BackupMenuState::render(void) sm_slidePanel->render(NULL, hasFocus); } -void BackupMenuState::refresh(void) +void BackupMenuState::refresh() { m_directoryListing.open(m_directoryPath); if (!m_directoryListing) @@ -294,7 +240,7 @@ void BackupMenuState::refresh(void) } } -void BackupMenuState::save_data_written(void) +void BackupMenuState::save_data_written() { if (!m_saveHasData) { @@ -302,6 +248,133 @@ void BackupMenuState::save_data_written(void) } } +void BackupMenuState::name_and_create_backup() +{ + static const char *STRING_ERROR_CREATE_NEW = "Error creating new backup: %s"; + + bool zip = config::get_by_key(config::keys::EXPORT_TO_ZIP); + char backupName[SIZE_BACKUP_NAME_LENGTH + 1] = {0}; + + std::snprintf(backupName, + SIZE_BACKUP_NAME_LENGTH, + "%s - %s", + m_user->get_path_safe_nickname(), + stringutil::get_date_string().c_str()); + + // ZR is a shortcut to skip the keyboard popping up. + if (!input::button_held(HidNpadButton_ZR) && + !keyboard::get_input(SwkbdType_QWERTY, + backupName, + strings::get_by_name(strings::names::KEYBOARD_STRINGS, 0), + backupName, + SIZE_BACKUP_NAME_LENGTH)) + { + return; + } + + if (zip && !std::strstr(backupName, STRING_ZIP_EXTENSION)) + { + // This could have potential consequences... + std::strncat(backupName, STRING_ZIP_EXTENSION, SIZE_BACKUP_NAME_LENGTH); + } + else if (!zip && !std::strstr(backupName, STRING_ZIP_EXTENSION) && + !fslib::directory_exists(m_directoryPath / backupName) && + !fslib::create_directory(m_directoryPath / backupName)) + { + logger::log(STRING_ERROR_CREATE_NEW, "Error creating export directory."); + return; + } + + // We should now be able to create the final target path. + fslib::Path targetPath = m_directoryPath / backupName; + + auto newBackupTask = std::make_shared(create_new_backup, m_user, m_titleInfo, targetPath, this); + StateManager::push_state(newBackupTask); +} + +void BackupMenuState::confirm_backup_overwrite() +{ + // This has one subtracted to account for New Backup. + int selected = sm_backupMenu->get_selected() - 1; + + std::string confirmationString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 0), + m_directoryListing[selected]); + + // This needs a new target path to pass. + m_dataStruct->targetPath = m_directoryPath / m_directoryListing[selected]; + + auto confirm = std::make_shared>( + confirmationString, + config::get_by_key(config::keys::HOLD_FOR_OVERWRITE), + overwrite_backup, + m_dataStruct); + + StateManager::push_state(confirm); +} + +void BackupMenuState::confirm_restore() +{ + if ((m_saveType == FsSaveDataType_System || m_saveType == FsSaveDataType_SystemBcat) && + !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) + { + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::BACKUPMENU_POPS, 0)); + return; + } + + int selected = sm_backupMenu->get_selected() - 1; + + fslib::Path target = m_directoryPath / m_directoryListing[selected]; + + if (fslib::directory_exists(target) && !fs::directory_has_contents(target)) + { + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 1)); + return; + } + else if (fslib::file_exists(target) && std::strcmp("zip", target.get_extension()) == 0 && + !fs::zip_has_contents(target)) + { + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 1)); + return; + } + + m_dataStruct->targetPath = m_directoryPath / m_directoryListing[selected]; + + std::string confirmationString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 1), + m_directoryListing[selected]); + + auto confirm = std::make_shared>( + confirmationString, + config::get_by_key(config::keys::HOLD_FOR_RESTORATION), + restore_backup, + m_dataStruct); + + StateManager::push_state(confirm); +} + +void BackupMenuState::confirm_delete() +{ + int selected = sm_backupMenu->get_selected() - 1; + + m_dataStruct->targetPath = m_directoryPath / m_directoryListing[selected]; + + std::string confirmationString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 2), + m_directoryListing[selected]); + + auto confirm = std::make_shared>( + confirmationString, + config::get_by_key(config::keys::HOLD_FOR_DELETION), + delete_backup, + m_dataStruct); + + StateManager::push_state(confirm); +} + // This is the function to create new backups. static void create_new_backup(sys::ProgressTask *task, data::User *user, @@ -386,12 +459,12 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptrm_user->get_save_info_by_id(dataStruct->m_titleInfo->get_application_id()); + 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->m_targetPath) && - !fslib::delete_directory_recursively(dataStruct->m_targetPath)) || - (fslib::file_exists(dataStruct->m_targetPath) && !fslib::delete_file(dataStruct->m_targetPath))) + if ((fslib::directory_exists(dataStruct->targetPath) && + !fslib::delete_directory_recursively(dataStruct->targetPath)) || + (fslib::file_exists(dataStruct->targetPath) && !fslib::delete_file(dataStruct->targetPath))) { logger::log(STRING_ERROR_PREFIX, fslib::get_error_string()); task->finished(); @@ -403,9 +476,9 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptrm_targetPath.c_string())) + if (std::strstr(STRING_ZIP_EXTENSION, dataStruct->targetPath.c_string())) { - zipFile backupZip = zipOpen64(dataStruct->m_targetPath.c_string(), APPEND_STATUS_CREATE); + zipFile backupZip = zipOpen64(dataStruct->targetPath.c_string(), APPEND_STATUS_CREATE); if (!backupZip) { logger::log("Error overwriting backup: Couldn't create new zip!"); @@ -441,11 +514,11 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptrm_targetPath.get_extension() == nullptr && fslib::create_directory(dataStruct->m_targetPath)) + else if (dataStruct->targetPath.get_extension() == nullptr && fslib::create_directory(dataStruct->targetPath)) { // Write this quick. { - fslib::Path metaPath = dataStruct->m_targetPath / fs::NAME_SAVE_META; + fslib::Path metaPath = dataStruct->targetPath / fs::NAME_SAVE_META; fslib::File metaFile(metaPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData)); if (metaFile && hasMeta) { @@ -453,7 +526,7 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptrm_targetPath, 0, {}, task); + fs::copy_directory(fs::DEFAULT_SAVE_ROOT, dataStruct->targetPath, 0, {}, task); } task->finished(); } @@ -461,7 +534,7 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptr dataStruct) { // Going to need this later. - FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(dataStruct->m_titleInfo->get_application_id()); + FsSaveDataInfo *saveInfo = dataStruct->user->get_save_info_by_id(dataStruct->titleInfo->get_application_id()); if (!saveInfo) { // To do: Log this. @@ -480,12 +553,12 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptrm_targetPath)) + if (fslib::directory_exists(dataStruct->targetPath)) { { // Process the save meta if it's there. fs::SaveMetaData meta = {0}; - fslib::Path saveMetaPath = dataStruct->m_targetPath / fs::NAME_SAVE_META; + fslib::Path saveMetaPath = dataStruct->targetPath / fs::NAME_SAVE_META; fslib::File saveMetaFile(saveMetaPath, FsOpenMode_Read); if (saveMetaFile && saveMetaFile.read(&meta, sizeof(fs::SaveMetaData)) == sizeof(fs::SaveMetaData)) { @@ -495,15 +568,15 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptrm_targetPath, + fs::copy_directory(dataStruct->targetPath, fs::DEFAULT_SAVE_ROOT, - dataStruct->m_journalSize, + dataStruct->journalSize, fs::DEFAULT_SAVE_MOUNT, task); } - else if (std::strstr(dataStruct->m_targetPath.c_string(), ".zip") != NULL) + else if (std::strstr(dataStruct->targetPath.c_string(), ".zip") != NULL) { - unzFile targetZip = unzOpen64(dataStruct->m_targetPath.c_string()); + unzFile targetZip = unzOpen64(dataStruct->targetPath.c_string()); if (!targetZip) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, @@ -527,22 +600,22 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptrm_journalSize, + dataStruct->journalSize, fs::DEFAULT_SAVE_MOUNT, task); unzClose(targetZip); } else { - fs::copy_file(dataStruct->m_targetPath, + fs::copy_file(dataStruct->targetPath, fs::DEFAULT_SAVE_ROOT, - dataStruct->m_journalSize, + dataStruct->journalSize, fs::DEFAULT_SAVE_MOUNT, task); } // Update this just in case. - dataStruct->m_spawningState->save_data_written(); + dataStruct->spawningState->save_data_written(); task->finished(); } @@ -551,22 +624,37 @@ static void delete_backup(sys::Task *task, std::shared_ptrset_status(strings::get_by_name(strings::names::DELETING_FILES, 0), dataStruct->m_targetPath.c_string()); + task->set_status(strings::get_by_name(strings::names::DELETING_FILES, 0), dataStruct->targetPath.c_string()); } - if (fslib::directory_exists(dataStruct->m_targetPath) && - !fslib::delete_directory_recursively(dataStruct->m_targetPath)) + if (fslib::directory_exists(dataStruct->targetPath) && !fslib::delete_directory_recursively(dataStruct->targetPath)) { logger::log("Error deleting folder backup: %s", fslib::get_error_string()); ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 4)); } - else if (fslib::file_exists(dataStruct->m_targetPath) && !fslib::delete_file(dataStruct->m_targetPath)) + else if (fslib::file_exists(dataStruct->targetPath) && !fslib::delete_file(dataStruct->targetPath)) { logger::log("Error deleting backup: %s", fslib::get_error_string()); ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 4)); } - dataStruct->m_spawningState->refresh(); + 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->targetPath.get_filename().data()); + } + + // To do: This but flashier. + remote::Storage *remote = remote::get_remote_storage(); + + remote->upload_file(dataStruct->targetPath); + task->finished(); } diff --git a/source/appstates/BaseTask.cpp b/source/appstates/BaseTask.cpp index 88846c7..7f61fb8 100644 --- a/source/appstates/BaseTask.cpp +++ b/source/appstates/BaseTask.cpp @@ -7,12 +7,12 @@ namespace constexpr uint64_t TICKS_GLYPH_TRIGGER = 50; } // namespace -BaseTask::BaseTask(void) : AppState(false) +BaseTask::BaseTask() : AppState(false) { m_frameTimer.start(TICKS_GLYPH_TRIGGER); } -void BaseTask::update(void) +void BaseTask::update() { // Just bail if the timer wasn't triggered yet. if (!m_frameTimer.is_triggered()) @@ -30,7 +30,7 @@ void BaseTask::update(void) m_colorMod.update(); } -void BaseTask::render_loading_glyph(void) +void BaseTask::render_loading_glyph() { // This assumes it's being called after the background was dimmed. sdl::text::render(NULL, 56, 673, 32, sdl::text::NO_TEXT_WRAP, m_colorMod, sm_glyphArray.at(m_currentFrame).data()); diff --git a/source/appstates/ExtrasMenuState.cpp b/source/appstates/ExtrasMenuState.cpp index ddab31e..26a820e 100644 --- a/source/appstates/ExtrasMenuState.cpp +++ b/source/appstates/ExtrasMenuState.cpp @@ -26,7 +26,7 @@ namespace }; } // namespace -ExtrasMenuState::ExtrasMenuState(void) +ExtrasMenuState::ExtrasMenuState() : m_extrasMenu(32, 8, 1000, 24, 555), m_renderTarget(sdl::TextureManager::create_load_texture(SECONDARY_TARGET, 1080, @@ -41,7 +41,7 @@ ExtrasMenuState::ExtrasMenuState(void) } } -void ExtrasMenuState::update(void) +void ExtrasMenuState::update() { m_extrasMenu.update(AppState::has_focus()); @@ -68,7 +68,7 @@ void ExtrasMenuState::update(void) } } -void ExtrasMenuState::render(void) +void ExtrasMenuState::render() { m_renderTarget->clear(colors::TRANSPARENT); m_extrasMenu.render(m_renderTarget->get(), AppState::has_focus()); diff --git a/source/appstates/MainMenuState.cpp b/source/appstates/MainMenuState.cpp index 6317d50..4214e15 100644 --- a/source/appstates/MainMenuState.cpp +++ b/source/appstates/MainMenuState.cpp @@ -13,7 +13,7 @@ #include "sdl.hpp" #include "strings.hpp" -MainMenuState::MainMenuState(void) +MainMenuState::MainMenuState() : m_renderTarget(sdl::TextureManager::create_load_texture("mainMenuTarget", 200, 555, @@ -46,7 +46,7 @@ MainMenuState::MainMenuState(void) MainMenuState::initialize_view_states(); } -void MainMenuState::update(void) +void MainMenuState::update() { // Update the main menu. m_mainMenu.update(AppState::has_focus()); @@ -69,13 +69,13 @@ void MainMenuState::update(void) { // Get pointers to data the user option state needs. data::User *targetUser = sm_users.at(selected); - TitleSelectCommon *targetTitleSelect = reinterpret_cast(sm_states.at(selected).get()); + TitleSelectCommon *targetTitleSelect = static_cast(sm_states.at(selected).get()); StateManager::push_state(std::make_shared(targetUser, targetTitleSelect)); } } -void MainMenuState::render(void) +void MainMenuState::render() { // Clear render target by rendering background to it. m_background->render(m_renderTarget->get(), 0, 0); @@ -84,7 +84,7 @@ void MainMenuState::render(void) // render target to screen. m_renderTarget->render(NULL, 0, 91); - // render next state for current user and control guide if this state has focus. + // render next state for current user and control guide if this state has focus. To do: Maybe this different? if (AppState::has_focus()) { sm_states.at(m_mainMenu.get_selected())->render(); @@ -92,7 +92,7 @@ void MainMenuState::render(void) } } -void MainMenuState::initialize_view_states(void) +void MainMenuState::initialize_view_states() { // Constructor should have taken care of the menu and user list. // Start by clearing the vector. @@ -117,12 +117,12 @@ void MainMenuState::initialize_view_states(void) sm_states.push_back(sm_extrasState); } -void MainMenuState::refresh_view_states(void) +void MainMenuState::refresh_view_states() { // For this, we're only looping through the user states to be extra careful because the last two don't have the refresh() function. int userCount = sm_users.size(); for (int i = 0; i < userCount; i++) { - std::static_pointer_cast(sm_states.at(i))->refresh(); + static_cast(sm_states.at(i).get())->refresh(); } } diff --git a/source/appstates/ProgressState.cpp b/source/appstates/ProgressState.cpp index 4a8ee0c..d1831c9 100644 --- a/source/appstates/ProgressState.cpp +++ b/source/appstates/ProgressState.cpp @@ -8,7 +8,7 @@ #include "ui/render_functions.hpp" #include -void ProgressState::update(void) +void ProgressState::update() { // Base routine. BaseTask::update(); @@ -29,7 +29,7 @@ void ProgressState::update(void) m_percentageX = 640 - (sdl::text::get_width(18, m_percentageString.c_str())); } -void ProgressState::render(void) +void ProgressState::render() { // This will dim the background. sdl::render_rect_fill(NULL, 0, 0, 1280, 720, colors::DIM_BACKGROUND); diff --git a/source/appstates/SaveCreateState.cpp b/source/appstates/SaveCreateState.cpp index 92982ed..b261327 100644 --- a/source/appstates/SaveCreateState.cpp +++ b/source/appstates/SaveCreateState.cpp @@ -44,7 +44,7 @@ SaveCreateState::SaveCreateState(data::User *user, TitleSelectCommon *titleSelec } } -void SaveCreateState::update(void) +void SaveCreateState::update() { if (m_refreshRequired) { @@ -75,7 +75,7 @@ void SaveCreateState::update(void) } } -void SaveCreateState::render(void) +void SaveCreateState::render() { // Clear slide target, render menu, render slide to frame buffer. sm_slidePanel->clear_target(); @@ -83,7 +83,7 @@ void SaveCreateState::render(void) sm_slidePanel->render(NULL, AppState::has_focus()); } -void SaveCreateState::data_and_view_refresh_required(void) +void SaveCreateState::data_and_view_refresh_required() { m_refreshRequired = true; } diff --git a/source/appstates/SettingsState.cpp b/source/appstates/SettingsState.cpp index 67b73eb..eda5e55 100644 --- a/source/appstates/SettingsState.cpp +++ b/source/appstates/SettingsState.cpp @@ -9,18 +9,40 @@ #include "logger.hpp" #include "strings.hpp" #include "stringutil.hpp" +#include namespace { // All of these states share the same render target. constexpr std::string_view SECONDARY_TARGET = "SecondaryTarget"; + + // This is needed to be able to get and set keys by index. Anything "NULL" isn't a key that can be easily toggled. + constexpr std::array CONFIG_KEY_ARRAY = {"NULL", + "NULL", + config::keys::INCLUDE_DEVICE_SAVES, + config::keys::AUTO_BACKUP_ON_RESTORE, + config::keys::AUTO_NAME_BACKUPS, + config::keys::AUTO_UPLOAD, + config::keys::HOLD_FOR_DELETION, + config::keys::HOLD_FOR_RESTORATION, + config::keys::HOLD_FOR_OVERWRITE, + config::keys::ONLY_LIST_MOUNTABLE, + config::keys::LIST_ACCOUNT_SYS_SAVES, + config::keys::ALLOW_WRITING_TO_SYSTEM, + config::keys::EXPORT_TO_ZIP, + config::keys::ZIP_COMPRESSION_LEVEL, + config::keys::TITLE_SORT_TYPE, + config::keys::JKSM_TEXT_MODE, + config::keys::FORCE_ENGLISH, + config::keys::ENABLE_TRASH_BIN, + "NULL"}; } // namespace // Declarations. Definitions after class members. static const char *get_value_text(uint8_t value); static const char *get_sort_type_text(uint8_t value); -SettingsState::SettingsState(void) +SettingsState::SettingsState() : m_settingsMenu(32, 8, 1000, 24, 555), m_renderTarget(sdl::TextureManager::create_load_texture(SECONDARY_TARGET, 1080, @@ -40,7 +62,7 @@ SettingsState::SettingsState(void) SettingsState::update_menu_options(); } -void SettingsState::update(void) +void SettingsState::update() { m_settingsMenu.update(AppState::has_focus()); @@ -54,7 +76,7 @@ void SettingsState::update(void) } } -void SettingsState::render(void) +void SettingsState::render() { m_renderTarget->clear(colors::TRANSPARENT); m_settingsMenu.render(m_renderTarget->get(), AppState::has_focus()); @@ -72,33 +94,34 @@ void SettingsState::render(void) } } -void SettingsState::update_menu_options(void) +void SettingsState::update_menu_options() { // These can be looped, so screw it. for (int i = 2; i < 13; i++) { std::string updatedOption = stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, i), - get_value_text(config::get_by_index(i - 2))); + get_value_text(config::get_by_key(CONFIG_KEY_ARRAY[i]))); m_settingsMenu.edit_option(i, updatedOption); } // This just displays the value. The config value is offset to account for the first two settings options. m_settingsMenu.edit_option(13, stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, 13), - config::get_by_index(11))); + config::get_by_key(CONFIG_KEY_ARRAY[13]))); // This gets the type according to the value. - m_settingsMenu.edit_option(14, - stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, 14), - get_sort_type_text(config::get_by_index(12)))); + m_settingsMenu.edit_option( + 14, + stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, 14), + get_sort_type_text(config::get_by_key(CONFIG_KEY_ARRAY[14])))); // Loop again. for (int i = 15; i < 18; i++) { std::string updatedOption = stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, i), - get_value_text(config::get_by_index(i - 2))); + get_value_text(config::get_by_key(CONFIG_KEY_ARRAY[i]))); m_settingsMenu.edit_option(i, updatedOption); } @@ -108,11 +131,10 @@ void SettingsState::update_menu_options(void) config::get_animation_scaling())); } -void SettingsState::toggle_options(void) +void SettingsState::toggle_options() { int selected = m_settingsMenu.get_selected(); - switch (selected) { // Zip level. @@ -154,10 +176,9 @@ void SettingsState::toggle_options(void) // Text mode. This is handled beyond a toggle. case 15: { - // We're gonna use this to toggle first. - config::toggle_by_index(selected - 2); + config::toggle_by_key(CONFIG_KEY_ARRAY[selected]); - // Need the main state to reinit all the views. + // This will reinit all the views according the key toggled. MainMenuState::initialize_view_states(); } break; @@ -176,7 +197,7 @@ void SettingsState::toggle_options(void) default: { - config::toggle_by_index(selected - 2); + config::toggle_by_key(CONFIG_KEY_ARRAY[selected]); } break; } diff --git a/source/appstates/TaskState.cpp b/source/appstates/TaskState.cpp index 9bf7b6b..21efa03 100644 --- a/source/appstates/TaskState.cpp +++ b/source/appstates/TaskState.cpp @@ -5,7 +5,7 @@ #include "strings.hpp" #include "ui/PopMessageManager.hpp" -void TaskState::update(void) +void TaskState::update() { // Run the base update routine. BaseTask::update(); @@ -22,7 +22,7 @@ void TaskState::update(void) } } -void TaskState::render(void) +void TaskState::render() { // Grab task string. std::string status = m_task.get_status(); diff --git a/source/appstates/TextTitleSelectState.cpp b/source/appstates/TextTitleSelectState.cpp index 5df479d..4b60f22 100644 --- a/source/appstates/TextTitleSelectState.cpp +++ b/source/appstates/TextTitleSelectState.cpp @@ -28,7 +28,7 @@ TextTitleSelectState::TextTitleSelectState(data::User *user) TextTitleSelectState::refresh(); } -void TextTitleSelectState::update(void) +void TextTitleSelectState::update() { m_titleSelectMenu.update(AppState::has_focus()); @@ -96,7 +96,7 @@ void TextTitleSelectState::update(void) } } -void TextTitleSelectState::render(void) +void TextTitleSelectState::render() { m_renderTarget->clear(colors::TRANSPARENT); m_titleSelectMenu.render(m_renderTarget->get(), AppState::has_focus()); @@ -104,7 +104,7 @@ void TextTitleSelectState::render(void) m_renderTarget->render(NULL, 201, 91); } -void TextTitleSelectState::refresh(void) +void TextTitleSelectState::refresh() { m_titleSelectMenu.reset(); for (size_t i = 0; i < m_user->get_total_data_entries(); i++) diff --git a/source/appstates/TitleInfoState.cpp b/source/appstates/TitleInfoState.cpp index 9641cf3..7164626 100644 --- a/source/appstates/TitleInfoState.cpp +++ b/source/appstates/TitleInfoState.cpp @@ -100,7 +100,7 @@ TitleInfoState::~TitleInfoState() sm_slidePanel->clear_elements(); } -void TitleInfoState::update(void) +void TitleInfoState::update() { // Grab this instead of calling the function over and over. bool hasFocus = AppState::has_focus(); @@ -123,7 +123,7 @@ void TitleInfoState::update(void) } } -void TitleInfoState::render(void) +void TitleInfoState::render() { // This is the vertical gap for rendering text. static constexpr int SIZE_VERT_GAP = SIZE_TEXT_TARGET_HEIGHT + 4; diff --git a/source/appstates/TitleOptionState.cpp b/source/appstates/TitleOptionState.cpp index af890a9..5dd08d9 100644 --- a/source/appstates/TitleOptionState.cpp +++ b/source/appstates/TitleOptionState.cpp @@ -75,7 +75,7 @@ TitleOptionState::TitleOptionState(data::User *user, data::TitleInfo *titleInfo, m_dataStruct->m_titleSelect = m_titleSelect; } -void TitleOptionState::update(void) +void TitleOptionState::update() { // This is kind of tricky to handle, because the blacklist function uses both. if (m_refreshRequired) @@ -251,19 +251,19 @@ void TitleOptionState::update(void) } } -void TitleOptionState::render(void) +void TitleOptionState::render() { sm_slidePanel->clear_target(); sm_titleOptionMenu->render(sm_slidePanel->get_target(), AppState::has_focus()); sm_slidePanel->render(NULL, AppState::has_focus()); } -void TitleOptionState::close_on_update(void) +void TitleOptionState::close_on_update() { m_exitRequired = true; } -void TitleOptionState::refresh_required(void) +void TitleOptionState::refresh_required() { m_refreshRequired = true; } diff --git a/source/appstates/TitleSelectCommon.cpp b/source/appstates/TitleSelectCommon.cpp index 0359ea8..8386a13 100644 --- a/source/appstates/TitleSelectCommon.cpp +++ b/source/appstates/TitleSelectCommon.cpp @@ -3,7 +3,7 @@ #include "sdl.hpp" #include "strings.hpp" -TitleSelectCommon::TitleSelectCommon(void) +TitleSelectCommon::TitleSelectCommon() { if (m_titleControlsX == 0) { @@ -11,7 +11,7 @@ TitleSelectCommon::TitleSelectCommon(void) } } -void TitleSelectCommon::render_control_guide(void) +void TitleSelectCommon::render_control_guide() { if (AppState::has_focus()) { diff --git a/source/appstates/TitleSelectState.cpp b/source/appstates/TitleSelectState.cpp index a3bb066..d0f24a9 100644 --- a/source/appstates/TitleSelectState.cpp +++ b/source/appstates/TitleSelectState.cpp @@ -27,7 +27,7 @@ TitleSelectState::TitleSelectState(data::User *user) SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)), m_titleView(m_user) {}; -void TitleSelectState::update(void) +void TitleSelectState::update() { if (m_user->get_total_data_entries() <= 0) { @@ -44,11 +44,8 @@ void TitleSelectState::update(void) FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); data::TitleInfo *titleInfo = data::get_title_info_by_id(applicationID); - // Path to output to. - fslib::Path targetPath = config::get_working_directory() / titleInfo->get_path_safe_title(); - - if ((fslib::directory_exists(targetPath) || fslib::create_directory(targetPath)) && - fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)) + // To do: Figure out how to handle this differently. Meta files need this closed. + if (fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo)) { auto backupMenuState = std::make_shared(m_user, @@ -94,7 +91,7 @@ void TitleSelectState::update(void) } } -void TitleSelectState::render(void) +void TitleSelectState::render() { m_renderTarget->clear(colors::TRANSPARENT); m_titleView.render(m_renderTarget->get(), AppState::has_focus()); @@ -102,7 +99,7 @@ void TitleSelectState::render(void) m_renderTarget->render(NULL, 201, 91); } -void TitleSelectState::refresh(void) +void TitleSelectState::refresh() { m_titleView.refresh(); } diff --git a/source/appstates/UserOptionState.cpp b/source/appstates/UserOptionState.cpp index efe5757..8b9bc7c 100644 --- a/source/appstates/UserOptionState.cpp +++ b/source/appstates/UserOptionState.cpp @@ -58,7 +58,7 @@ UserOptionState::UserOptionState(data::User *user, TitleSelectCommon *titleSelec m_dataStruct->m_spawningState = this; } -void UserOptionState::update(void) +void UserOptionState::update() { // Update the main panel. m_menuPanel->update(AppState::has_focus()); @@ -152,7 +152,7 @@ void UserOptionState::update(void) m_userOptionMenu.update(AppState::has_focus()); } -void UserOptionState::render(void) +void UserOptionState::render() { // Render target user's title selection screen. m_titleSelect->render(); @@ -163,7 +163,7 @@ void UserOptionState::render(void) m_menuPanel->render(NULL, AppState::has_focus()); } -void UserOptionState::data_and_view_refresh_required(void) +void UserOptionState::data_and_view_refresh_required() { m_refreshRequired = true; } diff --git a/source/config.cpp b/source/config.cpp index 6fb9dbd..6121fdd 100644 --- a/source/config.cpp +++ b/source/config.cpp @@ -5,26 +5,23 @@ #include #include #include +#include #include -#include #include namespace { - // Makes stuff slightly easier to read. - using ConfigPair = std::pair; - /// @brief This is the default working directory path. constexpr std::string_view PATH_DEFAULT_WORK_DIR = "sdmc:/JKSV"; // Folder path. constexpr std::string_view PATH_CONFIG_FOLDER = "sdmc:/config/JKSV"; - // Actual config path. - constexpr std::string_view PATH_CONFIG_FILE = "sdmc:/config/JKSV/JKSV.json"; // Paths file. Funny name too. constexpr std::string_view PATH_PATHS_PATH = "sdmc:/config/JKSV/Paths.json"; + // Actual config path. + const char *PATH_CONFIG_FILE = "sdmc:/config/JKSV/JKSV.json"; - // Vector to preserve order now. - std::vector s_configVector; + /// @brief This map holds the majority of config values. + std::map s_configMap; // Working directory fslib::Path s_workingDirectory; @@ -36,7 +33,7 @@ namespace std::vector s_blacklist; // Map of paths. - std::unordered_map s_pathMap; + std::map s_pathMap; } // namespace static void read_array_to_vector(std::vector &vector, json_object *array) @@ -56,7 +53,7 @@ static void read_array_to_vector(std::vector &vector, json_object *arr } } -void config::initialize(void) +void config::initialize() { if (!fslib::directory_exists(PATH_CONFIG_FOLDER) && !fslib::create_directories_recursively(PATH_CONFIG_FOLDER)) { @@ -65,7 +62,7 @@ void config::initialize(void) return; } - json::Object configJSON = json::new_object(json_object_from_file, PATH_CONFIG_FILE.data()); + json::Object configJSON = json::new_object(json_object_from_file, PATH_CONFIG_FILE); if (!configJSON) { logger::log("Error opening config for reading: %s", fslib::get_error_string()); @@ -99,7 +96,7 @@ void config::initialize(void) } else { - s_configVector.push_back(std::make_pair(keyName, json_object_get_uint64(configValue))); + s_configMap[keyName] = json_object_get_uint64(configValue); } json_object_iter_next(&configIterator); } @@ -132,29 +129,29 @@ void config::initialize(void) } } -void config::reset_to_default(void) +void config::reset_to_default() { s_workingDirectory = PATH_DEFAULT_WORK_DIR; - s_configVector.push_back(std::make_pair(config::keys::INCLUDE_DEVICE_SAVES.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::AUTO_BACKUP_ON_RESTORE.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::AUTO_NAME_BACKUPS.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::AUTO_UPLOAD.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::HOLD_FOR_DELETION.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::HOLD_FOR_RESTORATION.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::HOLD_FOR_OVERWRITE.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::ONLY_LIST_MOUNTABLE.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::LIST_ACCOUNT_SYS_SAVES.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::ALLOW_WRITING_TO_SYSTEM.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::EXPORT_TO_ZIP.data(), 1)); - s_configVector.push_back(std::make_pair(config::keys::ZIP_COMPRESSION_LEVEL.data(), 6)); - s_configVector.push_back(std::make_pair(config::keys::TITLE_SORT_TYPE.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::JKSM_TEXT_MODE.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::FORCE_ENGLISH.data(), 0)); - s_configVector.push_back(std::make_pair(config::keys::ENABLE_TRASH_BIN.data(), 0)); + 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; } -void config::save(void) +void config::save() { { json::Object configJSON = json::new_object(json_object_new_object); @@ -164,7 +161,7 @@ void config::save(void) json::add_object(configJSON, config::keys::WORKING_DIRECTORY.data(), workingDirectory); // Loop through map and add it. - for (auto &[key, value] : s_configVector) + for (auto &[key, value] : s_configMap) { json_object *jsonValue = json_object_new_uint64(value); json::add_object(configJSON, key.c_str(), jsonValue); @@ -234,83 +231,40 @@ void config::save(void) uint8_t config::get_by_key(std::string_view key) { // See if the key can be found. - auto findKey = std::find_if(s_configVector.begin(), s_configVector.end(), [key](const auto &configPair) { - return key == configPair.first; - }); - - // Bail. - if (findKey == s_configVector.end()) + auto findKey = s_configMap.find(key.data()); + if (findKey == s_configMap.end()) { return 0; } - - // Return the value. return findKey->second; } void config::toggle_by_key(std::string_view key) { - // Make sure the key exists first. - auto findKey = std::find_if(s_configVector.begin(), s_configVector.end(), [key](const auto &configPair) { - return key == configPair.first; - }); - - if (findKey == s_configVector.end()) + auto findKey = s_configMap.find(key.data()); + 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 = std::find_if(s_configVector.begin(), s_configVector.end(), [key](const auto &configPair) { - return key == configPair.first; - }); - if (findKey == s_configVector.end()) + auto findKey = s_configMap.find(key.data()); + if (findKey == s_configMap.end()) { return; } findKey->second = value; } - -uint8_t config::get_by_index(int index) -{ - if (index < 0 || index >= static_cast(s_configVector.size())) - { - return 0; - } - - return s_configVector.at(index).second; -} - -void config::toggle_by_index(int index) -{ - if (index < 0 || index >= static_cast(s_configVector.size())) - { - return; - } - s_configVector[index].second = s_configVector[index].second ? 0 : 1; -} - -void config::set_by_index(int index, uint8_t value) -{ - if (index < 0 || index >= static_cast(s_configVector.size())) - { - return; - } - - s_configVector[index].second = value; -} - -fslib::Path config::get_working_directory(void) +fslib::Path config::get_working_directory() { return s_workingDirectory; } -double config::get_animation_scaling(void) +double config::get_animation_scaling() { return s_uiAnimationScaling; } diff --git a/source/curl/curl.cpp b/source/curl/curl.cpp index b116a00..af74922 100644 --- a/source/curl/curl.cpp +++ b/source/curl/curl.cpp @@ -1,4 +1,5 @@ #include "curl/curl.hpp" +#include "logger.hpp" #include "stringutil.hpp" namespace @@ -7,16 +8,35 @@ namespace constexpr size_t SIZE_UPLOAD_BUFFER = 0x10000; } // namespace -bool curl::initialize(void) +bool curl::initialize() { return curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK; } -void curl::exit(void) +void curl::exit() { curl_global_cleanup(); } +bool curl::perform(curl::Handle &handle) +{ + CURLcode error = curl_easy_perform(handle.get()); + if (error != CURLE_OK) + { + logger::log("Error performing curl: %i.", error); + return false; + } + return true; +} + +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()); + list.reset(head); +} + size_t curl::read_data_from_file(char *buffer, size_t size, size_t count, fslib::File *target) { // This should be good enough. @@ -31,7 +51,7 @@ size_t curl::write_header_array(const char *buffer, size_t size, size_t count, c size_t curl::write_response_string(const char *buffer, size_t size, size_t count, std::string *string) { - string->append(buffer, buffer + (size * count)); + string->append(buffer, size * count); return size * count; } @@ -75,6 +95,43 @@ bool curl::get_header_value(const curl::HeaderArray &array, std::string_view hea return false; } +long curl::get_response_code(curl::Handle &handle) +{ + long code = 0; + curl_easy_getinfo(handle.get(), CURLINFO_RESPONSE_CODE, &code); + return code; +} + +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; + } + + out.assign(escaped); + + // I'm assuming this just calls free itself? + curl_free(escaped); + return true; +} + +bool curl::unescape_string(curl::Handle &handle, std::string_view in, std::string &out) +{ + int lengthOut{}; + char *unescaped = curl_easy_unescape(handle.get(), in.data(), in.length(), &lengthOut); + if (!unescaped) + { + return false; + } + + out.assign(unescaped); + + curl_free(unescaped); + return true; +} + void curl::prepare_get(curl::Handle &curl) { // Reset handle. This is faster than making duplicates over and over. @@ -98,6 +155,5 @@ void curl::prepare_upload(curl::Handle &curl) curl::reset_handle(curl); curl::set_option(curl, CURLOPT_UPLOAD, 1L); - curl::set_option(curl, CURLOPT_UPLOAD_BUFFERSIZE, SIZE_UPLOAD_BUFFER); curl::set_option(curl, CURLOPT_ACCEPT_ENCODING, ""); // Not really sure this will have any affect here... } diff --git a/source/data/TitleInfo.cpp b/source/data/TitleInfo.cpp index bd4c17b..9eea39b 100644 --- a/source/data/TitleInfo.cpp +++ b/source/data/TitleInfo.cpp @@ -73,22 +73,22 @@ data::TitleInfo::TitleInfo(uint64_t applicationID, NsApplicationControlData &con m_icon = sdl::TextureManager::create_load_texture(entry->name, m_data.icon, sizeof(m_data.icon)); } -uint64_t data::TitleInfo::get_application_id(void) const +uint64_t data::TitleInfo::get_application_id() const { return m_applicationID; } -NsApplicationControlData *data::TitleInfo::get_control_data(void) +NsApplicationControlData *data::TitleInfo::get_control_data() { return &m_data; } -bool data::TitleInfo::has_control_data(void) const +bool data::TitleInfo::has_control_data() const { return m_hasData; } -const char *data::TitleInfo::get_title(void) +const char *data::TitleInfo::get_title() { NacpLanguageEntry *entry = nullptr; if (R_FAILED(nacpGetLanguageEntry(&m_data.nacp, &entry))) @@ -98,12 +98,12 @@ const char *data::TitleInfo::get_title(void) return entry->name; } -const char *data::TitleInfo::get_path_safe_title(void) +const char *data::TitleInfo::get_path_safe_title() const { return m_pathSafeTitle; } -const char *data::TitleInfo::get_publisher(void) +const char *data::TitleInfo::get_publisher() { NacpLanguageEntry *Entry = nullptr; if (R_FAILED(nacpGetLanguageEntry(&m_data.nacp, &Entry))) @@ -113,7 +113,7 @@ const char *data::TitleInfo::get_publisher(void) return Entry->author; } -uint64_t data::TitleInfo::get_save_data_owner_id(void) const +uint64_t data::TitleInfo::get_save_data_owner_id() const { return m_data.nacp.save_data_owner_id; } @@ -312,9 +312,9 @@ int64_t data::TitleInfo::get_journal_size_max(uint8_t saveType) const return 0; } -bool data::TitleInfo::has_save_data_type(uint8_t saveType) +bool data::TitleInfo::has_save_data_type(uint8_t saveType) const { - NacpStruct *nacp = &m_data.nacp; + const NacpStruct *nacp = &m_data.nacp; // I'm not 100% sure this is the best way to test for this. switch (saveType) @@ -352,7 +352,7 @@ bool data::TitleInfo::has_save_data_type(uint8_t saveType) return false; } -sdl::SharedTexture data::TitleInfo::get_icon(void) const +sdl::SharedTexture data::TitleInfo::get_icon() const { return m_icon; } @@ -369,7 +369,7 @@ void data::TitleInfo::set_path_safe_title(const char *newPathSafe, size_t newPat std::memcpy(m_pathSafeTitle, newPathSafe, newPathLength); } -void data::TitleInfo::get_create_path_safe_title(void) +void data::TitleInfo::get_create_path_safe_title() { // Avoid calling this function over and over. uint64_t applicationID = TitleInfo::get_application_id(); diff --git a/source/data/User.cpp b/source/data/User.cpp index 5148ce3..6ba21df 100644 --- a/source/data/User.cpp +++ b/source/data/User.cpp @@ -70,7 +70,7 @@ void data::User::add_data(const FsSaveDataInfo *saveInfo, const PdmPlayStatistic m_userData.push_back(std::make_pair(applicationID, std::make_pair(*saveInfo, *playStats))); } -void data::User::clear_data_entries(void) +void data::User::clear_data_entries() { m_userData.clear(); } @@ -80,32 +80,32 @@ void data::User::erase_data(int index) m_userData.erase(m_userData.begin() + index); } -void data::User::sort_data(void) +void data::User::sort_data() { std::sort(m_userData.begin(), m_userData.end(), sort_user_data); } -AccountUid data::User::get_account_id(void) const +AccountUid data::User::get_account_id() const { return m_accountID; } -FsSaveDataType data::User::get_account_save_type(void) const +FsSaveDataType data::User::get_account_save_type() const { return m_saveType; } -const char *data::User::get_nickname(void) const +const char *data::User::get_nickname() const { return m_nickname; } -const char *data::User::get_path_safe_nickname(void) const +const char *data::User::get_path_safe_nickname() const { return m_pathSafeNickname; } -size_t data::User::get_total_data_entries(void) const +size_t data::User::get_total_data_entries() const { return m_userData.size(); } @@ -150,7 +150,7 @@ FsSaveDataInfo *data::User::get_save_info_by_id(uint64_t applicationID) return &findTitle->second.first; } -data::UserSaveInfoList &data::User::get_user_save_info_list(void) +data::UserSaveInfoList &data::User::get_user_save_info_list() { return m_userData; } @@ -168,12 +168,12 @@ PdmPlayStatistics *data::User::get_play_stats_by_id(uint64_t applicationID) return &findTitle->second.second; } -SDL_Texture *data::User::get_icon(void) +SDL_Texture *data::User::get_icon() { return m_icon->get(); } -sdl::SharedTexture data::User::get_shared_icon(void) +sdl::SharedTexture data::User::get_shared_icon() { return m_icon; } @@ -194,7 +194,7 @@ void data::User::erase_save_info_by_id(uint64_t applicationID) m_userData.erase(targetEntry); } -void data::User::load_user_data(void) +void data::User::load_user_data() { // Pull these from config quick. bool accountSystemSaves = config::get_by_key(config::keys::LIST_ACCOUNT_SYS_SAVES); @@ -297,7 +297,7 @@ void data::User::load_account(AccountProfile &profile, AccountProfileBase &profi } } -void data::User::create_account(void) +void data::User::create_account() { // This is needed a lot here. std::string accountIDString = stringutil::get_formatted_string("Acc_%08X", m_accountID.uid[0] & 0xFFFFFFFF); diff --git a/source/data/data.cpp b/source/data/data.cpp index 55f453b..9df7ceb 100644 --- a/source/data/data.cpp +++ b/source/data/data.cpp @@ -41,20 +41,20 @@ namespace // Declarations here. Definitions at bottom. These should appear in the order called. /// @brief Loads users from the system and creates the system users. -static bool load_create_user_accounts(void); +static bool load_create_user_accounts(); /// @brief Loads the application records from NS. -static void load_application_records(void); +static void load_application_records(); /// @brief Imports external SVI(Control Data) files. -static void import_svi_files(void); +static void import_svi_files(); /// @brief Attempts to read the cache file from the SD. /// @return True on success. False on failure. -static bool read_cache_file(void); +static bool read_cache_file(); /// @brief Creates the cache file on the SD card. -static void create_cache_file(void); +static void create_cache_file(); bool data::initialize(bool clearCache) { @@ -137,7 +137,7 @@ bool data::title_exists_in_map(uint64_t applicationID) return s_titleInfoMap.find(applicationID) != s_titleInfoMap.end(); } -std::unordered_map &data::get_title_info_map(void) +std::unordered_map &data::get_title_info_map() { return s_titleInfoMap; } @@ -157,7 +157,7 @@ void data::get_title_info_by_type(FsSaveDataType saveType, std::vector -int main(void) +int main() { JKSV jksv{}; while (appletMainLoop() && jksv.is_running()) diff --git a/source/remote/Form.cpp b/source/remote/Form.cpp new file mode 100644 index 0000000..f82fa13 --- /dev/null +++ b/source/remote/Form.cpp @@ -0,0 +1,45 @@ +#include "remote/Form.hpp" + +remote::Form::Form(const remote::Form &form) +{ + m_form = form.m_form; +} + +remote::Form::Form(remote::Form &&form) +{ + m_form = form.m_form; + form.m_form.clear(); +} + +remote::Form &remote::Form::operator=(const remote::Form &form) +{ + m_form = form.m_form; + return *this; +} + +remote::Form &remote::Form::operator=(remote::Form &&form) +{ + m_form = form.m_form; + form.m_form.clear(); + return *this; +} + +remote::Form &remote::Form::append_parameter(std::string_view param, std::string_view value) +{ + 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(); +} + +size_t remote::Form::length() const +{ + return m_form.length(); +} diff --git a/source/remote/GoogleDrive.cpp b/source/remote/GoogleDrive.cpp index 5ab4c92..5a05a15 100644 --- a/source/remote/GoogleDrive.cpp +++ b/source/remote/GoogleDrive.cpp @@ -1,117 +1,97 @@ #include "remote/GoogleDrive.hpp" #include "logger.hpp" +#include "remote/Form.hpp" +#include "remote/URL.hpp" +#include "remote/remote.hpp" #include "strings.hpp" +#include "stringutil.hpp" #include +#include namespace { - /// @brief Buffer size for snprintf'ing URLS. - constexpr size_t SIZE_URL_BUFFER = 0x800; - - /// @brief Path the client_secret from Google should be located at. - constexpr std::string_view PATH_CLIENT_SECRET_LOCATION = "sdmc:/config/JKSV/client_secret.json"; - - /// @brief Header for json type content. - constexpr std::string_view HEADER_CONTENT_TYPE_JSON = "Content-Type: application/json"; - /// @brief Header for URL encoded content. - constexpr std::string_view HEADER_CONTENT_TYPE_URL = "Content-Type: application/x-www-form-urlencoded"; - /// @brief Authorization header string that the token is appended to. + // This is the sort-of template for the authorization/token header. constexpr std::string_view HEADER_AUTH_BEARER = "Authorization: Bearer "; - /// @brief This is the string used to locate the header containing the upload URL. - constexpr std::string_view HEADER_UPLOAD_LOCATION = "location"; - /// @brief OAuth2 login for TV type devices. - /// @note This has limited scopes available. - constexpr std::string_view URL_OAUTH2_DEVICE_CODE = "https://oauth2.googleapis.com/device/code"; - /// @brief This is the OAuth2 token URL. - constexpr std::string_view URL_OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token"; - /// @brief This a the V2 about api endpoint so I can pull the root drive ID. V3 doesn't support this. - constexpr std::string_view URL_DRIVE_ABOUT_API = "https://www.googleapis.com/drive/v2/about"; - /// @brief This is the main V3 drive API url. - constexpr std::string_view URL_DRIVE_FILE_API = "https://www.googleapis.com/drive/v3/files"; - /// @brief This is the endpoint for starting uploads. - constexpr std::string_view URL_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files"; + // Content type headers used. + const char *HEADER_CONTENT_TYPE_JSON = "Content-Type: application/json"; + const char *HEADER_CONTENT_TYPE_FORM = "Content-Type: application/x-www-form-urlencoded"; - /// @brief The drive file scope allowed with the sign in method I chose this time around. - constexpr std::string_view PARAM_DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file"; - /// @brief Grant type string for device type (TV/etc). - constexpr std::string_view PARAM_GRANT_TYPE_DEVICE = "urn:ietf:params:oauth:grant-type:device_code"; - /// @brief These are the default parameters used to get a drive listing. JKSV loads everything it possible can at once. - constexpr std::string_view PARAM_DEFUALT_LIST_QUERY = - "fields=nextPageToken,files(name,id,size,parents,mimeType)&orderBy=name_natural&pageSize=256&q=trashed=false"; + // This is used for upload/patch for grabbing the upload location. + constexpr std::string_view HEADER_UPLOAD_LOCATION = "Location"; - // JSON keys - constexpr std::string_view JSON_KEY_ACCESS_TOKEN = "access_token"; - constexpr std::string_view JSON_KEY_CLIENT_ID = "client_id"; - constexpr std::string_view JSON_KEY_CLIENT_SECRET = "client_secret"; - constexpr std::string_view JSON_KEY_DEVICE_CODE = "device_code"; - constexpr std::string_view JSON_KEY_ERROR = "error"; - constexpr std::string_view JSON_KEY_EXPIRES_IN = "expires_in"; - constexpr std::string_view JSON_KEY_GRANT_TYPE = "grant_type"; - constexpr std::string_view JSON_KEY_ID = "id"; - constexpr std::string_view JSON_KEY_INSTALLED = "installed"; - constexpr std::string_view JSON_KEY_MIMETYPE = "mimeType"; - constexpr std::string_view JSON_KEY_NAME = "name"; - constexpr std::string_view JSON_KEY_NEXT_PAGE_TOKEN = "nextPageToken"; - constexpr std::string_view JSON_KEY_PARENTS = "parents"; - constexpr std::string_view JSON_KEY_REFRESH_TOKEN = "refresh_token"; + // These are API endpoints used in multiple request calls. + const char *URL_OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token"; + const char *URL_DRIVE_FILE_API = "https://www.googleapis.com/drive/v3/files"; + const char *URL_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files"; + + // These are json keys that are used for various requests. + const char *JSON_KEY_ACCESS_TOKEN = "access_token"; + const char *JSON_KEY_CLIENT_ID = "client_id"; + const char *JSON_KEY_CLIENT_SECRET = "client_secret"; + const char *JSON_KEY_DEVICE_CODE = "device_code"; + const char *JSON_KEY_EXPIRES_IN = "expires_in"; + const char *JSON_KEY_GRANT_TYPE = "grant_type"; + const char *JSON_KEY_ID = "id"; + const char *JSON_KEY_INSTALLED = "installed"; + const char *JSON_KEY_MIMETYPE = "mimeType"; + const char *JSON_KEY_NAME = "name"; + const char *JSON_KEY_PARENTS = "parents"; + const char *JSON_KEY_REFRESH_TOKEN = "refresh_token"; /// @brief Folder mimetype string. - constexpr std::string_view MIME_TYPE_DIRECTORY = "application/vnd.google-apps.folder"; + const char *MIME_TYPE_DIRECTORY = "application/vnd.google-apps.folder"; } // namespace -remote::GoogleDrive::GoogleDrive(void) : m_curl(curl::new_handle()) +remote::GoogleDrive::GoogleDrive() : Storage() { + 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; + // Load the json file. - json::Object clientJson = json::new_object(json_object_from_file, PATH_CLIENT_SECRET_LOCATION.data()); + json::Object clientJson = json::new_object(json_object_from_file, remote::PATH_GOOGLE_DRIVE_CONFIG.data()); if (!clientJson) { - logger::log("Error reading Google Drive configuration json!"); + logger::log(STRING_ERROR_READING_CONFIG, "Error reading configuration file!"); return; } - json_object *installed = json::get_object(clientJson, JSON_KEY_INSTALLED.data()); + json_object *installed = json::get_object(clientJson, JSON_KEY_INSTALLED); if (!installed) { - logger::log("Google Drive configuration is invalid!"); + logger::log(STRING_ERROR_READING_CONFIG, "Configuration file is malformed!"); return; } - json_object *clientId = json_object_object_get(installed, JSON_KEY_CLIENT_ID.data()); - json_object *clientSecret = json_object_object_get(installed, JSON_KEY_CLIENT_SECRET.data()); + json_object *clientId = json_object_object_get(installed, JSON_KEY_CLIENT_ID); + json_object *clientSecret = json_object_object_get(installed, JSON_KEY_CLIENT_SECRET); + json_object *refreshToken = json_object_object_get(installed, JSON_KEY_REFRESH_TOKEN); if (!clientId || !clientSecret) { - logger::log("Google Drive configuration is missing data!"); + logger::log(STRING_ERROR_READING_CONFIG, "Configuration file is missing required data!"); return; } // Grab them. m_clientId = json_object_get_string(clientId); m_clientSecret = json_object_get_string(clientSecret); - json_object *refreshToken = json_object_object_get(installed, JSON_KEY_REFRESH_TOKEN.data()); + // Returning here will make is_initialized return false. if (!refreshToken) { - // Bail here so it doesn't appear to be initialized. - return. + return; } m_refreshToken = json_object_get_string(refreshToken); -} -void remote::GoogleDrive::change_directory(std::string_view id) -{ - remote::Storage::List::iterator targetDir; - if (id == ".." && (targetDir = GoogleDrive::find_directory_by_id(m_parent)) != m_list.end()) + if (!GoogleDrive::refresh_token()) { - // Set it to the parent of the current parent. - m_parent = targetDir->get_parent_id(); + // If refreshing the token failed, this will cause is_initialized to return false and force a re-signin. + m_refreshToken.clear(); } - else if ((targetDir = Storage::find_directory(name)) != m_list.end()) + else if (GoogleDrive::get_root_id() && GoogleDrive::request_listing()) { - m_parent = targetDir->get_id(); - } - else - { - logger::log("Error changing Google Drive directory: directory specified not found!"); + m_isInitialized = true; } } @@ -119,56 +99,49 @@ bool remote::GoogleDrive::create_directory(std::string_view name) { if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { - logger::log("Error creating directory: Token is no longer valid and could not be refreshed."); return false; } - // Headers. curl::HeaderList headers = curl::new_header_list(); curl::append_header(headers, m_authHeader); - curl::append_header(headers, HEADER_CONTENT_TYPE_JSON.data()); + curl::append_header(headers, HEADER_CONTENT_TYPE_JSON); - // Post json. - json::object postJson = json::new_object(json_object_new_object); + json::Object postJson = json::new_object(json_object_new_object); json_object *directoryName = json_object_new_string(name.data()); - json_object *mimeType = json_object_new_string(MIME_TYPE_DIRECTORY.data()); - json::add_object(postJson, JSON_KEY_NAME.data(), directoryName); - json::add_object(postJson, JSON_KEY_MIMETYPE.data(), mimeType); - // Append the parent. Empty parent will default to root. + json_object *mimeType = json_object_new_string(MIME_TYPE_DIRECTORY); + json::add_object(postJson, JSON_KEY_NAME, directoryName); + json::add_object(postJson, JSON_KEY_MIMETYPE, mimeType); if (!m_parent.empty()) { // I don't understand why this is an array. json_object *parentArray = json_object_new_array(); json_object *parentId = json_object_new_string(m_parent.c_str()); json_object_array_add(parentArray, parentId); - json::add_object(postJson, JSON_KEY_PARENTS.data(), parentArray); + json::add_object(postJson, JSON_KEY_PARENTS, parentArray); } - // Response string. std::string response; - // Curl post curl::prepare_post(m_curl); curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); - curl::set_option(m_curl, CURLOPT_URL, URL_DRIVE_FILE_API.data()); + curl::set_option(m_curl, CURLOPT_URL, URL_DRIVE_FILE_API); curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); curl::set_option(m_curl, CURLOPT_POSTFIELDS, json_object_get_string(postJson.get())); - if (!curl::perform()) + if (!curl::perform(m_curl)) { return false; } - // Parse the response. - json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); - if (!responseParser) + json::Object parser = json::new_object(json_tokener_parse, response.c_str()); + if (!parser) { logger::log("Error creating directory: Error parsing server response."); return false; } // This is all I really need from the response. - json_object *id = json::get_object(responseParser, JSON_KEY_ID.data()); + json_object *id = json::get_object(parser, JSON_KEY_ID); if (!id) { // This doesn't really mean the request wasn't successful. It just means the new directory couldn't be appended @@ -177,67 +150,7 @@ bool remote::GoogleDrive::create_directory(std::string_view name) return false; } - // Emplace the new item. - m_list.emplace_back(name, json_object_get_string(id), m_parent, true); - - return true; -} - -// This is basically the same thing as deleting a file on Google Drive. -bool remote::GoogleDrive::delete_directory(std::string_view id) -{ - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } - - // Header list. - curl::HeaderList headers = curl::new_header_list(); - curl::append_header(headers, m_authHeader); - - // URL - char urlBuffer[SIZE_URL_BUFFER] = {0}; - std::snprintf(urlBuffer, SIZE_URL_BUFFER, "%s/%s", URL_DRIVE_FILE_API.data(), id.data()); - - // Response. This is only used to check for errors. - std::string response; - // Curl. - curl::reset_handle(m_curl); - curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); - curl::set_option(m_curl, CURLOPT_URL, urlBuffer); - curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); - curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); - - if (!curl::perform(m_curl)) - { - return false; - } - - // This is weird. No response means success? - json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); - if (GoogleDrive::error_occured(responseParser)) - { - return false; - } - - // Now remove it from the local list. - remote::Storage::List::iterator findDir = GoogleDrive::find_directory_by_id(id); - if (findDir == m_list.end()) - { - logger::log("Error deleting directory from Google Drive: directory could not be located locally for removal."); - return false; - } - - // Erase the directory locally and all of its children. - m_list.erase(findDir); - // Just gonna reuse the iterator. This is one hell of a while loop condition. - while ((findDir = std::find_if(m_list.begin(), m_list.end(), [id](const remote::Item &item) { - return item.get_parent_id() == id; - })) != m_list.end()) - { - m_list.erase(findDir); - } + m_list.emplace_back(name, json_object_get_string(id), m_parent, 0, true); return true; } @@ -249,7 +162,6 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } - // Make sure we can even open the file before proceeding any further. fslib::File sourceFile(source, FsOpenMode_Read); if (!sourceFile) { @@ -257,36 +169,35 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } - // Headers curl::HeaderList headers = curl::new_header_list(); curl::append_header(headers, m_authHeader); - curl::append_header(headers, HEADER_CONTENT_TYPE_JSON.data()); + curl::append_header(headers, HEADER_CONTENT_TYPE_JSON); - // URL - char urlBuffer[SIZE_URL_BUFFER] = {0}; - std::snprintf(urlBuffer, SIZE_URL_BUFFER, "%s?uploadType=resumable", URL_DRIVE_UPLOAD_API.data()); + // I don't know if I like this much. Looks like I'm using a high level language instead of a real one. + remote::URL url{URL_DRIVE_UPLOAD_API}; + url.append_parameter("uploadType", "resumable"); // Json to post. json::Object postJson = json::new_object(json_object_new_object); - json_object *driveName = json_object_new_string(source.get_file_name().data()); - json::add_object(postJson, JSON_KEY_NAME.data(), driveName); + json_object *driveName = json_object_new_string(source.get_filename().data()); + json::add_object(postJson, JSON_KEY_NAME, driveName); // Append the parent. if (!m_parent.empty()) { json_object *parentArray = json_object_new_array(); json_object *parentId = json_object_new_string(m_parent.c_str()); json_object_array_add(parentArray, parentId); - json::add_object(postJson, JSON_KEY_PARENTS.data(), parentArray); + json::add_object(postJson, JSON_KEY_PARENTS, parentArray); } // This requires reading a header to get the upload location. curl::HeaderArray headerArray; - // Curl post. + curl::prepare_post(m_curl); - curl::set_option(m_curl, CURL_HTTPHEADER, headers.get()); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); curl::set_option(m_curl, CURLOPT_HEADERFUNCTION, curl::write_header_array); curl::set_option(m_curl, CURLOPT_HEADERDATA, &headerArray); - curl::set_option(m_curl, CURLOPT_URL, urlBuffer); + curl::set_option(m_curl, CURLOPT_URL, url.get()); curl::set_option(m_curl, CURLOPT_POSTFIELDS, json_object_get_string(postJson.get())); if (!curl::perform(m_curl)) @@ -294,6 +205,11 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } + for (std::string &h : headerArray) + { + logger::log("h: %s", h.c_str()); + } + // Extract the location from the headers. std::string location; if (!curl::get_header_value(headerArray, HEADER_UPLOAD_LOCATION.data(), location)) @@ -302,9 +218,8 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } - // Response string. std::string response; - // This is the actual upload. This doesn't need the authentication header to work. + // 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); @@ -317,7 +232,6 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } - // Parse response. json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); if (!responseParser) { @@ -325,9 +239,10 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } - json_object *id = json::get_object(responseParser, JSON_KEY_ID.data()); - json_object *filename = json::get_object(responseParser, JSON_KEY_NAME.data()); - json_object *mimeType = json::get_object(responseParser, JSON_KEY_MIMETYPE.data()); + json_object *id = json::get_object(responseParser, JSON_KEY_ID); + json_object *filename = json::get_object(responseParser, JSON_KEY_NAME); + json_object *mimeType = json::get_object(responseParser, JSON_KEY_MIMETYPE); + json_object *size = json::get_object(responseParser, "size"); // All of these are needed for the emplace_back. if (!id || !filename || !mimeType) { @@ -338,14 +253,17 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) m_list.emplace_back(json_object_get_string(filename), json_object_get_string(id), m_parent, - std::strcmp(MIME_TYPE_DIRECTORY.data(), json_object_get_string(mimeType)) == 0); + json_object_get_uint64(size), + std::strcmp(MIME_TYPE_DIRECTORY, json_object_get_string(mimeType)) == 0); - // Assumed everything is fine! + // Assume everything is fine! return true; } -bool remote::GoogleDrive::patch_file(std::string_view id, const fslib::Path &source) +bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &source) { + static const char *STRING_PATCH_ERROR = "Error patching file: %s"; + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; @@ -354,27 +272,24 @@ bool remote::GoogleDrive::patch_file(std::string_view id, const fslib::Path &sou fslib::File sourceFile(source, FsOpenMode_Read); if (!sourceFile) { - logger::log("Error patching file: %s", fslib::get_error_string()); + logger::log(STRING_PATCH_ERROR, fslib::get_error_string()); return false; } - char urlBuffer[SIZE_URL_BUFFER] = {0}; - std::snprintf(urlBuffer, SIZE_URL_BUFFER, "%s/%s?uploadType=resumable", URL_DRIVE_UPLOAD_API.data(), id.data()); + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, m_authHeader); - curl::HeaderList headers = curl::new_header_list(); - curl::append_header(headers, m_authHeader); + remote::URL url{URL_DRIVE_UPLOAD_API}; + url.append_path(file->get_id()).append_parameter("uploadType", "resumable"); - // Response string. std::string response; - // Header array. curl::HeaderArray headerArray; - // Curl curl::reset_handle(m_curl); curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); curl::set_option(m_curl, CURLOPT_HEADERFUNCTION, curl::write_header_array); curl::set_option(m_curl, CURLOPT_HEADERDATA, &headerArray); - curl::set_option(m_curl, CURLOPT_URL, urlBuffer); + curl::set_option(m_curl, CURLOPT_URL, url.get()); curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); @@ -383,16 +298,16 @@ bool remote::GoogleDrive::patch_file(std::string_view id, const fslib::Path &sou return false; } - // Get the location from the headers. + // This is the target location to upload to. std::string location; - if (!curl::get_header_value(headerArray, HEADER_UPLOAD_LOCATION.data(), location)) + if (!curl::get_header_value(headerArray, HEADER_UPLOAD_LOCATION, location)) { - logger::log("Error patching file: upload location not found in headers!"); + logger::log(STRING_PATCH_ERROR, "Location could not be read from headers!"); return false; } - // Setup the curl upload. - curl::prepare_upload(); + // For some reason, this doesn't need the auth header. + 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); @@ -402,103 +317,38 @@ bool remote::GoogleDrive::patch_file(std::string_view id, const fslib::Path &sou return false; } - // Try to locate the file with the ID. - remote::Storage::List::iterator findFile = GoogleDrive::find_file_by_id(id); - if (findFile == m_list.end()) - { - logger::log("Error patching file: file could not be located for updating data."); - return false; - } - - // Update the size according to the source file instead of wasting time and pulling it from Google. - findFile->set_size(sourceFile.get_size()); + // Update the file size with the source file size. + file->set_size(sourceFile.get_size()); return true; } -bool remote::GoogleDrive::delete_file(std::string_view id) +bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::Path &destination) { if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } - // Headers - curl::HeaderList headers = curl::new_header_list(); - curl::append_header(headers, m_authHeader); - - char urlBuffer[SIZE_URL_BUFFER] = {0}; - std::snprintf(urlBuffer, SIZE_URL_BUFFER, "%s/%s", URL_DRIVE_FILE_API.data(), id.data()); - - std::string response; - curl::reset_handle(m_curl); - curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); - curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); - curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); - - if (!curl::perform(m_curl)) - { - return false; - } - - json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); - if (GoogleDrive::error_occured(responseParser)) - { - return false; - } - - // Find the file and remove it from the list if it's found. - remote::Storage::List::iterator findFile = GoogleDrive::find_file_by_id(id); - if (findFile == m_list.end()) - { - logger::log("Error deleting file from Drive: file could not be located in local list."); - return false; - } - - m_list.erase(findFile); - - return true; -} - -bool remote::GoogleDrive::download_file(std::string_view id, const fslib::Path &destination) -{ - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } - - // Get iterator to file. - remote::Storage::List::iterator findFile = GoogleDrive::find_file_by_id(id); - if (findFile == m_list.end()) - { - logger::log("Error downloading file: File information could not be located in local list!"); - return false; - } - - // Try to open the file too before continuing. - fslib::File destinationFile(destinationFile, FsOpenMode_Create | FsOpenMode_Write, findFile->get_size()); + // Try to open the file too before continuing. Using a starting size speeds up write calls later. + fslib::File destinationFile(destination, FsOpenMode_Create | FsOpenMode_Write, file->get_size()); if (!destinationFile) { logger::log("Error downloading file: local file could not be opened for writing!"); return false; } - // Headers - curl::HeaderList headers = curl::new_header_list(); - curl::append_header(headers, m_authHeader); + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, m_authHeader); - // URL - char urlBuffer[SIZE_URL_BUFFER] = {0}; - std::snprintf(urlBuffer, SIZE_URL_BUFFER, "%s/%s?alt=media", URL_DRIVE_FILE_API.data(), id.data()); + remote::URL url{URL_DRIVE_FILE_API}; + url.append_path(file->get_id()).append_parameter("alt", "media"); - // Response string std::string response; - // Curl curl::prepare_get(m_curl); - curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); - curl::set_option(m_curl, CURLOPT_URL, urlBuffer); - // This is temporary until I get some stuff figured out. + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + // This is temporary until I get threading figured out again. curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_data_to_file); curl::set_option(m_curl, CURLOPT_WRITEDATA, &destinationFile); @@ -510,53 +360,418 @@ bool remote::GoogleDrive::download_file(std::string_view id, const fslib::Path & return true; } -bool remote::GoogleDrive::sign_in_required(void) const +bool remote::GoogleDrive::delete_item(const remote::Item *item) { - return !m_isInitialized || m_refreshToken.empty(); -} + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) + { + return false; + } -bool remote::GoogleDrive::get_sign_in_data(std::string &signInString, std::time_t &expiration, int &waitInterval) -{ - // Header list. - curl::HeaderList headers = curl::new_header_list(); - curl::append_header(headers, HEADER_CONTENT_TYPE_JSON.data()); + // Iterator is needed to remove it from the list. + auto findItem = std::find_if(m_list.begin(), m_list.end(), [item](const Item &listItem) { + return item->get_id() == listItem.get_id(); + }); - json::Object postJson = json::new_object(json_object_new_object); - json_object *clientId = json_object_new_string(m_clientId.c_str()); - json_object *scope = json_object_new_string(PARAM_DRIVE_FILE_SCOPE.data()); - json::add_object(postJson, JSON_KEY_CLIENT_ID.data(), clientId); - json::add_object(postJson, "scope", scope); + if (findItem == m_list.end()) + { + logger::log("Error deleting item: Item not found in list!"); + return false; + } - std::string response; - curl::prepare_get(m_curl); - curl::set_option(m_curl, CURLOPT_HTTPHEADER, headers.get()); - curl::set_option(m_curl, CURLOPT_URL, URL_OAUTH2_DEVICE_CODE.data()); - curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); - curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); - curl::set_option(m_curl, CURLOPT_POSTFIELDS, json_object_get_string(postJson.get())); + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, m_authHeader); + + remote::URL url{URL_DRIVE_FILE_API}; + url.append_path(item->get_id()); + + curl::reset_handle(m_curl); + curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); if (!curl::perform(m_curl)) { return false; } - json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); - if (!responseParser || GoogleDrive::error_occurred(responseParser)) + // This might be a better way to check? + long code = curl::get_response_code(m_curl); + if (code != 204) + { + logger::log("Error deleting item from Google Drive: %i.", code); + return false; + } + + // Erase from the master list. + m_list.erase(findItem); + + return true; +} + +bool remote::GoogleDrive::sign_in_required() const +{ + return !m_isInitialized || m_refreshToken.empty(); +} + +bool remote::GoogleDrive::get_sign_in_data(std::string &message, std::string &code, std::time_t &expiration, int &wait) +{ + static const char *STRING_SIGN_IN_ERROR = "Error requesting sign-in data: %s"; + // This is the the URL for device codes and limited input. + static const char *API_URL_DEVICE_CODE = "https://oauth2.googleapis.com/device/code"; + // This is a gimped version of what I used to use. + static const char *STRING_DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file"; + + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, HEADER_CONTENT_TYPE_FORM); + + remote::Form post{}; + post.append_parameter(JSON_KEY_CLIENT_ID, m_clientId).append_parameter("scope", STRING_DRIVE_FILE_SCOPE); + + std::string response; + curl::prepare_post(m_curl); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, API_URL_DEVICE_CODE); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); + curl::set_option(m_curl, CURLOPT_POSTFIELDS, post.get()); + + if (!curl::perform(m_curl)) { return false; } - json_object *deviceCode = json::get_object(responseParser, JSON_KEY_DEVICE_CODE.data()); - json_object *userCode = json::get_object(responseParser, "user_code"); - json_object *verificationUrl = json::get_object(responseParser, "verification_url"); - json_object *expiresIn = json::get_object(responseParser, JSON_KEY_EXPIRES_IN.data()); - json_object *interval = json::get_object(responseParser, "interval"); + json::Object parser = json::new_object(json_tokener_parse, response.c_str()); + if (!parser || GoogleDrive::error_occurred(parser)) + { + return false; + } + + json_object *deviceCode = json::get_object(parser, JSON_KEY_DEVICE_CODE); + json_object *userCode = json::get_object(parser, "user_code"); + json_object *verificationUrl = json::get_object(parser, "verification_url"); + json_object *expiresIn = json::get_object(parser, "expires_in"); + json_object *interval = json::get_object(parser, "interval"); + // These are required and fatal. if (!deviceCode || !userCode || !verificationUrl || !expiresIn || !interval) { - logger::log("Abnormal sign in response."); + logger::log(STRING_SIGN_IN_ERROR, "Malformed or bad response."); return false; } - // Set expiration time. + // I hate how this looks, but whatever. + message = stringutil::get_formatted_string(strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 0), + json_object_get_string(verificationUrl), + json_object_get_string(userCode)); + + code = json_object_get_string(deviceCode); + + // This is a quick, dirty way to get the expiration time. expiration = std::time(NULL) + json_object_get_uint64(expiresIn); + + // This should be the time in seconds to wait between verification checks. + wait = json_object_get_int64(interval); + + return true; +} + +bool remote::GoogleDrive::poll_sign_in(std::string_view code) +{ + static const char *STRING_ERROR_POLLING = "Error polling Google OAuth2: %s"; + // I'm just gonna save this pre-escaped instead of wasting time escaping it. + static const char *STRING_DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code"; + + // I'm not sure how else to make this really work with JKSV's task threading system? + remote::Form post{}; + post.append_parameter(JSON_KEY_CLIENT_ID, m_clientId) + .append_parameter(JSON_KEY_CLIENT_SECRET, m_clientSecret) + .append_parameter("device_code", code) + .append_parameter(JSON_KEY_GRANT_TYPE, STRING_DEVICE_GRANT); + + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, HEADER_CONTENT_TYPE_FORM); + + std::string response; + curl::prepare_get(m_curl); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, URL_OAUTH2_TOKEN_URL); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); + curl::set_option(m_curl, CURLOPT_POSTFIELDS, post.get()); + + if (!curl::perform(m_curl) || response.empty()) + { + return false; + } + + json::Object parser = json::new_object(json_tokener_parse, response.c_str()); + // This error isn't logged, because that's how you know if the user logged in or not. + if (!parser || GoogleDrive::error_occurred(parser, false)) + { + return false; + } + + json_object *accessToken = json::get_object(parser, JSON_KEY_ACCESS_TOKEN); + json_object *expiresIn = json::get_object(parser, JSON_KEY_EXPIRES_IN); + json_object *refreshToken = json::get_object(parser, JSON_KEY_REFRESH_TOKEN); + // All of these are required. + if (!accessToken || !expiresIn || !refreshToken) + { + logger::log(STRING_ERROR_POLLING, "Malformed response or missing data!"); + return false; + } + + m_token = json_object_get_string(accessToken); + m_refreshToken = json_object_get_string(refreshToken); + m_tokenExpires = std::time(NULL) + json_object_get_uint64(expiresIn); + + m_authHeader = std::string(HEADER_AUTH_BEARER) + m_token; + + json::Object config = json::new_object(json_object_from_file, remote::PATH_GOOGLE_DRIVE_CONFIG.data()); + if (config) + { + // We're going to attach the refresh token to the installed object so nothing bad can happen to it and I don't + // have to deal with Git issues about it. + json_object *installed = json::get_object(config, JSON_KEY_INSTALLED); + json_object *refreshToken = json_object_new_string(m_refreshToken.c_str()); + json_object_object_add(installed, JSON_KEY_REFRESH_TOKEN, refreshToken); + + fslib::File configFile(remote::PATH_GOOGLE_DRIVE_CONFIG, FsOpenMode_Create | FsOpenMode_Write); + if (configFile) + { + configFile << json_object_get_string(config.get()); + } + } + + // Not sure where else to really put this. + if (!GoogleDrive::get_root_id()) + { + return false; + } + + m_isInitialized = true; + + return true; +} + +bool remote::GoogleDrive::get_root_id() +{ + // This is the only place this is used. V3 of the API doesn't allow you to retrieve this for some reason? + static const char *API_URL_ABOUT_ROOT_ID = "https://www.googleapis.com/drive/v2/about?fields=rootFolderId"; + + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) + { + return false; + } + + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, m_authHeader); + + std::string response; + curl::prepare_get(m_curl); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, API_URL_ABOUT_ROOT_ID); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); + + if (!curl::perform(m_curl)) + { + return false; + } + + json::Object parser = json::new_object(json_tokener_parse, response.c_str()); + if (!parser) + { + return false; + } + + json_object *rootId = json::get_object(parser, "rootFolderId"); + if (!rootId) + { + logger::log("Error getting drive root directory ID!"); + return false; + } + + m_root = json_object_get_string(rootId); + m_parent = m_root; + + return true; +} + +bool remote::GoogleDrive::token_is_valid() const +{ + // I'm giving this a grace period just to be safe. + return std::time(NULL) < m_tokenExpires - 10; +} + +bool remote::GoogleDrive::refresh_token() +{ + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, HEADER_CONTENT_TYPE_FORM); + + + remote::Form post{}; + post.append_parameter(JSON_KEY_CLIENT_ID, m_clientId) + .append_parameter(JSON_KEY_CLIENT_SECRET, m_clientSecret) + .append_parameter(JSON_KEY_REFRESH_TOKEN, m_refreshToken) + .append_parameter(JSON_KEY_GRANT_TYPE, JSON_KEY_REFRESH_TOKEN); + + std::string response; + curl::prepare_post(m_curl); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, URL_OAUTH2_TOKEN_URL); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); + curl::set_option(m_curl, CURLOPT_POSTFIELDS, post.get()); + curl::set_option(m_curl, CURLOPT_POSTFIELDSIZE, post.length()); + + if (!curl::perform(m_curl)) + { + return false; + } + + json::Object parser = json::new_object(json_tokener_parse, response.c_str()); + if (!parser || GoogleDrive::error_occurred(parser)) + { + return false; + } + + // These are the only things I care about. + json_object *accessToken = json::get_object(parser, JSON_KEY_ACCESS_TOKEN); + json_object *expiresIn = json::get_object(parser, JSON_KEY_EXPIRES_IN); + if (!accessToken || !expiresIn) + { + return false; + } + + // Got our new access token. + m_token = json_object_get_string(accessToken); + // This is better than pinging Google's servers every single time. + m_tokenExpires = std::time(NULL) + json_object_get_uint64(expiresIn); + + m_authHeader = std::string(HEADER_AUTH_BEARER) + m_token; + + return true; +} + +bool remote::GoogleDrive::request_listing() +{ + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) + { + return false; + } + + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, m_authHeader.c_str()); + + remote::URL url{URL_DRIVE_FILE_API}; + url.append_parameter("fields", "nextPageToken,files(name,id,size,parents,mimeType)") + .append_parameter("orderBy", "name_natural") + .append_parameter("pageSize", "256") + .append_parameter("trashed", "false"); + + std::string response; + curl::prepare_get(m_curl); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); + + // This is used as the loop condition. + json_object *nextPageToken = nullptr; + do + { + // This needs to be cleared for every request. + response.clear(); + + if (!curl::perform(m_curl)) + { + return false; + } + + json::Object parser = json::new_object(json_tokener_parse, response.c_str()); + if (!parser || GoogleDrive::error_occurred(parser) || !GoogleDrive::process_listing(parser)) + { + logger::log("Error while parseing Google Drive response!"); + return false; + } + + nextPageToken = json::get_object(parser, "nextPageToken"); + if (nextPageToken) + { + remote::URL nextPage{url}; + nextPage.append_parameter("pageToken", json_object_get_string(nextPageToken)); + curl::set_option(m_curl, CURLOPT_URL, nextPage); + } + } while (nextPageToken); + + return true; +} + +bool remote::GoogleDrive::process_listing(json::Object &json) +{ + static const char *STRING_ERROR_PROCESSING = "Error processing Google Drive listing: %s"; + + json_object *files = json::get_object(json, "files"); + if (!files) + { + return false; + } + + size_t arrayLength = json_object_array_length(files); + for (size_t i = 0; i < arrayLength; i++) + { + json_object *currentFile = json_object_array_get_idx(files, i); + if (!currentFile) + { + return false; + } + + json_object *mimeType = json_object_object_get(currentFile, JSON_KEY_MIMETYPE); + json_object *parents = json_object_object_get(currentFile, JSON_KEY_PARENTS); + json_object *id = json_object_object_get(currentFile, JSON_KEY_ID); + json_object *name = json_object_object_get(currentFile, JSON_KEY_NAME); + json_object *size = json_object_object_get(currentFile, "size"); + // All of these are REQUIRED! Size doesn't exist for folders. + if (!mimeType || !parents || !id || !name) + { + logger::log(STRING_ERROR_PROCESSING, "Malformed or missing data!"); + continue; + } + + // I still think it's stupid this is an array when there can only be one... + json_object *parent = json_object_array_get_idx(parents, 0); + if (!parent) + { + logger::log(STRING_ERROR_PROCESSING, "Missing parent ID!"); + continue; + } + + m_list.emplace_back(json_object_get_string(name), + json_object_get_string(id), + json_object_get_string(parent), + size ? json_object_get_uint64(size) : 0, + std::strcmp(MIME_TYPE_DIRECTORY, json_object_get_string(mimeType)) == 0); + } + return true; +} + +bool remote::GoogleDrive::error_occurred(json::Object &json, bool log) +{ + json_object *error = json::get_object(json, "error"); + if (!error) + { + return false; + } + + // Google has so many different error response structures. I'm covering two here. Technically, + // I could grab the code too from the second response, that makes this even more of a headache. + json_object *description = json::get_object(json, "error_description"); + json_object *message = json_object_object_get(error, "message"); + if (log && (description || message)) + { + logger::log("Google Drive error: %s.", + description ? json_object_get_string(description) : json_object_get_string(message)); + } + + return true; } diff --git a/source/remote/Item.cpp b/source/remote/Item.cpp index 5294929..ce32cf1 100644 --- a/source/remote/Item.cpp +++ b/source/remote/Item.cpp @@ -1,29 +1,29 @@ #include "remote/Item.hpp" -remote::Item::Item(std::string_view name, std::string_view id, std::string_view parent, bool directory) - : m_name(name), m_id(id), m_parent(parent), m_isDirectory(directory) {}; +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) {}; -std::string_view remote::Item::get_name(void) const +std::string_view remote::Item::get_name() const { return m_name; } -std::string_view remote::Item::get_id(void) const +std::string_view remote::Item::get_id() const { return m_id; } -std::string_view remote::Item::get_parent_id(void) const +std::string_view remote::Item::get_parent_id() const { return m_parent; } -size_t remote::Item::get_size(void) const +size_t remote::Item::get_size() const { return m_size; } -bool remote::Item::is_directory(void) const +bool remote::Item::is_directory() const { return m_isDirectory; } diff --git a/source/remote/Storage.cpp b/source/remote/Storage.cpp index abda548..86ee19b 100644 --- a/source/remote/Storage.cpp +++ b/source/remote/Storage.cpp @@ -1,51 +1,87 @@ #include "remote/Storage.hpp" #include -bool remote::Storage::is_initialized(void) const +// Declarations here. Defined at bottom. +remote::Storage::Storage() : m_curl(curl::new_handle()) {}; + +bool remote::Storage::is_initialized() const { return m_isInitialized; } bool remote::Storage::directory_exists(std::string_view name) { - return Storage::find_directory(name) != m_list.end(); + return Storage::find_directory_by_name(name) != m_list.end(); } -bool remote::Storage::get_directory_id(std::string_view name, std::string &idOut) +void remote::Storage::return_to_root() { - remote::Storage::List::iterator findDir = Storage::find_directory(name); - if (findDir == m_list.end()) + m_parent = m_root; +} + +void remote::Storage::set_root_directory(remote::Item *root) +{ + m_root = root->get_id(); +} + +void remote::Storage::change_directory(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 false; + return nullptr; } - idOut = findDir->get_id(); - return true; + return &(*findDirectory); +} + +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()) + { + listing.push_back(&(*current)); + } + + return listing; } bool remote::Storage::file_exists(std::string_view name) { - return Storage::find_file(name) != m_list.end(); + return Storage::find_file_by_name(name) != m_list.end(); } -bool remote::Storage::get_file_id(std::string_view name, std::string &idOut) +remote::Item *remote::Storage::get_file_by_name(std::string_view name) { - remote::Storage::List::iterator findFile = Storage::find_file(name); + auto findFile = Storage::find_file_by_name(name); if (findFile == m_list.end()) { - return false; + return nullptr; } - idOut = findFile->get_id(); - return true; + return &(*findFile); } -remote::Storage::List::iterator remote::Storage::find_directory(std::string_view name) +bool remote::Storage::supports_utf8() const +{ + return m_utf8Paths; +} + +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; }); } -remote::Storage::List::iterator remote::Storage::find_file(std::string_view 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; diff --git a/source/remote/URL.cpp b/source/remote/URL.cpp new file mode 100644 index 0000000..c6aa917 --- /dev/null +++ b/source/remote/URL.cpp @@ -0,0 +1,90 @@ +#include "remote/URL.hpp" + +remote::URL::URL(std::string_view base) : m_url(base) {}; + +remote::URL::URL(const URL &url) +{ + m_url = url.m_url; +} + +remote::URL::URL(URL &&url) +{ + m_url = url.m_url; + // This seems odd, but w/e + url.m_url.clear(); +} + +remote::URL &remote::URL::operator=(const remote::URL &url) +{ + m_url = url.m_url; + + return *this; +} + +remote::URL &remote::URL::operator=(remote::URL &&url) +{ + m_url = url.m_url; + url.m_url.clear(); + + return *this; +} + +remote::URL &remote::URL::set_base(std::string_view base) +{ + // This will just assign and clear out the old one, I hope. + m_url = base; + + return *this; +} + +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("/"); + } + + // This is here to make WebDav easier to read and deal with in case of blank basepaths. + if (path.empty()) + { + return *this; + } + + m_url.append(path); + + return *this; +} + +remote::URL &remote::URL::append_parameter(std::string_view param, std::string_view value) +{ + URL::append_separator(); + m_url.append(param).append("=").append(value); + return *this; +} + +remote::URL &remote::URL::append_slash() +{ + if (m_url.back() != '/') + { + m_url.append("/"); + } + return *this; +} + +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("&"); + } +} diff --git a/source/remote/WebDav.cpp b/source/remote/WebDav.cpp new file mode 100644 index 0000000..e2ea76c --- /dev/null +++ b/source/remote/WebDav.cpp @@ -0,0 +1,390 @@ +#include "remote/WebDav.hpp" +#include "JSON.hpp" +#include "curl/curl.hpp" +#include "logger.hpp" +#include "remote/remote.hpp" +#include + +namespace +{ + const char *TAG_XML_HREF = "href"; +} + +// Declarations here. Definitions at bottom. +/// @brief Gets a XML element by name. This is namespace agnostic. +/// @param parent Parent XML element. +/// @param name Name of the element to get. No namespace needed. +static tinyxml2::XMLElement *get_element_by_name(tinyxml2::XMLElement *parent, std::string_view name); + +/// @brief Returns the starting point of the tag name without the namespace. +/// @param tag Tag to get the name of. +static std::string_view get_tag_begin(std::string_view tag); + +remote::WebDav::WebDav() : Storage() +{ + static const char *STRING_CONFIG_READ_ERROR = "Error initializing WebDav: %s"; + + // WebDav has problems with these. + m_utf8Paths = false; + + json::Object config = json::new_object(json_object_from_file, remote::PATH_WEBDAV_CONFIG.data()); + if (!config) + { + logger::log(STRING_CONFIG_READ_ERROR, "Error reading configuration file!"); + return; + } + + // Let's just get this all out of the way at once. + 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"); + + // This is the bare minimum required to continue. + if (!origin) + { + logger::log(STRING_CONFIG_READ_ERROR, "Config is missing origin!"); + return; + } + m_origin = json_object_get_string(origin); + + if (basepath) + { + // 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 = json_object_get_string(basepath); + m_parent = m_root; + } + + if (username) + { + m_username = json_object_get_string(username); + } + + 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}; + url.append_path(m_root).append_slash(); + + // This'll recursively get the full listing of the basepath for the WebDav server. + std::string xml{}; + if (!WebDav::prop_find(url, xml) || !WebDav::process_listing(xml)) + { + logger::log(STRING_CONFIG_READ_ERROR, "Error retrieving listing from WebDav server!"); + return; + } + + m_isInitialized = true; +} + +bool remote::WebDav::create_directory(std::string_view name) +{ + static const char *STRING_CREATE_DIR_ERROR = "Error creating WebDav directory: %s"; + + std::string escapedName; + if (!curl::escape_string(m_curl, name, escapedName)) + { + logger::log(STRING_CREATE_DIR_ERROR, "Error escaping directory name!"); + return false; + } + + remote::URL url{m_origin}; + url.append_path(m_parent).append_path(escapedName).append_slash(); + + curl::reset_handle(m_curl); + WebDav::append_credentials(); + 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::get_response_code(m_curl) != 201) + { + logger::log(STRING_CREATE_DIR_ERROR, name.data()); + return false; + } + + // This is the ID string so we can make WebDav work within the same framework as Google Drive. + std::string id = m_parent + "/" + escapedName + "/"; + m_list.emplace_back(name, id, m_parent, 0, true); + + return true; +} + +bool remote::WebDav::upload_file(const fslib::Path &source) +{ + static const char *STRING_ERROR_UPLOADING = "Error uploading file: %s"; + + fslib::File file(source, FsOpenMode_Read); + if (!file) + { + logger::log(STRING_ERROR_UPLOADING, fslib::get_error_string()); + return false; + } + + std::string escapedName; + if (!curl::escape_string(m_curl, source.get_filename(), escapedName)) + { + logger::log(STRING_ERROR_UPLOADING, "Failed to escape filename!"); + return false; + } + + remote::URL url{m_origin}; + url.append_path(m_parent).append_path(escapedName); + + 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); + + if (!curl::perform(m_curl)) + { + return false; + } + + m_list.emplace_back(source.get_filename(), escapedName, m_parent, file.get_size(), false); + + return true; +} + +bool remote::WebDav::patch_file(remote::Item *item, const fslib::Path &source) +{ + static const char *STRING_ERROR_PATCHING = "Error patching file: %s"; + + fslib::File file(source, FsOpenMode_Read); + if (!file) + { + logger::log(STRING_ERROR_PATCHING, fslib::get_error_string()); + return false; + } + + + remote::URL url{m_origin}; + url.append_path(m_parent).append_path(item->get_id()); + + 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); + + if (!curl::perform(m_curl)) + { + return false; + } + + // Just update the size this time. + item->set_size(file.get_size()); + + return true; +} + +bool remote::WebDav::download_file(const remote::Item *item, const fslib::Path &destination) +{ + static const char *STRING_ERROR_DOWNLOADING = "Error downloading file: %s"; + + fslib::File file(destination, FsOpenMode_Create | FsOpenMode_Write); + if (!file) + { + logger::log(STRING_ERROR_DOWNLOADING, fslib::get_error_string()); + return false; + } + + remote::URL url{m_origin}; + url.append_path(m_parent).append_path(item->get_id()); + + curl::reset_handle(m_curl); + WebDav::append_credentials(); + curl::set_option(m_curl, CURLOPT_HTTPGET, 1L); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + 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; + } + + return true; +} + +bool remote::WebDav::delete_item(const remote::Item *item) +{ + static const char *STRING_ERROR_DELETING = "Error deleting item: %s"; + + remote::URL url{m_origin}; + url.append_path(m_parent).append_path(item->get_id()); + 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::get_response_code(m_curl) != 204) + { + logger::log(STRING_ERROR_DELETING, "Deletion failed!"); + return false; + } + + return true; +} + +void remote::WebDav::append_credentials() +{ + 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()); + } +} + + +bool remote::WebDav::prop_find(const remote::URL &url, std::string &xml) +{ + // Some servers block Depth: Infinity. + curl::HeaderList header = curl::new_header_list(); + curl::append_header(header, "Depth: 1"); + + curl::reset_handle(m_curl); + WebDav::append_credentials(); + curl::set_option(m_curl, CURLOPT_CUSTOMREQUEST, "PROPFIND"); + curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); + curl::set_option(m_curl, CURLOPT_URL, url.get()); + curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); + curl::set_option(m_curl, CURLOPT_WRITEDATA, &xml); + + return curl::perform(m_curl); +} + +bool remote::WebDav::process_listing(std::string_view xml) +{ + static const char *STRING_ERROR_PROCESSING_XML = "Error processing XML: %s"; + + tinyxml2::XMLDocument listing{}; + if (listing.Parse(xml.data(), xml.length())) + { + logger::log(STRING_ERROR_PROCESSING_XML, "Couldn't parse XML!"); + return false; + } + + tinyxml2::XMLElement *root = listing.RootElement(); + + // The first element is what we're using as the parent. + tinyxml2::XMLElement *parent = root->FirstChildElement(); + + tinyxml2::XMLElement *parentLocation = get_element_by_name(parent, TAG_XML_HREF); + if (!parentLocation) + { + logger::log(STRING_ERROR_PROCESSING_XML, "Error finding list parent location!"); + return false; + } + + // There's no point in continuing if this fails. Just return true. + tinyxml2::XMLElement *current = parent->NextSiblingElement(); + if (!current) + { + return true; + } + + 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 *resourceType = get_element_by_name(prop, "resourcetype"); + if (!href || !propstat || !prop || !resourceType) + { + logger::log(STRING_ERROR_PROCESSING_XML, "Element is missing data!"); + continue; + } + + // This is the best way to detect a directory. + tinyxml2::XMLElement *collection = get_element_by_name(resourceType, "collection"); + if (collection) + { + // JKSV doesn't expose folder names to the end user, so they are emplaced as-is. + m_list.emplace_back(href->GetText(), href->GetText(), parentLocation->GetText(), 0, true); + + remote::URL nextUrl{m_origin}; + nextUrl.append_path(href->GetText()); + + std::string xml{}; + if (!WebDav::prop_find(nextUrl, xml) || !WebDav::process_listing(xml)) + { + logger::log(STRING_ERROR_PROCESSING_XML, href->GetText()); + } + } + else + { + tinyxml2::XMLElement *displayName = get_element_by_name(prop, "displayname"); + tinyxml2::XMLElement *getContentLength = get_element_by_name(prop, "getcontentlength"); + if (!displayName || !getContentLength) + { + logger::log(STRING_ERROR_PROCESSING_XML, "Missing needed tags for file!"); + continue; + } + + m_list.emplace_back(displayName->GetText(), + href->GetText(), + parentLocation->GetText(), + std::strtoll(getContentLength->GetText(), NULL, 10), + false); + } + } while ((current = current->NextSiblingElement())); + + return true; +} + +static tinyxml2::XMLElement *get_element_by_name(tinyxml2::XMLElement *parent, std::string_view name) +{ + tinyxml2::XMLElement *current = parent->FirstChildElement(); + if (!current) + { + return nullptr; + } + + do + { + if (get_tag_begin(current->Name()) == name) + { + return current; + } + } while ((current = current->NextSiblingElement())); + return nullptr; +} + +static std::string_view get_tag_begin(std::string_view tag) +{ + size_t colon = tag.find_first_of(':'); + if (colon == tag.npos) + { + return tag; + } + return tag.substr(colon + 1); +} diff --git a/source/remote/remote.cpp b/source/remote/remote.cpp new file mode 100644 index 0000000..d615c3a --- /dev/null +++ b/source/remote/remote.cpp @@ -0,0 +1,140 @@ +#include "remote/remote.hpp" +#include "StateManager.hpp" +#include "appStates/TaskState.hpp" +#include "logger.hpp" +#include "remote/GoogleDrive.hpp" +#include "remote/WebDav.hpp" +#include "strings.hpp" +#include "ui/PopMessageManager.hpp" +#include +#include +#include +#include + +namespace +{ + /// @brief This is just the string for finding and creating the JKSV dir. + const char *STRING_JKSV_DIR = "JKSV"; + + /// @brief This is the single (for now) instance of a storage class. + std::unique_ptr s_storage = nullptr; +} // namespace + +// Declarations here. Definitions at bottom. +/// @brief This is the thread function that handles logging into Google. +static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive); + +/// @brief This creates (if needed) the JKSV folder for Google Drive and sets it as the root. +/// @param drive Pointer to the drive instance.. +static void drive_set_jksv_root(remote::GoogleDrive *drive); + +void remote::initialize_google_drive() +{ + // Create drive instance. + s_storage = std::make_unique(); + + // Need to cast this for it to work right. + remote::GoogleDrive *drive = static_cast(s_storage.get()); + + if (drive->sign_in_required()) + { + auto signIn = std::make_shared(drive_sign_in, drive); + StateManager::push_state(signIn); + // We can return here because the task should handle the rest of the setup for us. + return; + } + + // To do: Handle this better. Maybe retry somehow? + if (!drive->is_initialized()) + { + return; + } + // Can't forget this. + drive_set_jksv_root(drive); + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 1)); +} + +void remote::initialize_webdav() +{ + s_storage = std::make_unique(); + + if (s_storage->is_initialized()) + { + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::WEBDAV_STRINGS, 0)); + } + else + { + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::WEBDAV_STRINGS, 1)); + } +} + +remote::Storage *remote::get_remote_storage() +{ + if (!s_storage || !s_storage->is_initialized()) + { + return nullptr; + } + return s_storage.get(); +} + +static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive) +{ + static const char *STRING_ERROR_SIGNING_IN = "Error signing into Google Drive: %s"; + + std::string message{}, deviceCode{}; + std::time_t expiration = 0; + int pollingInterval = 0; + + if (!drive->get_sign_in_data(message, deviceCode, expiration, pollingInterval)) + { + logger::log(STRING_ERROR_SIGNING_IN, "Getting sign in data failed!"); + task->finished(); + return; + } + + task->set_status(message.c_str()); + + while (std::time(NULL) < expiration && !drive->poll_sign_in(deviceCode)) + { + std::this_thread::sleep_for(std::chrono::seconds(pollingInterval)); + } + + if (drive->is_initialized()) + { + // Run this quick so the root is set correctly. + drive_set_jksv_root(drive); + // Show everyone I did it! + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 1)); + } + else + { + ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, + strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 2)); + } + + task->finished(); +} + +static void drive_set_jksv_root(remote::GoogleDrive *drive) +{ + static const char *STRING_ERROR_SETTING_DIR = "Error creating/setting JKSV directory on Drive: %s"; + + if (!drive->directory_exists(STRING_JKSV_DIR) && !drive->create_directory(STRING_JKSV_DIR)) + { + logger::log(STRING_ERROR_SETTING_DIR, "Error finding and/or creating directory!"); + return; + } + + remote::Item *jksvDir = drive->get_directory_by_name(STRING_JKSV_DIR); + if (!jksvDir) + { + logger::log(STRING_ERROR_SETTING_DIR, "Error locating directory in list! This shouldn't be able to happen!"); + return; + } + drive->set_root_directory(jksvDir); + drive->change_directory(jksvDir); +} diff --git a/source/strings.cpp b/source/strings.cpp index da8789a..8073d8e 100644 --- a/source/strings.cpp +++ b/source/strings.cpp @@ -32,7 +32,7 @@ namespace } // namespace // This returns the language file to use depending on the system's language. -static fslib::Path get_file_path(void) +static fslib::Path get_file_path() { fslib::Path returnPath = "romfs:/Text"; diff --git a/source/system/ProgressTask.cpp b/source/system/ProgressTask.cpp index 539b28c..bc5271e 100644 --- a/source/system/ProgressTask.cpp +++ b/source/system/ProgressTask.cpp @@ -11,12 +11,12 @@ void sys::ProgressTask::update_current(double current) m_current = current; } -double sys::ProgressTask::get_goal(void) const +double sys::ProgressTask::get_goal() const { return m_goal; } -double sys::ProgressTask::get_current(void) const +double sys::ProgressTask::get_current() const { return m_current / m_goal; } diff --git a/source/system/Task.cpp b/source/system/Task.cpp index bbfdc2c..1433343 100644 --- a/source/system/Task.cpp +++ b/source/system/Task.cpp @@ -12,12 +12,12 @@ sys::Task::~Task() m_thread.join(); } -bool sys::Task::is_running(void) const +bool sys::Task::is_running() const { return m_isRunning; } -void sys::Task::finished(void) +void sys::Task::finished() { m_isRunning = false; } @@ -35,7 +35,7 @@ void sys::Task::set_status(const char *format, ...) m_status = vaBuffer; } -std::string sys::Task::get_status(void) +std::string sys::Task::get_status() { std::scoped_lock StatusLock(m_statusLock); return m_status; diff --git a/source/system/Timer.cpp b/source/system/Timer.cpp index ee78ed0..1e04dd0 100644 --- a/source/system/Timer.cpp +++ b/source/system/Timer.cpp @@ -17,7 +17,7 @@ void sys::Timer::start(uint64_t triggerTicks) m_startingTicks = SDL_GetTicks64(); } -bool sys::Timer::is_triggered(void) +bool sys::Timer::is_triggered() { uint64_t currentTicks = SDL_GetTicks64(); @@ -34,7 +34,7 @@ bool sys::Timer::is_triggered(void) return true; } -void sys::Timer::restart(void) +void sys::Timer::restart() { m_startingTicks = SDL_GetTicks64(); } diff --git a/source/ui/ColorMod.cpp b/source/ui/ColorMod.cpp index badd7b7..f511f2d 100644 --- a/source/ui/ColorMod.cpp +++ b/source/ui/ColorMod.cpp @@ -1,6 +1,6 @@ #include "ui/ColorMod.hpp" -void ui::ColorMod::update(void) +void ui::ColorMod::update() { if (m_direction && (m_colorMod += 6) >= 0x72) { @@ -12,7 +12,7 @@ void ui::ColorMod::update(void) } } -ui::ColorMod::operator sdl::Color(void) const +ui::ColorMod::operator sdl::Color() const { return {static_cast((0x88 + m_colorMod) << 16 | (0xC5 + (m_colorMod / 2)) << 8 | 0xFF)}; } diff --git a/source/ui/IconMenu.cpp b/source/ui/IconMenu.cpp index 1c75d49..b7e8a25 100644 --- a/source/ui/IconMenu.cpp +++ b/source/ui/IconMenu.cpp @@ -28,7 +28,6 @@ void ui::IconMenu::render(SDL_Texture *target, bool hasFocus) } sdl::render_rect_fill(m_optionTarget->get(), 0, 0, 4, 130, {0x00FFC5FF}); } - //m_options.at(i)->render(m_optiontarget->Get(), 0, 0); m_options.at(i)->render_stretched(m_optionTarget->get(), 8, 1, 128, 128); m_optionTarget->render(target, m_x, tempY); } diff --git a/source/ui/Menu.cpp b/source/ui/Menu.cpp index 482e064..9dcf5b8 100644 --- a/source/ui/Menu.cpp +++ b/source/ui/Menu.cpp @@ -145,7 +145,7 @@ void ui::Menu::edit_option(int index, std::string_view newOption) m_options[index] = newOption.data(); } -int ui::Menu::get_selected(void) const +int ui::Menu::get_selected() const { return m_selected; } @@ -160,7 +160,7 @@ void ui::Menu::set_width(int width) m_width = width; } -void ui::Menu::reset(void) +void ui::Menu::reset() { m_selected = 0; m_y = m_originalY; diff --git a/source/ui/PopMessageManager.cpp b/source/ui/PopMessageManager.cpp index 29bc50a..ede4574 100644 --- a/source/ui/PopMessageManager.cpp +++ b/source/ui/PopMessageManager.cpp @@ -13,7 +13,7 @@ namespace } // namespace -void ui::PopMessageManager::update(void) +void ui::PopMessageManager::update() { // Grab instance. PopMessageManager &manager = PopMessageManager::get_instance(); @@ -65,7 +65,7 @@ void ui::PopMessageManager::update(void) } } -void ui::PopMessageManager::render(void) +void ui::PopMessageManager::render() { // Get instance. PopMessageManager &manager = PopMessageManager::get_instance(); diff --git a/source/ui/SlideOutPanel.cpp b/source/ui/SlideOutPanel.cpp index bbcb520..79dd9a6 100644 --- a/source/ui/SlideOutPanel.cpp +++ b/source/ui/SlideOutPanel.cpp @@ -59,29 +59,29 @@ void ui::SlideOutPanel::render(SDL_Texture *Target, bool hasFocus) m_renderTarget->render(NULL, m_x, 0); } -void ui::SlideOutPanel::clear_target(void) +void ui::SlideOutPanel::clear_target() { m_renderTarget->clear(colors::SLIDE_PANEL_CLEAR); } -void ui::SlideOutPanel::reset(void) +void ui::SlideOutPanel::reset() { m_x = m_side == Side::Left ? -(m_width) : 1280.0f; m_isOpen = false; m_closePanel = false; } -void ui::SlideOutPanel::close(void) +void ui::SlideOutPanel::close() { m_closePanel = true; } -bool ui::SlideOutPanel::is_open(void) const +bool ui::SlideOutPanel::is_open() const { return m_isOpen; } -bool ui::SlideOutPanel::is_closed(void) const +bool ui::SlideOutPanel::is_closed() const { return m_closePanel && (m_side == Side::Left ? m_x > -(m_width) : m_x < 1280); } @@ -91,12 +91,12 @@ void ui::SlideOutPanel::push_new_element(std::shared_ptr newElement m_elements.push_back(newElement); } -void ui::SlideOutPanel::clear_elements(void) +void ui::SlideOutPanel::clear_elements() { m_elements.clear(); } -SDL_Texture *ui::SlideOutPanel::get_target(void) +SDL_Texture *ui::SlideOutPanel::get_target() { return m_renderTarget->get(); } diff --git a/source/ui/TitleTile.cpp b/source/ui/TitleTile.cpp index 89c7f28..b553250 100644 --- a/source/ui/TitleTile.cpp +++ b/source/ui/TitleTile.cpp @@ -30,18 +30,18 @@ void ui::TitleTile::render(SDL_Texture *target, int x, int y) } } -void ui::TitleTile::reset(void) +void ui::TitleTile::reset() { m_renderWidth = 128; m_renderHeight = 128; } -int ui::TitleTile::get_width(void) const +int ui::TitleTile::get_width() const { return m_renderWidth; } -int ui::TitleTile::get_height(void) const +int ui::TitleTile::get_height() const { return m_renderHeight; } diff --git a/source/ui/TitleView.cpp b/source/ui/TitleView.cpp index ce89f1e..5bcb474 100644 --- a/source/ui/TitleView.cpp +++ b/source/ui/TitleView.cpp @@ -111,12 +111,12 @@ 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(void) const +int ui::TitleView::get_selected() const { return m_selected; } -void ui::TitleView::refresh(void) +void ui::TitleView::refresh() { // Clear the current tiles. m_titleTiles.clear(); @@ -140,7 +140,7 @@ void ui::TitleView::refresh(void) } } -void ui::TitleView::reset(void) +void ui::TitleView::reset() { for (ui::TitleTile ¤tTile : m_titleTiles) {