From 831a5b9a459fb6b28257188c03166c87cbd2db5b Mon Sep 17 00:00:00 2001 From: neru Date: Thu, 7 May 2026 02:38:17 -0300 Subject: [PATCH] feat: add virtual file system --- src/lib/seallib/vfs.cpp | 274 ++++++++++++++++++++++++++++++++++++++++ src/lib/seallib/vfs.h | 127 +++++++++++++++++++ 2 files changed, 401 insertions(+) create mode 100644 src/lib/seallib/vfs.cpp create mode 100644 src/lib/seallib/vfs.h diff --git a/src/lib/seallib/vfs.cpp b/src/lib/seallib/vfs.cpp new file mode 100644 index 0000000..a2adfb8 --- /dev/null +++ b/src/lib/seallib/vfs.cpp @@ -0,0 +1,274 @@ +#include "vfs.h" + +#include +#include +#include + +using namespace seallib; + +/* + filebuffer +*/ +FileBuffer FileBuffer::fromString(const std::string& content) +{ + FileBuffer buffer; + buffer.size = content.size(); + buffer.data = std::make_unique(buffer.size); + std::memcpy(buffer.data.get(), content.data(), buffer.size); + return buffer; +} + +std::string FileBuffer::toString() const +{ + if (!isValid() || size == 0) return {}; + return std::string(reinterpret_cast(data.get()), size); +} + +/* + vfs +*/ +void VirtualFileSystem::mount(const std::string& virtualPath, std::unique_ptr provider, int priority) +{ + if (!provider) return; + + std::string normalizedPath = normalizePath(virtualPath); + normalizedPath = trimPathSeparators(normalizedPath); + + std::unique_lock lock(_mutex); + + _mounts.emplace_back(normalizedPath, std::move(provider), priority); + sortMounts(); +} + +bool VirtualFileSystem::unmount(const std::string& virtualPath) +{ + std::string normalizedPath = normalizePath(virtualPath); + normalizedPath = trimPathSeparators(normalizedPath); + + std::unique_lock lock(_mutex); + + auto it = std::find_if(_mounts.begin(), _mounts.end(), + [&normalizedPath](const MountPoint& mount) { return mount.virtualPath == normalizedPath; }); + + if (it != _mounts.end()) + { + _mounts.erase(it); + return true; + } + + return false; +} + +bool VirtualFileSystem::unmount(IFileProvider* provider) +{ + if (!provider) return false; + + std::unique_lock lock(_mutex); + + auto it = std::find_if(_mounts.begin(), _mounts.end(), + [provider](const MountPoint& mount) { return mount.provider.get() == provider; }); + + if (it != _mounts.end()) + { + _mounts.erase(it); + return true; + } + return false; +} + +void VirtualFileSystem::unmountAll() +{ + std::unique_lock lock(_mutex); + _mounts.clear(); +} + +bool VirtualFileSystem::exists(const std::string& path) const +{ + std::string relativePath; + const MountPoint* mount = findBestMount(path, relativePath); + + if (!mount) return false; + + return mount->provider->exists(relativePath); +} + +VFSResult VirtualFileSystem::readFile(const std::string& path, FileBuffer& out_buffer) const +{ + std::string relativePath; + const MountPoint* mount = findBestMount(path, relativePath); + + if (!mount) return VFSResult::FileNotFound; + + VFSResult result = mount->provider->readFile(relativePath, out_buffer); + return result; +} + +VFSResult VirtualFileSystem::writeFile(const std::string& path, const FileBuffer& buffer) +{ + if (!buffer.isValid()) return VFSResult::Unknown; + + std::string relativePath; + MountPoint* mount = findBestMount(path, relativePath); + + if (!mount) return VFSResult::AccessDenied; + + return mount->provider->writeFile(relativePath, buffer); +} + +VFSResult VirtualFileSystem::deleteFile(const std::string& path) +{ + std::string relativePath; + MountPoint* mount = findBestMount(path, relativePath); + + if (!mount) return VFSResult::FileNotFound; + + return mount->provider->deleteFile(relativePath); +} + +VFSResult VirtualFileSystem::createDirectory(const std::string& path) +{ + std::string relativePath; + MountPoint* mount = findBestMount(path, relativePath); + + if (!mount) return VFSResult::InvalidPath; + + return mount->provider->createDirectory(relativePath); +} + +std::vector VirtualFileSystem::listDirectory(const std::string& path) const +{ + std::string relativePath; + const MountPoint* mount = findBestMount(path, relativePath); + + if (!mount) return {}; + + return mount->provider->listDirectory(relativePath); +} + +std::string VirtualFileSystem::normalizePath(const std::string& path) const +{ + if (path.empty()) return "/"; + + std::string result; + result.reserve(path.length()); + + for (char c : path) + { + if (isPathSeparator(c)) + result += '/'; + else + result += static_cast(std::tolower(static_cast(c))); + } + + std::vector parts; + size_t pos = 0; + while (pos < result.length()) + { + size_t next = result.find('/', pos); + if (next == std::string::npos) next = result.length(); + + std::string part = result.substr(pos, next - pos); + if (part == "..") + { + if (!parts.empty()) parts.pop_back(); + } + else if (part != "." && !part.empty()) + { + parts.push_back(part); + } + + pos = next + 1; + } + + if (parts.empty()) return "/"; + + result.clear(); + for (const auto& part : parts) + { + result += '/' + part; + } + + return result; +} + +const VirtualFileSystem::MountPoint* VirtualFileSystem::findBestMount(const std::string& path, + std::string& relativePath) const +{ + std::string normalizedFullPath = normalizePath(path); + std::string searchPath = trimPathSeparators(normalizedFullPath); + + std::shared_lock lock(_mutex); + + for (const auto& mount : _mounts) + { + bool match = false; + + if (searchPath == mount.virtualPath) + { + match = true; + relativePath = "/"; + } + else if (searchPath.length() > mount.virtualPath.length() && searchPath.find(mount.virtualPath + '/') == 0) + { + match = true; + relativePath = searchPath.substr(mount.virtualPath.length() + 1); + } + + if (match) + { + if (relativePath.empty()) relativePath = "/"; + return &mount; + } + } + + return nullptr; +} + +VirtualFileSystem::MountPoint* VirtualFileSystem::findBestMount(const std::string& path, std::string& relativePath) +{ + const auto* constResult = const_cast(this)->findBestMount(path, relativePath); + return const_cast(constResult); +} + +void VirtualFileSystem::sortMounts() +{ + for (auto& m : _mounts) + m.pathDepth = MountPoint::countPathDepth(m.virtualPath); + + std::sort(_mounts.begin(), _mounts.end(), [](const MountPoint& a, const MountPoint& b) { + if (a.priority != b.priority) return a.priority > b.priority; + return a.pathDepth > b.pathDepth; + }); +} + +bool VirtualFileSystem::isPathSeparator(char c) +{ + return c == '/' || c == '\\'; +} + +std::string VirtualFileSystem::trimPathSeparators(const std::string& path) +{ + if (path.empty()) return path; + + size_t start = 0; + size_t end = path.length(); + + while (start < end && isPathSeparator(path[start])) + start++; + + while (end > start && isPathSeparator(path[end - 1])) + end--; + + return path.substr(start, end - start); +} + +/* + mountpoint +*/ +size_t VirtualFileSystem::MountPoint::countPathDepth(const std::string& path) +{ + size_t depth = 0; + for (char c : path) + if (isPathSeparator(c)) depth++; + return depth; +} diff --git a/src/lib/seallib/vfs.h b/src/lib/seallib/vfs.h new file mode 100644 index 0000000..5e05d19 --- /dev/null +++ b/src/lib/seallib/vfs.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include + +namespace seallib +{ + struct FileBuffer + { + std::unique_ptr data; + size_t size = 0; + bool isValid() const { return data != nullptr; } + + static FileBuffer fromString(const std::string& content); + std::string toString() const; + }; + + enum class VFSResult + { + Success = 0, + FileNotFound, + AccessDenied, + ProviderError, + InvalidPath, + AlreadyExists, + NotADirectory, + Unknown + }; + + class IFileProvider + { + public: + virtual ~IFileProvider() = default; + + virtual bool exists(const std::string& path) const = 0; + + virtual VFSResult readFile(const std::string& path, FileBuffer& out_buffer) const = 0; + virtual VFSResult writeFile(const std::string& path, const FileBuffer& buffer) = 0; + + virtual VFSResult deleteFile(const std::string& path) = 0; + virtual VFSResult createDirectory(const std::string& path) = 0; + + virtual std::vector listDirectory(const std::string& path) const = 0; + + virtual std::string getProviderName() const { return "UnknownProvider"; } + }; + + class VirtualFileSystem + { + public: + VirtualFileSystem() = default; + ~VirtualFileSystem() = default; + + VirtualFileSystem(const VirtualFileSystem&) = delete; + VirtualFileSystem& operator=(const VirtualFileSystem&) = delete; + + VirtualFileSystem(VirtualFileSystem&&) noexcept = default; + VirtualFileSystem& operator=(VirtualFileSystem&&) noexcept = default; + + void mount(const std::string& virtualPath, std::unique_ptr provider, int priority = 0); + bool unmount(const std::string& virtualPath); + bool unmount(IFileProvider* provider); + void unmountAll(); + + bool exists(const std::string& path) const; + VFSResult readFile(const std::string& path, FileBuffer& out_buffer) const; + VFSResult writeFile(const std::string& path, const FileBuffer& buffer); + VFSResult deleteFile(const std::string& path); + + VFSResult createDirectory(const std::string& path); + std::vector listDirectory(const std::string& path) const; + + std::string normalizePath(const std::string& path) const; + + private: + struct MountPoint + { + std::string virtualPath; + std::unique_ptr provider; + int priority; + size_t pathDepth; + + MountPoint(std::string path, std::unique_ptr prov, int prio) + : virtualPath(std::move(path)), provider(std::move(prov)), priority(prio), + pathDepth(countPathDepth(virtualPath)) + { + } + + static size_t countPathDepth(const std::string& path); + }; + + std::vector _mounts; + mutable std::shared_mutex _mutex; + + const MountPoint* findBestMount(const std::string& path, std::string& relativePath) const; + MountPoint* findBestMount(const std::string& path, std::string& relativePath); + + void sortMounts(); + static bool isPathSeparator(char c); + static std::string trimPathSeparators(const std::string& path); + }; + + inline std::string vfsResultToString(VFSResult result) + { + switch (result) + { + case VFSResult::Success: + return "Success"; + case VFSResult::FileNotFound: + return "FileNotFound"; + case VFSResult::AccessDenied: + return "AccessDenied"; + case VFSResult::ProviderError: + return "ProviderError"; + case VFSResult::InvalidPath: + return "InvalidPath"; + case VFSResult::AlreadyExists: + return "AlreadyExists"; + case VFSResult::NotADirectory: + return "NotADirectory"; + default: + return "Unknown"; + } + } +} // namespace seallib