mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-08-28 13:34:05 -05:00
Add DataContext for data cleanup, add cache invalidation at boot.
This commit is contained in:
@@ -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{};
|
||||
|
||||
80
include/data/DataContext.hpp
Normal file
80
include/data/DataContext.hpp
Normal 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{};
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
264
source/data/DataContext.cpp
Normal 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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user