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

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