diff --git a/src/io/devices/MemoryDevice.cpp b/src/io/devices/MemoryDevice.cpp new file mode 100644 index 00000000..8f46d6b7 --- /dev/null +++ b/src/io/devices/MemoryDevice.cpp @@ -0,0 +1,210 @@ +#include "MemoryDevice.hpp" + +#include "../memory_istream.hpp" +#include "../memory_ostream.hpp" +#include "../finalizing_ostream.hpp" + +#include + +io::MemoryDevice::MemoryDevice() {} + +std::filesystem::path io::MemoryDevice::resolve(std::string_view path) { + throw std::runtime_error("unable to resolve filesystem path"); +} + +std::unique_ptr io::MemoryDevice::write(std::string_view path) { + std::string filePath = std::string(path); + return std::make_unique( + std::make_unique(), + [this, filePath](auto ostream) { + auto& memoryStream = dynamic_cast(*ostream); + createFile(std::move(filePath), memoryStream.release()); + } + ); +} + +std::unique_ptr io::MemoryDevice::read(std::string_view path) { + const auto& found = nodes.find(std::string(path)); + if (found == nodes.end()) { + return nullptr; + } + auto& node = found->second; + if (auto file = node.get_if()) { + if (file->content != nullptr) { + return std::make_unique(file->content); + } + } + return nullptr; +} + +size_t io::MemoryDevice::size(std::string_view path) { + const auto& found = nodes.find(std::string(path)); + if (found == nodes.end()) { + return 0; + } + return std::visit([](auto&& arg) -> size_t { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return arg.content.size(); + } else if constexpr (std::is_same_v) { + return arg.content.size(); + } else { + return 0; + } + }, found->second.data); +} + +io::file_time_type io::MemoryDevice::lastWriteTime(std::string_view path) { + return file_time_type::min(); +} + +bool io::MemoryDevice::exists(std::string_view path) { + return nodes.find(std::string(path)) != nodes.end(); +} + +bool io::MemoryDevice::isdir(std::string_view path) { + const auto& found = nodes.find(std::string(path)); + if (found == nodes.end()) { + return false; + } + return found->second.holds_alternative(); +} + +bool io::MemoryDevice::isfile(std::string_view path) { + const auto& found = nodes.find(std::string(path)); + if (found == nodes.end()) { + return false; + } + return found->second.holds_alternative(); +} + +bool io::MemoryDevice::mkdir(std::string_view path) { + return createDir(std::string(path)) != nullptr; +} + +bool io::MemoryDevice::mkdirs(std::string_view path) { + io::path dirPath = std::string(path); + std::vector parts; + while (!dirPath.pathPart().empty()) { + parts.push_back(dirPath.name()); + dirPath = dirPath.parent(); + } + for (int i = parts.size() - 1; i >= 0; i--) { + dirPath = dirPath / parts[i]; + createDir(dirPath.string()); + } + return true; +} + +bool io::MemoryDevice::remove(std::string_view path) { + std::string pathString = std::string(path); + const auto& found = nodes.find(pathString); + if (found == nodes.end()) { + return false; + } + if (found->second.holds_alternative()) { + const auto& dir = found->second.get_if(); + if (!dir->content.empty()) { + return false; + } + } + io::path filePath = pathString; + io::path parentPath = filePath.parent(); + auto parentDir = getDir(parentPath.string()); + if (parentDir) { + auto& content = parentDir->content; + content.erase( + std::remove(content.begin(), content.end(), filePath.name()), + content.end() + ); + } + nodes.erase(found); + return true; +} + +uint64_t io::MemoryDevice::removeAll(std::string_view path) { + std::string pathString = std::string(path); + const auto& found = nodes.find(pathString); + if (found == nodes.end()) { + return 0; + } + io::path filePath = pathString; + + uint64_t count = 0; + if (found->second.holds_alternative()) { + auto dir = found->second.get_if(); + for (const auto& name : dir->content) { + io::path subPath = filePath / name; + count += removeAll(subPath.string()); + } + } + if (remove(pathString)) { + count++; + } + return count; +} + +namespace { + struct MemoryPathsGenerator : public io::PathsGenerator { + std::vector entries; + size_t index = 0; + + MemoryPathsGenerator(std::vector&& entries) + : entries(std::move(entries)) {} + + bool next(io::path& outPath) override { + if (index >= entries.size()) { + return false; + } + outPath = entries[index++]; + return true; + } + }; +} + +std::unique_ptr io::MemoryDevice::list(std::string_view path) { + auto dir = getDir(path); + if (!dir) { + return nullptr; + } + return std::make_unique( + std::vector(dir->content) + ); +} + +io::MemoryDevice::Dir* io::MemoryDevice::createDir(std::string path) { + io::path filePath = path; + io::path parent = filePath.parent(); + auto parentDir = getDir(parent.string()); + if (!parentDir) { + return nullptr; + } + parentDir->content.push_back(filePath.name()); + auto& node = nodes[std::move(path)]; + node.data = Dir {}; + return node.get_if(); +} + +io::MemoryDevice::Node* io::MemoryDevice::createFile( + std::string path, util::Buffer&& content +) { + io::path filePath = path; + io::path parent = filePath.parent(); + auto dir = getDir(parent.string()); + if (!dir) { + return nullptr; + } + dir->content.push_back(filePath.name()); + auto& node = nodes[std::move(path)]; + node.data = File {std::move(content)}; + return &node; +} + +io::MemoryDevice::Dir* io::MemoryDevice::getDir(std::string_view path) { + const auto& found = nodes.find(std::string(path)); + if (found == nodes.end()) { + return nullptr; + } + auto& node = found->second; + return node.get_if(); +} diff --git a/src/io/devices/MemoryDevice.hpp b/src/io/devices/MemoryDevice.hpp new file mode 100644 index 00000000..b8eef5fb --- /dev/null +++ b/src/io/devices/MemoryDevice.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "Device.hpp" +#include "util/Buffer.hpp" + +#include +#include +#include +#include + +namespace io { + /// @brief In-memory filesystem device + class MemoryDevice : public Device { + enum class NodeType { + DIR, FILE + }; + + struct File { + util::Buffer content; + }; + + struct Dir { + std::vector content; + }; + + struct Node { + std::variant data; + + NodeType type() const { + return std::visit([](auto&& arg) -> NodeType { + using T = std::decay_t; + if constexpr (std::is_same_v) return NodeType::DIR; + else if constexpr (std::is_same_v) return NodeType::FILE; + }, data); + } + + template + bool holds_alternative() const { + return std::holds_alternative(data); + } + + template + T* get_if() { + return std::get_if(&data); + } + }; + public: + MemoryDevice(); + + std::filesystem::path resolve(std::string_view path) override; + std::unique_ptr write(std::string_view path) override; + std::unique_ptr read(std::string_view path) override; + size_t size(std::string_view path) override; + file_time_type lastWriteTime(std::string_view path) override; + bool exists(std::string_view path) override; + bool isdir(std::string_view path) override; + bool isfile(std::string_view path) override; + bool mkdir(std::string_view path) override; + bool mkdirs(std::string_view path) override; + bool remove(std::string_view path) override; + uint64_t removeAll(std::string_view path) override; + std::unique_ptr list(std::string_view path) override; + private: + std::unordered_map nodes; + + Node* createFile(std::string path, util::Buffer&& content); + Dir* createDir(std::string path); + Dir* getDir(std::string_view path); + }; +}