From ba6ad3f20df82034d9194fd846c0b151a4f4584c Mon Sep 17 00:00:00 2001 From: J-D-K Date: Fri, 15 Aug 2025 15:43:03 -0400 Subject: [PATCH] Add DataContext for data cleanup, add cache invalidation at boot. --- include/appstates/DataLoadingState.hpp | 33 +-- include/data/DataContext.hpp | 80 +++++++ include/data/TitleInfo.hpp | 6 + include/data/User.hpp | 5 + include/data/data.hpp | 4 - include/remote/Storage.hpp | 12 ++ source/appstates/DataLoadingState.cpp | 21 +- source/data/DataContext.cpp | 264 +++++++++++++++++++++++ source/data/TitleInfo.cpp | 4 +- source/data/data.cpp | 279 ++----------------------- source/remote/GoogleDrive.cpp | 4 +- source/remote/Storage.cpp | 37 ++++ 12 files changed, 452 insertions(+), 297 deletions(-) create mode 100644 include/data/DataContext.hpp create mode 100644 source/data/DataContext.cpp diff --git a/include/appstates/DataLoadingState.hpp b/include/appstates/DataLoadingState.hpp index 4749799..3ac1d35 100644 --- a/include/appstates/DataLoadingState.hpp +++ b/include/appstates/DataLoadingState.hpp @@ -1,6 +1,7 @@ #pragma once #include "StateManager.hpp" #include "appstates/BaseTask.hpp" +#include "data/DataContext.hpp" #include "sdl.hpp" #include @@ -13,23 +14,34 @@ class DataLoadingState final : public BaseTask using DestructFunction = std::function; template - DataLoadingState(void (*function)(sys::Task *, Args...), Args... args) + DataLoadingState(data::DataContext &context, + DestructFunction destructFunction, + void (*function)(sys::Task *, Args...), + Args... args) : BaseTask() + , m_context(context) + , m_destructFunction(destructFunction) { DataLoadingState::initialize_static_members(); m_task = std::make_unique(function, std::forward(args)...); } template - static std::shared_ptr create(void (*function)(sys::Task *, Args...), Args... args) + static std::shared_ptr create(data::DataContext &context, + DestructFunction destructFunction, + void (*function)(sys::Task *, Args...), + Args... args) { - return std::make_shared(function, std::forward(args)...); + return std::make_shared(context, destructFunction, function, std::forward(args)...); } template - static std::shared_ptr create_and_push(void (*function)(sys::Task *, Args...), Args... args) + static std::shared_ptr create_and_push(data::DataContext &context, + DestructFunction destructFunction, + void (*function)(sys::Task *, Args...), + Args... args) { - auto newState = DataLoadingState::create(function, std::forward(args)...); + auto newState = DataLoadingState::create(context, destructFunction, function, std::forward(args)...); StateManager::push_state(newState); return newState; } @@ -43,18 +55,15 @@ class DataLoadingState final : public BaseTask /// @brief Render override. void render() override; - /// @brief Executes the destruct functions passed. - void execute_destructs(); - - /// @brief Adds a function to the vector to be executed upon destruction. - void add_destruct_function(DataLoadingState::DestructFunction function); - private: + /// @brief Reference to the data context to run post-init operations. + data::DataContext &m_context; + /// @brief X coord of the status text. int m_statusX{}; /// @brief The functions called upon destruction. - std::vector m_destructFunctions{}; + DestructFunction m_destructFunction{}; /// @brief Icon displayed in the center of the screen. static inline sdl::SharedTexture sm_jksvIcon{}; diff --git a/include/data/DataContext.hpp b/include/data/DataContext.hpp new file mode 100644 index 0000000..00583f4 --- /dev/null +++ b/include/data/DataContext.hpp @@ -0,0 +1,80 @@ +#pragma once +#include "data/DataCommon.hpp" +#include "data/TitleInfo.hpp" +#include "data/User.hpp" +#include "sys/Task.hpp" + +#include +#include +#include + +namespace data +{ + class DataContext + { + public: + /// @brief Default constructor. + DataContext() = default; + + /// @brief Loads the users from the system and creates the accounts for system users. + bool load_create_users(sys::Task *task); + + /// @brief Loops and runs the load routine for all users found. + void load_user_save_info(sys::Task *task); + + /// @brief Gets a vector of pointers to the users loaded. + void get_users(data::UserList &listOut); + + /// @brief Loads the titles from the Switch's application records. + void load_application_records(sys::Task *task); + + /// @brief Returns whether a title is loaded with the application ID passed. + bool title_is_loaded(uint64_t applicationID); + + /// @brief Attempts to load a title with the application ID passed. + void load_title(uint64_t applicationID); + + /// @brief Returns the title info mapped to applicationID. nullptr on not found. + data::TitleInfo *get_title_by_id(uint64_t applicationID); + + /// @brief Gets a vector of pointers to all of the current title info instances. + void get_title_info_list(data::TitleInfoList &listOut); + + /// @brief Gets a list of title info that has savedata for type. + void get_title_info_list_by_type(FsSaveDataType type, data::TitleInfoList &listOut); + + /// @brief Imports the SVI files from the SD card. + void import_svi_files(sys::Task *task); + + /// @brief Attempts to read the cache file from the SD card. + bool read_cache(sys::Task *task); + + /// @brief Writes the cache to file. + bool write_cache(sys::Task *task); + + /// @brief Processes the icon queue. + void process_icon_queue(); + + private: + /// @brief User vector. + std::vector m_users{}; + + /// @brief Map of titles paired with their application ID. + std::unordered_map m_titleInfo{}; + + /// @brief Queue of the above to process the icons. + std::vector m_iconQueue{}; + + /// @brief Mutex for users. + std::mutex m_userMutex{}; + + /// @brief Mutex for titles. + std::mutex m_titleMutex{}; + + /// @brief Mutex to make sure the icon queue doesn't get mutilated. + std::mutex m_iconQueueMutex{}; + + /// @brief Whether or not the cache is still valid. + bool m_cacheIsValid{}; + }; +} diff --git a/include/data/TitleInfo.hpp b/include/data/TitleInfo.hpp index b868a52..c86cca1 100644 --- a/include/data/TitleInfo.hpp +++ b/include/data/TitleInfo.hpp @@ -5,9 +5,15 @@ #include #include #include +#include namespace data { + class TitleInfo; + + /// @brief Vector of pointers to titleinfo instances. + using TitleInfoList = std::vector; + /// @brief Class that holds data related to titles loaded from the system. class TitleInfo final : public data::DataCommon { diff --git a/include/data/User.hpp b/include/data/User.hpp index 7fd1142..c5eb486 100644 --- a/include/data/User.hpp +++ b/include/data/User.hpp @@ -10,6 +10,8 @@ namespace data { + class User; + /// @brief Type used to store save info and play statistics in the vector. Vector is used to preserve the order since I /// can't use a map without having to extra heap allocate it. using UserDataEntry = std::pair>; @@ -17,6 +19,9 @@ namespace data /// @brief Type definition for the user save info/play stats vector. using UserSaveInfoList = std::vector; + /// @brief A vector of pointers to User instances. + using UserList = std::vector; + /// @brief Class that stores data for the user. class User final : public data::DataCommon { diff --git a/include/data/data.hpp b/include/data/data.hpp index 3a0942e..c252bcb 100644 --- a/include/data/data.hpp +++ b/include/data/data.hpp @@ -9,10 +9,6 @@ namespace data { - /// @brief Declaration for user list/user pointer vector. - using UserList = std::vector; - using TitleInfoList = std::vector; - /// @brief Launches the data loading/initialization state. /// @param clear Whether or not the cache should be cleared. /// @param onDestruction Function that is executed upon destruction of the data loading screen. diff --git a/include/remote/Storage.hpp b/include/remote/Storage.hpp index f976be9..ce8fb91 100644 --- a/include/remote/Storage.hpp +++ b/include/remote/Storage.hpp @@ -133,10 +133,22 @@ namespace remote /// @param name Name to search for. Storage::List::iterator find_directory_by_name(std::string_view name); + /// @brief Searches the list for a directory matching ID. + /// @param id ID of the directory to search for. + Storage::List::iterator find_directory_by_id(std::string_view id); + /// @brief Searches to find if a file with name exists within the current parent. /// @param name Name of the file to search for. Storage::List::iterator find_file_by_name(std::string_view name); + /// @brief Searches the list for a file matching ID. + /// @param id ID to search for.s + Storage::List::iterator find_file_by_id(std::string_view id); + + /// @brief Locates any item (directory/file) by the id passed. + /// @param id ID to search for. + Storage::List::iterator find_item_by_id(std::string_view id); + /// @brief Searches starting with the iterator start for items that belong to parentID /// @param start Beginning iterator for search. /// @param parentID ParentID to match. diff --git a/source/appstates/DataLoadingState.cpp b/source/appstates/DataLoadingState.cpp index f6f7fe7..cffe6a2 100644 --- a/source/appstates/DataLoadingState.cpp +++ b/source/appstates/DataLoadingState.cpp @@ -1,19 +1,26 @@ #include "appstates/DataLoadingState.hpp" -#include "appstates/FadeState.hpp" #include "colors.hpp" +#include "logger.hpp" namespace { constexpr int SCREEN_CENTER = 640; } -DataLoadingState::~DataLoadingState() { DataLoadingState::execute_destructs(); } +DataLoadingState::~DataLoadingState() +{ + // This is to catch stragglers. + m_context.process_icon_queue(); + if (m_destructFunction) { m_destructFunction(); } +} void DataLoadingState::update() { static constexpr int SCREEN_CENTER = 640; + m_context.process_icon_queue(); + BaseTask::update(); const std::string status = m_task->get_status(); const int statusWidth = sdl::text::get_width(22, status); @@ -32,16 +39,6 @@ void DataLoadingState::render() BaseTask::render_loading_glyph(); } -void DataLoadingState::execute_destructs() -{ - for (auto &function : m_destructFunctions) { function(); } -} - -void DataLoadingState::add_destruct_function(DataLoadingState::DestructFunction function) -{ - m_destructFunctions.push_back(function); -} - void DataLoadingState::initialize_static_members() { if (sm_jksvIcon) { return; } diff --git a/source/data/DataContext.cpp b/source/data/DataContext.cpp new file mode 100644 index 0000000..61f7ff0 --- /dev/null +++ b/source/data/DataContext.cpp @@ -0,0 +1,264 @@ +#include "data/DataContext.hpp" + +#include "config.hpp" +#include "error.hpp" +#include "fs/fs.hpp" +#include "logger.hpp" +#include "strings.hpp" +#include "stringutil.hpp" + +namespace +{ + /// @brief This is the path to the cache file. + constexpr std::string_view PATH_CACHE_FILE = "sdmc:/config/JKSV/cache.zip"; + + /// @brief This is used in multiple places. + constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData); +} + +bool data::DataContext::load_create_users(sys::Task *task) +{ + static constexpr int32_t MAX_SWITCH_ACCOUNTS = 8; + + static constexpr AccountUid ID_SYSTEM_USER = {FsSaveDataType_System}; + static constexpr AccountUid ID_BCAT_USER = {FsSaveDataType_Bcat}; + static constexpr AccountUid ID_DEVICE_USER = {FsSaveDataType_Device}; + static constexpr AccountUid ID_CACHE_USER = {FsSaveDataType_Cache}; + + static constexpr const char *systemSafe = "System"; + static constexpr const char *bcatSafe = "BCAT"; + static constexpr const char *deviceSafe = "Device"; + static constexpr const char *cacheSafe = "Cache"; + + if (error::is_null(task) || !m_users.empty()) { return false; } + + const char *statusLoading = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 0); + const char *statusCreating = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 1); + const char *systemName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 0); + const char *bcatName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 2); + const char *deviceName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 3); + const char *cacheName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 5); + task->set_status(statusLoading); + + int total{}; + AccountUid accounts[8]{}; + const bool accountError = error::libnx(accountListAllUsers(accounts, MAX_SWITCH_ACCOUNTS, &total)); + const bool noAccounts = total <= 0; + if (accountError || noAccounts) { return false; } + + { + std::lock_guard userGuard{m_userMutex}; + for (int i = 0; i < total; i++) { m_users.emplace_back(accounts[i], FsSaveDataType_Account); } + + task->set_status(statusCreating); + m_users.emplace_back(ID_DEVICE_USER, deviceName, deviceSafe, FsSaveDataType_Device); + m_users.emplace_back(ID_BCAT_USER, bcatName, bcatSafe, FsSaveDataType_Bcat); + m_users.emplace_back(ID_CACHE_USER, cacheName, cacheSafe, FsSaveDataType_Cache); + m_users.emplace_back(ID_SYSTEM_USER, systemName, systemSafe, FsSaveDataType_System); + } + + std::lock_guard iconGuard{m_iconQueueMutex}; + for (data::User &user : m_users) { m_iconQueue.push_back(&user); } + + return true; +} + +void data::DataContext::load_user_save_info(sys::Task *task) +{ + if (error::is_null(task)) { return; } + + const char *statusLoadingUserInfo = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 5); + + for (data::User &user : m_users) + { + std::lock_guard userGuard{m_userMutex}; + { + const char *nickname = user.get_nickname(); + const std::string status = stringutil::get_formatted_string(statusLoadingUserInfo, nickname); + task->set_status(status); + } + user.load_user_data(); + } +} + +void data::DataContext::get_users(data::UserList &listOut) +{ + std::lock_guard userGuard{m_userMutex}; + for (data::User &user : m_users) { listOut.push_back(&user); } +} + +void data::DataContext::load_application_records(sys::Task *task) +{ + if (error::is_null(task)) { return; } + + const char *statusLoadingRecords = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 2); + + int offset{}, count{}; + NsApplicationRecord record{}; + + bool listError{}; + do { + listError = error::libnx(nsListApplicationRecord(&record, 1, offset++, &count)) || count <= 0; + if (listError) { break; } + if (DataContext::title_is_loaded(record.application_id)) { continue; } + + { + const std::string status = stringutil::get_formatted_string(statusLoadingRecords, record.application_id); + task->set_status(status); + } + DataContext::load_title(record.application_id); + } while (!listError); +} + +bool data::DataContext::title_is_loaded(uint64_t applicationID) +{ + const bool isSystem = applicationID & 0x8000000000000000; + + std::lock_guard titleGuard{m_titleMutex}; + const bool loaded = m_titleInfo.find(applicationID) != m_titleInfo.end(); + if (!isSystem && !loaded) { m_cacheIsValid = false; } + return loaded; +} + +void data::DataContext::load_title(uint64_t applicationID) +{ + std::scoped_lock titleGuard{m_titleMutex, m_iconQueueMutex}; + m_titleInfo.emplace(applicationID, applicationID); + m_iconQueue.push_back(&m_titleInfo.at(applicationID)); +} + +data::TitleInfo *data::DataContext::get_title_by_id(uint64_t applicationID) +{ + std::lock_guard titleGuard{m_titleMutex}; + auto findTitle = m_titleInfo.find(applicationID); + if (findTitle == m_titleInfo.end()) { return nullptr; } + return &findTitle->second; +} + +void data::DataContext::get_title_info_list(data::TitleInfoList &listOut) +{ + std::lock_guard titleGuard{m_titleMutex}; + for (auto &[applicationID, titleInfo] : m_titleInfo) { listOut.push_back(&titleInfo); } +} + +void data::DataContext::get_title_info_list_by_type(FsSaveDataType type, data::TitleInfoList &listOut) +{ + std::lock_guard titleGuard{m_titleMutex}; + for (auto &[application, titleInfo] : m_titleInfo) + { + if (titleInfo.has_save_data_type(type)) { listOut.push_back(&titleInfo); } + } +} + +void data::DataContext::import_svi_files(sys::Task *task) +{ + static constexpr size_t SIZE_UINT64 = sizeof(uint64_t); + static constexpr size_t SIZE_SVI = SIZE_UINT64 + SIZE_CTRL_DATA; + + if (error::is_null(task)) { return; } + + const char *statusLoadingSvi = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 3); + + const fslib::Path sviPath{config::get_working_directory() / "svi"}; + const fslib::Directory sviDir{sviPath}; + if (error::fslib(sviDir.is_open())) { return; } + + task->set_status(statusLoadingSvi); + + const int64_t sviCount = sviDir.get_count(); + for (int64_t i = 0; i < sviCount; i++) + { + const fslib::Path target = sviPath / sviDir[i]; + fslib::File sviFile{target, FsOpenMode_Read}; + + const bool goodSvi = sviFile.is_open() && sviFile.get_size() == SIZE_SVI; + if (!goodSvi) { continue; } + + uint64_t applicationID{}; + auto controlData = std::make_unique(); + const bool idRead = sviFile.read(&applicationID, SIZE_UINT64) == SIZE_UINT64; + const bool dataRead = sviFile.read(controlData.get(), SIZE_CTRL_DATA) == SIZE_CTRL_DATA; + if (!idRead || !dataRead) { continue; } + + const bool exists = DataContext::title_is_loaded(applicationID); + if (exists) { continue; } + + std::scoped_lock multiGuard{m_iconQueueMutex, m_titleMutex}; + data::TitleInfo newTitle{applicationID, controlData}; + m_titleInfo.emplace(applicationID, std::move(newTitle)); + m_iconQueue.push_back(&m_titleInfo.at(applicationID)); + } +} + +bool data::DataContext::read_cache(sys::Task *task) +{ + if (error::is_null(task)) { return false; } + + m_cacheIsValid = false; + fs::MiniUnzip cacheZip{PATH_CACHE_FILE}; + if (!cacheZip.is_open()) { return false; } + + const char *statusLoadingCache = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 4); + task->set_status(statusLoadingCache); + + do { + auto controlData = std::make_unique(); + const bool dataRead = cacheZip.read(controlData.get(), SIZE_CTRL_DATA) == SIZE_CTRL_DATA; + if (!dataRead) { continue; } + + std::string_view filename{cacheZip.get_filename()}; + const size_t nameBegin = filename.find_first_not_of('/'); + if (nameBegin == filename.npos) { continue; } + + filename = filename.substr(nameBegin); + const uint64_t applicationID = std::strtoull(filename.data(), nullptr, 16); + + std::scoped_lock multiGuard{m_iconQueueMutex, m_titleMutex}; + + data::TitleInfo newTitle{applicationID, controlData}; + m_titleInfo.emplace(applicationID, std::move(newTitle)); + m_iconQueue.push_back(&m_titleInfo.at(applicationID)); + } while (cacheZip.next_file()); + m_cacheIsValid = true; + return true; +} + +bool data::DataContext::write_cache(sys::Task *task) +{ + if (error::is_null(task)) { return false; } + + const fslib::Path cachePath{PATH_CACHE_FILE}; + const bool cacheExists = fslib::file_exists(cachePath); + if (cacheExists && m_cacheIsValid) { return true; } + + fs::MiniZip cacheZip{cachePath}; + if (!cacheZip.is_open()) { return false; } + + const char *statusWritingCache = "Writing cache to SD..."; + task->set_status(statusWritingCache); + + std::lock_guard titleGuard{m_titleMutex}; + for (auto &[applicationID, titleInfo] : m_titleInfo) + { + if (!titleInfo.has_control_data()) { continue; } + + const NsApplicationControlData *controlData = titleInfo.get_control_data(); + const std::string cacheName = stringutil::get_formatted_string("//%016llX", applicationID); + const bool opened = cacheZip.open_new_file(cacheName); + if (!opened) { continue; } + + const bool controlWritten = cacheZip.write(controlData, SIZE_CTRL_DATA); + if (!controlWritten) { logger::log("Error writing control data to zip!"); } + cacheZip.close_current_file(); + } + m_cacheIsValid = true; + + return true; +} + +void data::DataContext::process_icon_queue() +{ + std::scoped_lock multiGuard{m_iconQueueMutex, m_userMutex, m_titleMutex}; + for (data::DataCommon *common : m_iconQueue) { common->load_icon(); } + m_iconQueue.clear(); +} diff --git a/source/data/TitleInfo.cpp b/source/data/TitleInfo.cpp index 1bf213f..ac72539 100644 --- a/source/data/TitleInfo.cpp +++ b/source/data/TitleInfo.cpp @@ -39,9 +39,7 @@ data::TitleInfo::TitleInfo(uint64_t applicationID) } else if (!getError && !entryError) { - const size_t iconSize = controlSize - SIZE_NACP; - m_hasData = true; - + m_hasData = true; TitleInfo::get_create_path_safe_title(); } } diff --git a/source/data/data.cpp b/source/data/data.cpp index fc02420..905d41f 100644 --- a/source/data/data.cpp +++ b/source/data/data.cpp @@ -1,299 +1,52 @@ #include "data/data.hpp" #include "appstates/DataLoadingState.hpp" -#include "appstates/FadeState.hpp" -#include "colors.hpp" -#include "config.hpp" +#include "data/DataContext.hpp" #include "error.hpp" -#include "fs/fs.hpp" -#include "logger.hpp" #include "strings.hpp" -#include "stringutil.hpp" -#include "sys/sys.hpp" -#include -#include -#include #include -#include -#include -#include namespace { - /// @brief This contains the user accounts on the system. - std::vector s_users{}; - - // Map of Title info paired with its title/application - std::unordered_map s_titleinfo{}; - - /// @brief This vector holds pointers to everything that needs icons and processes it all on the main thread at the end. - std::vector s_iconQueue; - - /// @brief Path used for cacheing title information since NS got slow on 20.0+ - constexpr std::string_view PATH_CACHE_PATH = "sdmc:/config/JKSV/cache.zip"; - - // These are the ID's used for system type users. - constexpr AccountUid ID_SYSTEM_USER = {FsSaveDataType_System}; - constexpr AccountUid ID_BCAT_USER = {FsSaveDataType_Bcat}; - constexpr AccountUid ID_DEVICE_USER = {FsSaveDataType_Device}; - constexpr AccountUid ID_CACHE_USER = {FsSaveDataType_Cache}; - - /// @brief This is for loading the cache. - constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData); - constexpr size_t SIZE_SAVE_INFO = sizeof(FsSaveDataInfo); + data::DataContext s_context{}; } // namespace /// @brief The main routine for the task to load data. static void data_initialize_task(sys::Task *task, bool clearCache); -/// @brief Loads users from the system and creates the system users. -static bool load_create_user_accounts(sys::Task *task); - -/// @brief Loads the application records from NS. -static void load_application_records(sys::Task *task); - -/// @brief Imports external SVI(Control Data) files. -static void import_svi_files(sys::Task *task); - -/// @brief Attempts to read the cache file from the SD. -/// @return True on success. False on failure. -static bool read_cache_file(sys::Task *task); - -/// @brief Creates the cache file on the SD card. -static void create_cache_file(); - -/// @brief Processes the queues. -static void process_queue(); - void data::launch_initialization(bool clearCache, std::function onDestruction) { - auto loadingState = DataLoadingState::create(data_initialize_task, clearCache); - loadingState->add_destruct_function(process_queue); - loadingState->add_destruct_function(create_cache_file); - loadingState->add_destruct_function(onDestruction); + auto loadingState = DataLoadingState::create(s_context, onDestruction, data_initialize_task, clearCache); StateManager::push_state(loadingState); } -void data::get_users(data::UserList &userList) -{ - for (data::User &user : s_users) { userList.push_back(&user); } -} +void data::get_users(data::UserList &userList) { s_context.get_users(userList); } -data::TitleInfo *data::get_title_info_by_id(uint64_t applicationID) -{ - auto findTitle = s_titleinfo.find(applicationID); - if (findTitle == s_titleinfo.end()) { return nullptr; } - return &findTitle->second; -} +data::TitleInfo *data::get_title_info_by_id(uint64_t applicationID) { return s_context.get_title_by_id(applicationID); } -void data::load_title_to_map(uint64_t applicationID) { s_titleinfo.emplace(applicationID, applicationID); } +void data::load_title_to_map(uint64_t applicationID) { s_context.load_title(applicationID); } -bool data::title_exists_in_map(uint64_t applicationID) { return s_titleinfo.find(applicationID) != s_titleinfo.end(); } +bool data::title_exists_in_map(uint64_t applicationID) { return s_context.title_is_loaded(applicationID); } -void data::get_title_info_list(data::TitleInfoList &listOut) -{ - for (auto &[applicationID, titleInfo] : s_titleinfo) { listOut.push_back(&titleInfo); } -} +void data::get_title_info_list(data::TitleInfoList &listOut) { s_context.get_title_info_list(listOut); } void data::get_title_info_by_type(FsSaveDataType saveType, data::TitleInfoList &listOut) { - for (auto &[applicationID, titleInfo] : s_titleinfo) - { - if (titleInfo.has_save_data_type(saveType)) { listOut.push_back(&titleInfo); } - } + s_context.get_title_info_list_by_type(saveType, listOut); } static void data_initialize_task(sys::Task *task, bool clearCache) { if (error::is_null(task)) { return; } - const char *statusLoadingUserInfo = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 5); - const char *statusFinalizing = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 6); + const char *statusFinalizing = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 6); - const fslib::Path &cachePath{PATH_CACHE_PATH}; - bool cacheExists = fslib::file_exists(cachePath); - if (clearCache && cacheExists) - { - error::fslib(fslib::delete_file(cachePath)); - cacheExists = false; - } + s_context.read_cache(task); + s_context.load_application_records(task); + s_context.load_create_users(task); + s_context.load_user_save_info(task); + s_context.write_cache(task); - if (s_users.empty()) { load_create_user_accounts(task); } - - const bool cacheRead = cacheExists && read_cache_file(task); - if (!cacheRead) - { - s_titleinfo.clear(); - load_application_records(task); - import_svi_files(task); - } - - { - for (data::User &user : s_users) - { - { - const char *nickname = user.get_nickname(); - const std::string status = stringutil::get_formatted_string(statusLoadingUserInfo, nickname); - task->set_status(status); - } - user.load_user_data(); - } - } - - task->set_status(statusFinalizing); - for (data::User &user : s_users) { s_iconQueue.push_back(&user); } - for (auto &[applicationID, titleInfo] : s_titleinfo) { s_iconQueue.push_back(&titleInfo); } + task->set_status(statusFinalizing); // This is here so at least they know something is happening instead of a freeze. task->complete(); } - -static bool load_create_user_accounts(sys::Task *task) -{ - static constexpr const char *systemSafe = "System"; - static constexpr const char *bcatSafe = "BCAT"; - static constexpr const char *deviceSafe = "Device"; - static constexpr const char *cacheSafe = "Cache"; - - if (error::is_null(task)) { return false; } - - const char *statusLoading = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 0); - const char *statusCreating = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 1); - const char *systemName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 0); - const char *bcatName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 2); - const char *deviceName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 3); - const char *cacheName = strings::get_by_name(strings::names::SAVE_DATA_TYPES, 5); - - task->set_status(statusLoading); - - int total{}; - AccountUid accounts[8]{}; - const bool accountError = error::libnx(accountListAllUsers(accounts, 8, &total)); - const bool noAccounts = total <= 0; - if (accountError || noAccounts) { return false; } - - for (int i = 0; i < total; i++) { s_users.emplace_back(accounts[i], FsSaveDataType_Account); } - - task->set_status(statusCreating); - s_users.emplace_back(ID_DEVICE_USER, deviceName, deviceSafe, FsSaveDataType_Device); - s_users.emplace_back(ID_BCAT_USER, bcatName, bcatSafe, FsSaveDataType_Bcat); - s_users.emplace_back(ID_CACHE_USER, cacheName, cacheSafe, FsSaveDataType_Cache); - s_users.emplace_back(ID_SYSTEM_USER, systemName, systemSafe, FsSaveDataType_System); - - return true; -} - -static void load_application_records(sys::Task *task) -{ - if (error::is_null(task)) { return; } - - int offset{}; - int count{}; - NsApplicationRecord record{}; - const char *statusLoadingRecords = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 2); - - bool listError{}; - do { - listError = error::libnx(nsListApplicationRecord(&record, 1, offset++, &count)) || count <= 0; - if (listError) { break; } - - { - const std::string status = stringutil::get_formatted_string(statusLoadingRecords, record.application_id); - task->set_status(status); - } - - s_titleinfo.emplace(record.application_id, record.application_id); - } while (!listError); -} - -static void import_svi_files(sys::Task *task) -{ - static constexpr size_t SIZE_UINT64 = sizeof(uint64_t); - static constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData); - static constexpr size_t SIZE_SVI = SIZE_UINT64 + SIZE_CTRL_DATA; - - if (error::is_null(task)) { return; } - - const fslib::Path sviPath = config::get_working_directory() / "svi"; - const fslib::Directory sviDir{sviPath}; - if (error::fslib(sviDir.is_open())) { return; } - - const char *statusLoadingSvi = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 3); - task->set_status(statusLoadingSvi); - - const int64_t sviCount = sviDir.get_count(); - for (int64_t i = 0; i < sviCount; i++) - { - const fslib::Path target = sviPath / sviDir[i]; - fslib::File sviFile{target, FsOpenMode_Read}; - - const bool goodFile = sviFile.is_open() && sviFile.get_size() == SIZE_SVI; - if (!goodFile) { continue; } - - uint64_t applicationID{}; - auto controlData = std::make_unique(); - const bool idReadGood = sviFile.read(&applicationID, SIZE_UINT64) == SIZE_UINT64; - const bool dataReadGood = sviFile.read(controlData.get(), SIZE_CTRL_DATA) == SIZE_CTRL_DATA; - if (!idReadGood || !dataReadGood) { continue; } - - data::TitleInfo newTitle{applicationID, controlData}; - s_titleinfo.emplace(applicationID, std::move(newTitle)); - } -} - -static bool read_cache_file(sys::Task *task) -{ - if (error::is_null(task)) { return false; } - - fs::MiniUnzip cacheZip{PATH_CACHE_PATH}; - if (!cacheZip.is_open()) { return false; } - - const char *statusLoadingCache = strings::get_by_name(strings::names::DATA_LOADING_STATUS, 4); - task->set_status(statusLoadingCache); - - do { - auto controlBuffer = std::make_unique(); - const bool read = cacheZip.read(controlBuffer.get(), SIZE_CTRL_DATA) == SIZE_CTRL_DATA; - if (!read) { continue; } - - std::string_view filename{cacheZip.get_filename()}; - const size_t nameBegin = filename.find_first_not_of('/'); - if (nameBegin == filename.npos) { continue; } // This is required in order to get the application ID. - - filename = filename.substr(nameBegin); - const uint64_t applicationID = std::strtoull(filename.data(), nullptr, 16); - - data::TitleInfo newTitle{applicationID, controlBuffer}; - s_titleinfo.emplace(applicationID, std::move(newTitle)); - } while (cacheZip.next_file()); - - return true; -} - -static void create_cache_file() -{ - const fslib::Path cachePath{PATH_CACHE_PATH}; - const bool cacheExists = fslib::file_exists(cachePath); - if (cacheExists) { return; } - - fs::MiniZip cacheZip{PATH_CACHE_PATH}; - if (!cacheZip.is_open()) { return; } - - for (auto &[applicationID, titleInfo] : s_titleinfo) - { - if (!titleInfo.has_control_data()) { continue; } - - const NsApplicationControlData *controlData = titleInfo.get_control_data(); - const std::string cacheName = stringutil::get_formatted_string("//%016llX", applicationID); - const bool opened = cacheZip.open_new_file(cacheName); - if (!opened) { continue; } - - const bool controlWritten = cacheZip.write(controlData, SIZE_CTRL_DATA); - if (!controlWritten) { logger::log("Error writing control data to zip!"); } - cacheZip.close_current_file(); - } -} - -static void process_queue() -{ - for (data::DataCommon *dataCommon : s_iconQueue) { dataCommon->load_icon(); } - s_iconQueue.clear(); -} diff --git a/source/remote/GoogleDrive.cpp b/source/remote/GoogleDrive.cpp index 05c3a25..93a8a45 100644 --- a/source/remote/GoogleDrive.cpp +++ b/source/remote/GoogleDrive.cpp @@ -306,7 +306,6 @@ bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::P remote::URL url{URL_DRIVE_FILE_API}; url.append_path(file->get_id()).append_parameter("alt", "media"); - logger::log("%s", url.get()); curl::DownloadStruct download{.dest = &destFile, .task = task, .fileSize = itemSize}; curl::prepare_get(m_curl); @@ -328,8 +327,7 @@ bool remote::GoogleDrive::delete_item(const remote::Item *item) // Iterator is needed to remove it from the list. const std::string_view itemId = item->get_id(); - auto findItem = - std::find_if(m_list.begin(), m_list.end(), [&](const Item &listItem) { return itemId == listItem.get_id(); }); + auto findItem = Storage::find_item_by_id(itemId); if (findItem == m_list.end()) { logger::log("Error deleting item: Item not found in list!"); diff --git a/source/remote/Storage.cpp b/source/remote/Storage.cpp index ce336d0..948396d 100644 --- a/source/remote/Storage.cpp +++ b/source/remote/Storage.cpp @@ -80,6 +80,18 @@ remote::Storage::List::iterator remote::Storage::find_directory_by_name(std::str return std::find_if(m_list.begin(), m_list.end(), is_match); } +remote::Storage::List::iterator remote::Storage::find_directory_by_id(std::string_view id) +{ + auto is_match = [&](const Item &item) + { + const bool isDir = item.is_directory(); + const bool idMatch = item.get_id() == id; + return isDir && idMatch; + }; + + return std::find_if(m_list.begin(), m_list.end(), is_match); +} + remote::Storage::List::iterator remote::Storage::find_file_by_name(std::string_view name) { auto is_match = [&](const Item &item) @@ -94,6 +106,31 @@ remote::Storage::List::iterator remote::Storage::find_file_by_name(std::string_v return std::find_if(m_list.begin(), m_list.end(), is_match); } +remote::Storage::List::iterator remote::Storage::find_file_by_id(std::string_view id) +{ + auto is_match = [&](const Item &item) + { + const bool isFile = !item.is_directory(); + const bool isMatch = item.get_id() == id; + + return isFile && isMatch; + }; + + return std::find_if(m_list.begin(), m_list.end(), is_match); +} + +remote::Storage::List::iterator remote::Storage::find_item_by_id(std::string_view id) +{ + auto is_match = [&](const Item &item) + { + const bool isMatch = item.get_id() == id; + + return isMatch; + }; + + return std::find_if(m_list.begin(), m_list.end(), is_match); +} + remote::Storage::List::iterator remote::Storage::find_by_parent_id(remote::Storage::List::iterator start, std::string_view parentID) {