From 93857e241a461b05f0aba117de3a677c821c378b Mon Sep 17 00:00:00 2001 From: Charles <37224242+Charlito33@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:38:18 +0200 Subject: [PATCH] Added FileConfig. --- lib/system/FileConfig.cpp | 460 ++++++++++++++++++++++++++++++++++++++ lib/system/FileConfig.hpp | 233 +++++++++++++++++++ 2 files changed, 693 insertions(+) create mode 100644 lib/system/FileConfig.cpp create mode 100644 lib/system/FileConfig.hpp diff --git a/lib/system/FileConfig.cpp b/lib/system/FileConfig.cpp new file mode 100644 index 00000000..e17cdd3c --- /dev/null +++ b/lib/system/FileConfig.cpp @@ -0,0 +1,460 @@ +// +// Created by Charles on 08/09/2024. +// + +#include "FileConfig.hpp" + +#include +#include +#include + +namespace libsystem { + FileConfig::FileConfig(const storage::Path &path) { + m_path = path; + + // Init fields before parsing + m_version = 0; + m_mainNode = std::make_shared("main"); + m_currentNode = m_mainNode; + + m_fileStream = nullptr; + m_fileStreamSize = 0; + m_fileStreamCursor = 0; + + if (m_path.exists()) { + // If file exist, read it and parse it + // Else, do nothing + + // Open file in binary mode (FileStream is always binary) + openFileStream(m_path, storage::READ); + + if (!checkFormat()) { + throw exceptions::RuntimeError("File is the wrong format."); + } + + parse(); + + closeFileStream(); + } + } + + FileConfig::~FileConfig() = default; + + void FileConfig::write() { + openFileStream(m_path, storage::WRITE); + + m_fileStream->write("BFC"); + m_fileStream->write(static_cast(m_version)); + + m_mainNode->write(m_fileStream); + + closeFileStream(); + } + + FileConfig::file_config_types_t FileConfig::getRaw(const std::string &key) const { + const std::shared_ptr namespaceNode = getNamespaceNodeFromNamespaceKey(key); + + // Get only the last part of the key + const std::string keySuffix = key.substr(key.find_last_of('.') + 1); + + const std::shared_ptr value = namespaceNode->getValue(keySuffix); + + return value->getValue(); + } + + void FileConfig::setRaw(const std::string &key, const file_config_types_t& value) const { + const std::shared_ptr namespaceNode = getNamespaceNodeFromNamespaceKey(key, true); + + // Get only the last part of the key + const std::string keySuffix = key.substr(key.find_last_of('.') + 1); + + const auto valueNode = std::make_shared(keySuffix, value); + + namespaceNode->addValueNode(valueNode); + } + + std::string FileConfig::toString() const { + return "FileConfig{path=" + m_path.str() + ", version=" + std::to_string(m_version) + "}"; + } + + bool FileConfig::checkFormat() { + return readString(3) == "BFC"; + } + + void FileConfig::parse() { + m_version = readUint8(); + + OpCode opCode; + + while (hasNext() && (opCode = readOpCode()) != NULL_CODE) { + switch (opCode) { + case PROPERTY: + newProperty(); + break; + case BEGIN_NAMESPACE: + pushNamespace(); + break; + case END_NAMESPACE: + popNamespace(); + break; + default:; + } + } + } + + void FileConfig::newProperty() { + const Type type = readType(); + const std::string key = readString(); + + file_config_types_t value; + + switch (type) { + case UINT8: + value = readUint8(); + break; + case UINT16: + value = readUint16(); + break; + case UINT32: + value = readUint32(); + break; + case UINT64: + value = readUint64(); + break; + case STRING: + value = readString(); + break; + default:; + } + + const auto valueNode = std::make_shared(key, value); + m_currentNode->addValueNode(valueNode); + } + + void FileConfig::pushNamespace() { + const std::string name = readString(); + + if (m_currentNode->hasNamespace(name)) { + // If namespace already exists + // Get existing namespace + const std::shared_ptr namespaceNode = m_currentNode->getNamespace(name); + m_currentNode = namespaceNode; + return; + } + + // If namespace doesn't exist + // Create new one + const auto namespaceNode = std::make_shared(name); + m_currentNode->addNamespaceNode(namespaceNode); + m_currentNode = namespaceNode; + } + + void FileConfig::popNamespace() { + m_currentNode = m_currentNode->getParent(); + } + + std::string FileConfig::getNamespacedKey(const std::string &key) const { + if (m_currentNode->getParent() == nullptr) { + return key; + } + + return m_currentNode->getPath() + "." + key; + } + + // ReSharper disable once CppDFAUnreachableFunctionCall + std::vector FileConfig::getSplicedNamespacedKey(const std::string &namespacedKey) { + std::vector output; + + // Code from : + // https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c + + size_t next = 0; + size_t last = 0; + while ((next = namespacedKey.find('.', last)) != std::string::npos) { + output.push_back(namespacedKey.substr(last, next - last)); + last = next + 1; + } + + output.push_back(namespacedKey.substr(last)); + + return output; + } + + // ReSharper disable once CppDFAUnreachableFunctionCall + std::shared_ptr FileConfig::getNamespaceNodeFromNamespaceKey( + const std::string &namespacedKey, const bool createNewNamespaces) const { + + std::vector keySplit = getSplicedNamespacedKey(namespacedKey); + + std::shared_ptr namespaceNode = m_mainNode; + while (keySplit.size() > 1) { + if (const std::string namespaceName = keySplit.front(); namespaceNode->hasNamespace(namespaceName)) { + namespaceNode = namespaceNode->getNamespace(namespaceName); + } else { + if (createNewNamespaces) { + auto newNamespace = std::make_shared(namespaceName); + namespaceNode->addNamespaceNode(newNamespace); + namespaceNode = newNamespace; + } else { + throw exceptions::InvalidArgument("Unknown key: " + namespacedKey + "."); + } + } + + keySplit.erase(keySplit.begin()); + } + + return namespaceNode; + } + + void FileConfig::openFileStream(const storage::Path &path, storage::Mode mode) { + m_fileStream = std::make_shared(path.str(), mode); + m_fileStreamSize = m_fileStream->size(); + m_fileStreamCursor = 0; + + // Reset cursor + m_fileStream->close(); + m_fileStream->open(path.str(), mode); + } + + void FileConfig::closeFileStream() { + m_fileStream->close(); + m_fileStream.reset(); + } + + bool FileConfig::hasNext() const { + return m_fileStreamCursor < m_fileStreamSize; + } + + uint8_t FileConfig::read() { + m_fileStreamCursor++; + + return m_fileStream->readchar(); + } + + uint8_t FileConfig::readUint8() { + return read(); + } + + uint16_t FileConfig::readUint16() { + return read() << 8 | read(); + } + + uint32_t FileConfig::readUint32() { + return read() << 24 | read() << 16 | read() << 8 | read(); + } + + uint64_t FileConfig::readUint64() { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-count-overflow" + return read() << 56 | read() << 48 | read() << 40 | read() << 32 | + read() << 24 | read() << 16 | read() << 8 | read(); +#pragma GCC diagnostic pop + } + + std::string FileConfig::readString(const size_t length) { + std::string output; + + if (length == 0) { + char c; + while (hasNext() && (c = static_cast(read())) != 0x00) { + output += c; + } + } else { + for (size_t i = 0; i < length; i++) { + output += m_fileStream->readchar(); + } + } + + return output; + } + + FileConfig::OpCode FileConfig::readOpCode() { + return static_cast(read()); + } + + FileConfig::Type FileConfig::readType() { + return static_cast(read()); + } + + FileConfig::ValueNode::ValueNode(const std::string &key, const file_config_types_t &value) { + m_key = key; + m_value = value; + } + + std::shared_ptr FileConfig::ValueNode::getParent() const { + return std::static_pointer_cast(m_parent); + } + + std::string FileConfig::ValueNode::getKey() { + return m_key; + } + + FileConfig::Type FileConfig::ValueNode::getType() const { + return static_cast(m_value.index()); // Risky + } + + FileConfig::file_config_types_t FileConfig::ValueNode::getValue() { + return m_value; + } + + std::string FileConfig::ValueNode::getPath() { + if (getParent() == nullptr) { + return m_key; + } + + return getParent()->getPath() + "." + m_key; + } + + void FileConfig::ValueNode::write(const std::shared_ptr &fileStream) const { + fileStream->write(PROPERTY); + fileStream->write(getType()); + + fileStream->write(m_key); + fileStream->write(0x00); + + switch (getType()) { + case UINT8: + fileStream->write(static_cast(std::get(m_value))); + break; + case UINT16: { + const uint16_t v = std::get(m_value); + + fileStream->write(static_cast(v >> 8)); + fileStream->write(static_cast(v & 0xFF)); + + break; + } + case UINT32: { + const uint32_t v = std::get(m_value); + + fileStream->write(static_cast(v >> 24)); + fileStream->write(static_cast(v >> 16 & 0xFF)); + fileStream->write(static_cast(v >> 8 & 0xFF)); + fileStream->write(static_cast(v & 0xFF)); + + break; + } + case UINT64: { + const uint64_t v = std::get(m_value); + + fileStream->write(static_cast(v >> 56)); + fileStream->write(static_cast(v >> 48 & 0xFF)); + fileStream->write(static_cast(v >> 40 & 0xFF)); + fileStream->write(static_cast(v >> 32 & 0xFF)); + fileStream->write(static_cast(v >> 24 & 0xFF)); + fileStream->write(static_cast(v >> 16 & 0xFF)); + fileStream->write(static_cast(v >> 8 & 0xFF)); + fileStream->write(static_cast(v & 0xFF)); + + break; + } + case STRING: { + const std::string v = std::get(m_value); + + for (const char c : v) { + fileStream->write(c); + } + fileStream->write(0x00); + + break; + } + default: + fileStream->write(69); + } + } + + FileConfig::NamespaceNode::NamespaceNode(const std::string &name) { + + m_name = name; + } + + std::shared_ptr FileConfig::NamespaceNode::getParent() const { + return std::static_pointer_cast(m_parent); + } + + std::string FileConfig::NamespaceNode::getName() { + return m_name; + } + + bool FileConfig::NamespaceNode::hasValue(const std::string &key) { + const auto it = m_values.find(key); + return it != m_values.end(); + } + + std::shared_ptr FileConfig::NamespaceNode::getValue(const std::string &key) { + const auto it = m_values.find(key); + + if (it == m_values.end()) { + throw exceptions::RuntimeError("Cannot find value."); + } + + return it->second; + } + + bool FileConfig::NamespaceNode::hasNamespace(const std::string &name) { + const auto it = m_namespaces.find(name); + return it != m_namespaces.end(); + } + + std::shared_ptr FileConfig::NamespaceNode::getNamespace(const std::string &name) { + const auto it = m_namespaces.find(name); + + if (it == m_namespaces.end()) { + throw exceptions::RuntimeError("Cannot find namespace."); + } + + return it->second; + } + + void FileConfig::NamespaceNode::addValueNode(const std::shared_ptr &node) { + node->m_parent = shared_from_this(); + m_values.insert({ + node->getKey(), + node + }); + } + + void FileConfig::NamespaceNode::addNamespaceNode(const std::shared_ptr &node) { + node->m_parent = shared_from_this(); + m_namespaces.insert({ + node->getName(), + node + }); + } + + std::string FileConfig::NamespaceNode::getPath() { + std::string path = m_name; + + std::shared_ptr parent; + while ((parent = getParent()) != nullptr) { + path.insert(0, "."); + path.insert(0, parent->getName()); + } + + return path; + } + + void FileConfig::NamespaceNode::write(const std::shared_ptr& fileStream) const { // NOLINT(*-no-recursion) + // Write begin namespace OpCode, if not top-level namespace + if (getParent() != nullptr) { + fileStream->write(BEGIN_NAMESPACE); + fileStream->write(m_name); + fileStream->write(0x00); + } + + // Write every namespace recursively + for (const auto&[fst, snd] : m_namespaces) { + snd->write(fileStream); + } + + // Write every values + for (const auto&[fst, snd] : m_values) { + snd->write(fileStream); + } + + // Write end namespace OpCode, if not top-level namespace + if (getParent() != nullptr) { + fileStream->write(END_NAMESPACE); + } + } +} // libsystem diff --git a/lib/system/FileConfig.hpp b/lib/system/FileConfig.hpp new file mode 100644 index 00000000..a80b64b4 --- /dev/null +++ b/lib/system/FileConfig.hpp @@ -0,0 +1,233 @@ +// +// Created by Charles on 08/09/2024. +// + +#ifndef FILECONFIG_HPP +#define FILECONFIG_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace libsystem { + /** + * File config is a binary file format (.bfc). + * It is used to store simple data like user preferences (E.g. screen brightness). + */ + class FileConfig { + // Need to match "FileConfig::Type" ! + /** + * Available types for values. + * Logic needs to be implemented before adding any new value. + */ + typedef std::variant file_config_types_t; + + public: + /** + * Create and load a file config. + * @param path The path to load and store the config. + */ + explicit FileConfig(const storage::Path &path); + ~FileConfig(); + + /** + * Write the cached data to disk. + */ + void write(); + + // Can't implement in .cpp + /** + * Get a value from the configuration. + * @tparam T The type of the value to get. + * @param key The key of the value to get. + * @return The value. + */ + template + [[nodiscard]] T get(const std::string &key) const { + return std::get(getRaw(key)); + } + + // Can't implement in .cpp + /** + * Set a value in the configuration. + * @tparam T The type of the value to set. + * @param key The key of the value to set. + * @param value The value to set to. + */ + template + void set(const std::string &key, T value) const { + setRaw(key, static_cast(value)); + } + + /** + * Simple conversion for debug purposes, don't show any contained value. + * @return The string version of the object. + */ + [[nodiscard]] std::string toString() const; + + private: + enum OpCode { + NULL_CODE, + PROPERTY, + BEGIN_NAMESPACE, + END_NAMESPACE + }; + + enum Type { + NULL_TYPE, + UINT8, + UINT16, + UINT32, + UINT64, + STRING + }; + + enum VersionFlag { + EMPTY + }; + + class Node : public std::enable_shared_from_this { + public: + virtual ~Node() = default; + + virtual std::string getPath() = 0; + + protected: + std::shared_ptr m_parent = nullptr; + }; + + class NamespaceNode; + + class ValueNode final : public Node { + friend class NamespaceNode; + + public: + ValueNode(const std::string &key, const file_config_types_t &value); + + [[nodiscard]] std::shared_ptr getParent() const; + + std::string getKey(); + Type getType() const; + file_config_types_t getValue(); + + std::string getPath() override; + + void write(const std::shared_ptr& fileStream) const; + + private: + std::string m_key; + file_config_types_t m_value; + }; + + class NamespaceNode final : public Node { + friend class ValueNode; + + public: + explicit NamespaceNode(const std::string& name); + + [[nodiscard]] std::shared_ptr getParent() const; + + std::string getName(); + + bool hasValue(const std::string &key); + std::shared_ptr getValue(const std::string &key); + + bool hasNamespace(const std::string &name); + std::shared_ptr getNamespace(const std::string &name); + + void addValueNode(const std::shared_ptr& node); + void addNamespaceNode(const std::shared_ptr& node); + + std::string getPath() override; + + void write(const std::shared_ptr& fileStream) const; + + private: + std::string m_name; + std::map> m_namespaces; + std::map> m_values; + }; + + storage::Path m_path; + + std::shared_ptr m_fileStream; + size_t m_fileStreamSize; + size_t m_fileStreamCursor; + + uint8_t m_version; + + std::shared_ptr m_mainNode; + std::shared_ptr m_currentNode; + + /** + * Check the provided file format. + * @return True if the opened file is the correct format. + */ + [[nodiscard]] bool checkFormat(); + + void parse(); + + /** + * Read a property and store it. + */ + void newProperty(); + + /** + * Read a namespace and push it in the namespace stack. + */ + void pushNamespace(); + + /** + * Pop the last namespace from the namespace stack. + */ + void popNamespace(); + + /** + * Returns the key processed with the namespace stack. + * @param key The key to namespace. + * @return The namespaced key. + */ + [[nodiscard]] std::string getNamespacedKey(const std::string &key) const; + + /** + * Split a namespaced key to a vector. + * @param namespacedKey The key to split. + * @return A vector containing every namespace before the key (also included). + */ + static std::vector getSplicedNamespacedKey(const std::string &namespacedKey); + + [[nodiscard]] std::shared_ptr getNamespaceNodeFromNamespaceKey(const std::string &namespacedKey, + bool createNewNamespaces = false) const; + + [[nodiscard]] file_config_types_t getRaw(const std::string &key) const; + void setRaw(const std::string &key, const file_config_types_t& value) const; + + /* + * FileStream interface + */ + + void openFileStream(const storage::Path &path, storage::Mode mode); + void closeFileStream(); + + [[nodiscard]] bool hasNext() const; + + // Big endian + [[nodiscard]] uint8_t read(); + + [[nodiscard]] uint8_t readUint8(); + [[nodiscard]] uint16_t readUint16(); + [[nodiscard]] uint32_t readUint32(); + [[nodiscard]] uint64_t readUint64(); + [[nodiscard]] std::string readString(size_t length = 0); + [[nodiscard]] OpCode readOpCode(); + [[nodiscard]] Type readType(); + }; +} // libsystem + +#endif //FILECONFIG_HPP