diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d47e380..cc1b956 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(YYCCommonplace PRIVATE # Sources COMHelper.cpp + ConfigManager.cpp ConsoleHelper.cpp DialogHelper.cpp EncodingHelper.cpp @@ -21,6 +22,7 @@ FILES # Headers # Common headers COMHelper.hpp + ConfigManager.hpp ConsoleHelper.hpp DialogHelper.hpp EncodingHelper.hpp diff --git a/src/ConfigManager.cpp b/src/ConfigManager.cpp new file mode 100644 index 0000000..cc6cfc8 --- /dev/null +++ b/src/ConfigManager.cpp @@ -0,0 +1,148 @@ +#include "ConfigManager.hpp" + +#include "EncodingHelper.hpp" +#include "IOHelper.hpp" + +namespace YYCC::ConfigManager { + +#pragma region Core Manager + + CoreManager::CoreManager( + const yycc_char8_t* cfg_file_path, + uint64_t version_identifier, + std::initializer_list settings) : + m_CfgFilePath(), m_VersionIdentifier(version_identifier), m_Settings() { + // assign cfg path + if (cfg_file_path != nullptr) + m_CfgFilePath = cfg_file_path; + // assign settings + for (auto* setting : settings) { + m_Settings.try_emplace(setting->GetName(), setting); + } + } + + bool CoreManager::Load() { + // reset all settings first + Reset(); + + // get file handle + auto fs = this->GetFileHandle(YYCC_U8("rb")); + if (fs.get() == nullptr) { + // if we fail to get, it means that we do not have corresponding cfg file. + // all settings should be reset to default value. + return true; + } + + // fetch version info + uint64_t version_info; + if (std::fread(&version_info, sizeof(version_info), 1u, fs.get()) != sizeof(version_info)) + return false; + // check version + // if read version is greater than we expected, + // it means that this cfg file is created by the program higer than this. + // we should not read anything from it. + // however, for compaitibility reason, we allow read old cfg data. + if (version_info > m_VersionIdentifier) + return true; + + // fetch setting item from file + yycc_u8string name_cache; + while (true) { + // try fetch setting name + // fetch name length + size_t name_length; + if (std::fread(&name_length, sizeof(name_length), 1u, fs.get()) != sizeof(name_length)) { + // we also check whether reach EOF at there. + if (std::feof(fs.get())) break; + else return false; + } + // fetch name body + name_cache.resize(name_length); + if (std::fread(name_cache.data(), name_length, 1u, fs.get()) != name_length) + return false; + + // get setting data length + size_t data_length; + if (std::fread(&data_length, sizeof(data_length), 1u, fs.get()) != sizeof(data_length)) + return false; + + // get matched setting first + const auto& found = m_Settings.find(name_cache); + if (found != m_Settings.end()) { + // found. read data for it + found->second->ResizeData(data_length); + if (std::fread(found->second->GetDataPtr(), data_length, 1u, fs.get()) != data_length) + return false; + // call user defined load function + // if fail to parse, reset to default value + if (!found->second->UserLoad()) + found->second->UserReset(); + } else { + // fail to find. skip this unknown setting + if (fseek(fs.get(), static_cast(data_length), SEEK_CUR) != 0) + return false; + } + } + + return true; + } + + bool CoreManager::Save() { + // get file handle + auto fs = this->GetFileHandle(YYCC_U8("wb")); + // if we fail to get, return false. + if (fs == nullptr) return false; + + // write config data + uint64_t version_info = m_VersionIdentifier; + if (std::fwrite(&version_info, sizeof(version_info), 1u, fs.get()) != sizeof(version_info)) + return false; + + // iterate all data for writing + for (const auto& pair : m_Settings) { + // do user defined save + // if failed, skip this setting + if (!pair.second->UserSave()) + continue; + + // write setting name + // write name length + size_t name_length = pair.first.size(); + if (std::fwrite(&name_length, sizeof(name_length), 1u, fs.get()) != sizeof(name_length)) + return false; + // write name body + if (std::fwrite(pair.first.c_str(), name_length, 1u, fs.get()) != name_length) + return false; + + // write setting daat + // write data length + size_t data_length = pair.second->GetDataSize(); + if (std::fwrite(&data_length, sizeof(data_length), 1u, fs.get()) != sizeof(data_length)) + return false; + // write data body + if (std::fwrite(pair.second->GetDataPtr(), data_length, 1u, fs.get()) != data_length) + return false; + } + + // all settings done, return true + return true; + } + + void CoreManager::Reset() { + for (const auto& pair : m_Settings) { + pair.second->UserReset(); + } + } + + CoreManager::FileHandleGuard_t CoreManager::GetFileHandle(const yycc_char8_t* mode) const { + return CoreManager::FileHandleGuard_t( + IOHelper::UTF8FOpen(this->m_CfgFilePath.c_str(), mode), + [](FILE* fs) -> void { + if (fs != nullptr) std::fclose(fs); + } + ); + } + +#pragma endregion + +} diff --git a/src/ConfigManager.hpp b/src/ConfigManager.hpp new file mode 100644 index 0000000..c57fd17 --- /dev/null +++ b/src/ConfigManager.hpp @@ -0,0 +1,200 @@ +#pragma once +#include "YYCCInternal.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace YYCC::ConfigManager { + + template + struct Constrain { + using CheckFct_t = std::function; + //using CorrectFct_t = std::function<_Ty(const _Ty&)>; + CheckFct_t m_CheckFct; + //CorrectFct_t m_CorrectFct; + + bool IsValid() const { + return m_CheckFct != nullptr/* && m_CorrectFct != nullptr*/; + } + }; + + namespace ConstrainPresets { + + template, int> = 0> + Constrain<_Ty> GetNumberRangeConstrain(_Ty min_value, _Ty max_value) { + if (min_value > max_value) + throw std::invalid_argument("invalid min max value for NumberRangeConstrain"); + return Constrain<_Ty> { + [min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); } + /*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/ + }; + } + + } + + class AbstractSetting { + friend class CoreManager; + public: + AbstractSetting(const yycc_char8_t* name) : m_Name(), m_RawData() { + if (name != nullptr) m_Name = name; + } + virtual ~AbstractSetting() {} + + // Name interface + public: + const yycc_u8string& GetName() const { return m_Name; } + private: + yycc_u8string m_Name; + + // User Implementations + protected: + virtual bool UserLoad() = 0; + virtual bool UserSave() = 0; + virtual void UserReset() = 0; + + // Buffer related functions + protected: + void ResizeData(size_t new_size) { m_RawData.resize(new_size); } + const void* GetDataPtr() const { return m_RawData.data(); } + void* GetDataPtr() { return m_RawData.data(); } + size_t GetDataSize() const { return m_RawData.size(); } + private: + std::vector m_RawData; + }; + + class CoreManager { + public: + CoreManager( + const yycc_char8_t* cfg_file_path, + uint64_t version_identifier, + std::initializer_list settings); + ~CoreManager() {} + + // Core functions + public: + bool Load(); + bool Save(); + void Reset(); + + private: + using FileHandleGuard_t = std::unique_ptr>; + FileHandleGuard_t GetFileHandle(const yycc_char8_t* mode) const; + + yycc_u8string m_CfgFilePath; + uint64_t m_VersionIdentifier; + std::map m_Settings; + }; + +#pragma region Setting Presets + + template, int> = 0> + class NumberSetting : public AbstractSetting { + public: + NumberSetting(const yycc_char8_t* name, _Ty default_value, Constrain<_Ty> constrain = Constrain<_Ty> {}) : + AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constrain(constrain) {} + virtual ~NumberSetting() {} + + _Ty Get() const { return m_Data; } + bool Set(_Ty new_data) { + // validate data + if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data)) + return false; + // assign data + m_Data = new_data; + return true; + } + + protected: + virtual bool UserLoad() override { + // read data + if (sizeof(m_Data) != GetDataSize()) + return false; + m_Data = *reinterpret_cast(GetDataPtr()); + // check data + if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data)) + return false; + return true; + } + virtual bool UserSave() override { + // write data + ResizeData(sizeof(m_Data)); + *reinterpret_cast<_Ty*>(GetDataPtr()) = m_Data; + return true; + } + virtual void UserReset() override { + m_Data = m_DefaultData; + } + + _Ty m_Data, m_DefaultData; + Constrain<_Ty> m_Constrain; + }; + + class StringSetting : public AbstractSetting { + public: + StringSetting(const yycc_char8_t* name, const yycc_char8_t* default_value, Constrain constrain = Constrain {}) : + AbstractSetting(name), m_Data(), m_DefaultData(), m_Constrain(constrain) { + if (default_value != nullptr) { + m_Data = default_value; + m_DefaultData = default_value; + } + } + virtual ~StringSetting() {} + + const yycc_u8string& Get() const { return m_Data; } + bool Set(const yycc_char8_t* new_data) { + // check data validation + if (new_data == nullptr) + return false; + if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data)) + return false; + // assign data + m_Data = new_data; + return true; + } + + protected: + virtual bool UserLoad() override { + // read string length + size_t string_length; + if (GetDataSize() < sizeof(string_length)) + return false; + string_length = *reinterpret_cast(GetDataPtr()); + // read string body + if (GetDataSize() != sizeof(string_length) + string_length) + return false; + m_Data.assign( + reinterpret_cast(static_cast(GetDataPtr()) + sizeof(string_length)), + string_length + ); + // check data + if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data)) + return false; + return true; + } + virtual bool UserSave() override { + // allocate result buffer + size_t string_length = m_Data.size(); + ResizeData(sizeof(string_length) + string_length); + // get pointer + uint8_t* ptr = static_cast(GetDataPtr()); + // assign string length + *reinterpret_cast(ptr) = string_length; + // assign string body + std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length); + return true; + } + + yycc_u8string m_Data, m_DefaultData; + Constrain m_Constrain; + }; + +#pragma endregion + + +} diff --git a/src/YYCCommonplace.hpp b/src/YYCCommonplace.hpp index 865c191..3a4f5a2 100644 --- a/src/YYCCommonplace.hpp +++ b/src/YYCCommonplace.hpp @@ -12,3 +12,5 @@ #include "WinFctHelper.hpp" #include "FsPathPatch.hpp" #include "ExceptionHelper.hpp" + +#include "ConfigManager.hpp"