remote::Storage just barely working.

This commit is contained in:
J-D-K 2025-07-04 13:06:47 -04:00
parent e29db89706
commit e27e0c8e51
92 changed files with 2240 additions and 1044 deletions

@ -1 +1 @@
Subproject commit bc0bf99b32ef89a7892093737407977b3e649e79
Subproject commit 403a0993d5ed2bffcf0ac9b298be95279d0d3749

View File

@ -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

View File

@ -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.

View File

@ -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<std::shared_ptr<AppState>> sm_stateVector;

View File

@ -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.

View File

@ -10,7 +10,7 @@
#include <memory>
/// @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();
};

View File

@ -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.

View File

@ -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 <typename TaskType, typename StateType, typename StructType>
class ConfirmState : public AppState
class ConfirmState final : public AppState
{
public:
/// @brief All functions passed to this state need to follow this signature: void function(<TaskType> *, std::shared_ptr<<StructType>>)
@ -39,9 +39,8 @@ class ConfirmState : public AppState
bool holdRequired,
TaskFunction function,
std::shared_ptr<StructType> 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);

View File

@ -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

View File

@ -6,26 +6,26 @@
#include <memory>
/// @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.

View File

@ -5,7 +5,7 @@
#include <switch.h>
/// @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.

View File

@ -7,7 +7,7 @@
#include <memory>
/// @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.

View File

@ -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();
};

View File

@ -4,7 +4,7 @@
#include <switch.h>
/// @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.

View File

@ -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.

View File

@ -7,7 +7,7 @@
#include <memory>
#include <string>
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.

View File

@ -6,7 +6,7 @@
#include "ui/SlideOutPanel.hpp"
#include <memory>
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

View File

@ -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.

View File

@ -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.

View File

@ -7,7 +7,7 @@
#include <memory>
/// @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

View File

@ -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.

View File

@ -1,6 +1,5 @@
#pragma once
#include "fslib.hpp"
#include "logger.hpp"
#include <curl/curl.h>
#include <memory>
#include <string>
@ -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<CURL, decltype(&curl_easy_cleanup)>;
@ -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);

View File

@ -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

View File

@ -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.

View File

@ -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);
}

View File

@ -26,7 +26,7 @@ namespace data
/// @brief Returns a reference to the title info map.
/// @return Reference to TitleInfoMap.
std::unordered_map<uint64_t, data::TitleInfo> &get_title_info_map(void);
std::unordered_map<uint64_t, data::TitleInfo> &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.

View File

@ -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.

View File

@ -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.

43
include/remote/Form.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <string>
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

View File

@ -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

View File

@ -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.

View File

@ -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<remote::Item *>;
/// @brief This makes writing some stuff for these classes way easier.
using List = std::vector<remote::Item>;
/// @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

60
include/remote/URL.hpp Normal file
View File

@ -0,0 +1,60 @@
#pragma once
#include <string>
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

58
include/remote/WebDav.hpp Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include "remote/Storage.hpp"
#include "remote/URL.hpp"
#include <string>
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

19
include/remote/remote.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "remote/Storage.hpp"
#include <memory>
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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -8,7 +8,7 @@ namespace ui
{
public:
/// @brief Default constructor.
Element(void) = default;
Element() = default;
/// @brief Virtual destructor.
virtual ~Element() {};

View File

@ -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.

View File

@ -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.

View File

@ -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;

View File

@ -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<ui::Element> 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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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!"
]
}

View File

@ -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<MainMenuState>());
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);

View File

@ -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<AppState> newState)
instance.sm_stateVector.push_back(newState);
}
StateManager &StateManager::get_instance(void)
StateManager &StateManager::get_instance()
{
static StateManager instance;
return instance;

View File

@ -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;
}

View File

@ -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<BackupMenu
static void restore_backup(sys::ProgressTask *task, std::shared_ptr<BackupMenuState::DataStruct> 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<BackupMenuState::DataStruct> dataStruct);
// Uploads a backup to the remote server.
static void upload_backup(sys::ProgressTask *task, std::shared_ptr<BackupMenuState::DataStruct> 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<BackupMenuState::DataStruct>())
{
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<ProgressState>(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<ConfirmState<sys::ProgressTask, ProgressState, BackupMenuState::DataStruct>>(
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<ConfirmState<sys::ProgressTask, ProgressState, BackupMenuState::DataStruct>>(
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<ConfirmState<sys::Task, TaskState, BackupMenuState::DataStruct>>(
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<ProgressState>(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<ProgressState>(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<ConfirmState<sys::ProgressTask, ProgressState, BackupMenuState::DataStruct>>(
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<ConfirmState<sys::ProgressTask, ProgressState, BackupMenuState::DataStruct>>(
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<ConfirmState<sys::Task, TaskState, BackupMenuState::DataStruct>>(
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_ptr<BackupMenu
static const char *STRING_ERROR_PREFIX = "Error overwriting backup: %s";
// Might 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());
// 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_ptr<BackupMenu
bool hasMeta = fs::fill_save_meta_data(saveInfo, meta);
if (std::strstr(STRING_ZIP_EXTENSION.data(), dataStruct->m_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_ptr<BackupMenu
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, backupZip, task);
zipClose(backupZip, NULL);
} // I hope this check works for making sure this is a folder
else if (dataStruct->m_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_ptr<BackupMenu
}
}
fs::copy_directory(fs::DEFAULT_SAVE_ROOT, dataStruct->m_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<BackupMenu
static void restore_backup(sys::ProgressTask *task, std::shared_ptr<BackupMenuState::DataStruct> 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_ptr<BackupMenuSt
return;
}
if (fslib::directory_exists(dataStruct->m_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_ptr<BackupMenuSt
}
}
fs::copy_directory(dataStruct->m_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_ptr<BackupMenuSt
fs::copy_zip_to_directory(targetZip,
fs::DEFAULT_SAVE_ROOT,
dataStruct->m_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_ptr<BackupMenuState::Data
{
if (task)
{
task->set_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<BackupMenuState::DataStruct> 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();
}

View File

@ -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());

View File

@ -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());

View File

@ -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<TitleSelectCommon *>(sm_states.at(selected).get());
TitleSelectCommon *targetTitleSelect = static_cast<TitleSelectCommon *>(sm_states.at(selected).get());
StateManager::push_state(std::make_shared<UserOptionState>(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<TitleSelectCommon>(sm_states.at(i))->refresh();
static_cast<TitleSelectCommon *>(sm_states.at(i).get())->refresh();
}
}

View File

@ -8,7 +8,7 @@
#include "ui/render_functions.hpp"
#include <cmath>
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);

View File

@ -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;
}

View File

@ -9,18 +9,40 @@
#include "logger.hpp"
#include "strings.hpp"
#include "stringutil.hpp"
#include <array>
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<std::string_view, 19> 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;
}

View File

@ -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();

View File

@ -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++)

View File

@ -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;

View File

@ -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;
}

View File

@ -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())
{

View File

@ -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<BackupMenuState>(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();
}

View File

@ -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;
}

View File

@ -5,26 +5,23 @@
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
namespace
{
// Makes stuff slightly easier to read.
using ConfigPair = std::pair<std::string, uint8_t>;
/// @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<ConfigPair> s_configVector;
/// @brief This map holds the majority of config values.
std::map<std::string, uint8_t> s_configMap;
// Working directory
fslib::Path s_workingDirectory;
@ -36,7 +33,7 @@ namespace
std::vector<uint64_t> s_blacklist;
// Map of paths.
std::unordered_map<uint64_t, std::string> s_pathMap;
std::map<uint64_t, std::string> s_pathMap;
} // namespace
static void read_array_to_vector(std::vector<uint64_t> &vector, json_object *array)
@ -56,7 +53,7 @@ static void read_array_to_vector(std::vector<uint64_t> &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<int>(s_configVector.size()))
{
return 0;
}
return s_configVector.at(index).second;
}
void config::toggle_by_index(int index)
{
if (index < 0 || index >= static_cast<int>(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<int>(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;
}

View File

@ -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...
}

View File

@ -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();

View File

@ -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);

View File

@ -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<uint64_t, data::TitleInfo> &data::get_title_info_map(void)
std::unordered_map<uint64_t, data::TitleInfo> &data::get_title_info_map()
{
return s_titleInfoMap;
}
@ -157,7 +157,7 @@ void data::get_title_info_by_type(FsSaveDataType saveType, std::vector<data::Tit
}
}
static bool load_create_user_accounts(void)
static bool load_create_user_accounts()
{
// For saving total accounts found.
int total = 0;
@ -206,7 +206,7 @@ static bool load_create_user_accounts(void)
return true;
}
static void load_application_records(void)
static void load_application_records()
{
// Record struct/buffer.
NsApplicationRecord record = {0};
@ -223,7 +223,7 @@ static void load_application_records(void)
}
}
static void import_svi_files(void)
static void import_svi_files()
{
// Path.
fslib::Path sviPath = config::get_working_directory() / "svi";
@ -271,7 +271,7 @@ static void import_svi_files(void)
}
}
static bool read_cache_file(void)
static bool read_cache_file()
{
// Try opening the cache file.
fslib::File cache(PATH_CACHE_PATH, FsOpenMode_Read);
@ -311,7 +311,7 @@ static bool read_cache_file(void)
return true;
}
static void create_cache_file(void)
static void create_cache_file()
{
// First try creating and opening the file.
fslib::File cache(PATH_CACHE_PATH, FsOpenMode_Create | FsOpenMode_Write);

View File

@ -5,13 +5,13 @@ namespace
PadState s_gamepad;
}
void input::initialize(void)
void input::initialize()
{
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
padInitializeDefault(&s_gamepad);
}
void input::update(void)
void input::update()
{
padUpdate(&s_gamepad);
}

View File

@ -12,7 +12,7 @@ namespace
constexpr size_t VA_BUFFER_SIZE = 0x1000;
} // namespace
void logger::initialize(void)
void logger::initialize()
{
// Create log path and empty the log for this run.
s_logFilePath = "sdmc:/config/JKSV/JKSV.log";

View File

@ -2,7 +2,7 @@
#include "config.hpp"
#include <switch.h>
int main(void)
int main()
{
JKSV jksv{};
while (appletMainLoop() && jksv.is_running())

45
source/remote/Form.cpp Normal file
View File

@ -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();
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -1,51 +1,87 @@
#include "remote/Storage.hpp"
#include <algorithm>
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;

90
source/remote/URL.cpp Normal file
View File

@ -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("&");
}
}

390
source/remote/WebDav.cpp Normal file
View File

@ -0,0 +1,390 @@
#include "remote/WebDav.hpp"
#include "JSON.hpp"
#include "curl/curl.hpp"
#include "logger.hpp"
#include "remote/remote.hpp"
#include <tinyxml2.h>
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);
}

140
source/remote/remote.cpp Normal file
View File

@ -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 <chrono>
#include <ctime>
#include <memory>
#include <thread>
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<remote::Storage> 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<remote::GoogleDrive>();
// Need to cast this for it to work right.
remote::GoogleDrive *drive = static_cast<remote::GoogleDrive *>(s_storage.get());
if (drive->sign_in_required())
{
auto signIn = std::make_shared<TaskState>(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<remote::WebDav>();
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);
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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<std::mutex> StatusLock(m_statusLock);
return m_status;

View File

@ -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();
}

View File

@ -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<uint32_t>((0x88 + m_colorMod) << 16 | (0xC5 + (m_colorMod / 2)) << 8 | 0xFF)};
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -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<ui::Element> 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();
}

View File

@ -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;
}

View File

@ -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 &currentTile : m_titleTiles)
{