feat: add virtual file system
This commit is contained in:
@@ -0,0 +1,274 @@
|
|||||||
|
#include "vfs.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
using namespace seallib;
|
||||||
|
|
||||||
|
/*
|
||||||
|
filebuffer
|
||||||
|
*/
|
||||||
|
FileBuffer FileBuffer::fromString(const std::string& content)
|
||||||
|
{
|
||||||
|
FileBuffer buffer;
|
||||||
|
buffer.size = content.size();
|
||||||
|
buffer.data = std::make_unique<uint8_t[]>(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<const char*>(data.get()), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
vfs
|
||||||
|
*/
|
||||||
|
void VirtualFileSystem::mount(const std::string& virtualPath, std::unique_ptr<IFileProvider> provider, int priority)
|
||||||
|
{
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
std::string normalizedPath = normalizePath(virtualPath);
|
||||||
|
normalizedPath = trimPathSeparators(normalizedPath);
|
||||||
|
|
||||||
|
std::unique_lock<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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<std::string> 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<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> 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<std::shared_mutex> 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<const VirtualFileSystem*>(this)->findBestMount(path, relativePath);
|
||||||
|
return const_cast<MountPoint*>(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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
namespace seallib
|
||||||
|
{
|
||||||
|
struct FileBuffer
|
||||||
|
{
|
||||||
|
std::unique_ptr<uint8_t[]> 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<std::string> 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<IFileProvider> 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<std::string> listDirectory(const std::string& path) const;
|
||||||
|
|
||||||
|
std::string normalizePath(const std::string& path) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct MountPoint
|
||||||
|
{
|
||||||
|
std::string virtualPath;
|
||||||
|
std::unique_ptr<IFileProvider> provider;
|
||||||
|
int priority;
|
||||||
|
size_t pathDepth;
|
||||||
|
|
||||||
|
MountPoint(std::string path, std::unique_ptr<IFileProvider> 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<MountPoint> _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
|
||||||
Reference in New Issue
Block a user