Add DataContext for data cleanup, add cache invalidation at boot.

This commit is contained in:
J-D-K
2025-08-15 15:43:03 -04:00
parent 74892e169f
commit ba6ad3f20d
12 changed files with 452 additions and 297 deletions

View File

@@ -1,6 +1,7 @@
#pragma once
#include "StateManager.hpp"
#include "appstates/BaseTask.hpp"
#include "data/DataContext.hpp"
#include "sdl.hpp"
#include <functional>
@@ -13,23 +14,34 @@ class DataLoadingState final : public BaseTask
using DestructFunction = std::function<void()>;
template <typename... Args>
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<sys::Task>(function, std::forward<Args>(args)...);
}
template <typename... Args>
static std::shared_ptr<DataLoadingState> create(void (*function)(sys::Task *, Args...), Args... args)
static std::shared_ptr<DataLoadingState> create(data::DataContext &context,
DestructFunction destructFunction,
void (*function)(sys::Task *, Args...),
Args... args)
{
return std::make_shared<DataLoadingState>(function, std::forward<Args>(args)...);
return std::make_shared<DataLoadingState>(context, destructFunction, function, std::forward<Args>(args)...);
}
template <typename... Args>
static std::shared_ptr<DataLoadingState> create_and_push(void (*function)(sys::Task *, Args...), Args... args)
static std::shared_ptr<DataLoadingState> create_and_push(data::DataContext &context,
DestructFunction destructFunction,
void (*function)(sys::Task *, Args...),
Args... args)
{
auto newState = DataLoadingState::create(function, std::forward<Args>(args)...);
auto newState = DataLoadingState::create(context, destructFunction, function, std::forward<Args>(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<DestructFunction> m_destructFunctions{};
DestructFunction m_destructFunction{};
/// @brief Icon displayed in the center of the screen.
static inline sdl::SharedTexture sm_jksvIcon{};

View File

@@ -0,0 +1,80 @@
#pragma once
#include "data/DataCommon.hpp"
#include "data/TitleInfo.hpp"
#include "data/User.hpp"
#include "sys/Task.hpp"
#include <mutex>
#include <unordered_map>
#include <vector>
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<data::User> m_users{};
/// @brief Map of titles paired with their application ID.
std::unordered_map<uint64_t, data::TitleInfo> m_titleInfo{};
/// @brief Queue of the above to process the icons.
std::vector<data::DataCommon *> 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{};
};
}

View File

@@ -5,9 +5,15 @@
#include <cstdint>
#include <memory>
#include <switch.h>
#include <vector>
namespace data
{
class TitleInfo;
/// @brief Vector of pointers to titleinfo instances.
using TitleInfoList = std::vector<data::TitleInfo *>;
/// @brief Class that holds data related to titles loaded from the system.
class TitleInfo final : public data::DataCommon
{

View File

@@ -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<uint64_t, std::pair<FsSaveDataInfo, PdmPlayStatistics>>;
@@ -17,6 +19,9 @@ namespace data
/// @brief Type definition for the user save info/play stats vector.
using UserSaveInfoList = std::vector<UserDataEntry>;
/// @brief A vector of pointers to User instances.
using UserList = std::vector<data::User *>;
/// @brief Class that stores data for the user.
class User final : public data::DataCommon
{

View File

@@ -9,10 +9,6 @@
namespace data
{
/// @brief Declaration for user list/user pointer vector.
using UserList = std::vector<data::User *>;
using TitleInfoList = std::vector<data::TitleInfo *>;
/// @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.

View File

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

View File

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

264
source/data/DataContext.cpp Normal file
View File

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

View File

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

View File

@@ -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 <algorithm>
#include <array>
#include <string>
#include <switch.h>
#include <thread>
#include <unordered_map>
#include <vector>
namespace
{
/// @brief This contains the user accounts on the system.
std::vector<data::User> s_users{};
// Map of Title info paired with its title/application
std::unordered_map<uint64_t, data::TitleInfo> 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<data::DataCommon *> 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<void()> 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<NsApplicationControlData>();
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<NsApplicationControlData>();
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();
}

View File

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

View File

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