Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f979dacd4 | |||
| b51f253705 | |||
| 61983f5903 | |||
| fb259b019b | |||
| 4d63fe9b71 | |||
| 4a0d387328 | |||
| 9d3b5d25b2 | |||
| 0fbf8c715c | |||
| 430cb5bc5b |
+1
-1
@@ -32,7 +32,7 @@ if (SEALLIB_ASSERT)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SEALLIB_EVENTS)
|
if (SEALLIB_EVENTS)
|
||||||
list(APPEND SEALLIB_SOURCES "src/lib/seallib/events.cpp" "src/lib/seallib/events.h")
|
list(APPEND SEALLIB_SOURCES "src/lib/seallib/events.h")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SEALLIB_LOG)
|
if (SEALLIB_LOG)
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace seallib
|
||||||
|
{
|
||||||
|
template <typename... T> class Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using CallbackFunc = std::function<void(T...)>;
|
||||||
|
using ConnectionId = size_t;
|
||||||
|
|
||||||
|
ConnectionId addListener(CallbackFunc listener)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mtx);
|
||||||
|
ConnectionId id = ++_nextId;
|
||||||
|
_listeners.push_back({id, std::move(listener)});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool removeListener(ConnectionId id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mtx);
|
||||||
|
|
||||||
|
auto it =
|
||||||
|
std::find_if(_listeners.begin(), _listeners.end(), [id](const Listener& l) { return l.id == id; });
|
||||||
|
|
||||||
|
if (it != _listeners.end())
|
||||||
|
{
|
||||||
|
_listeners.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args> void run(Args&&... args)
|
||||||
|
{
|
||||||
|
std::vector<CallbackFunc> snapshot;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mtx);
|
||||||
|
snapshot.reserve(_listeners.size());
|
||||||
|
for (const auto& l : _listeners)
|
||||||
|
{
|
||||||
|
snapshot.push_back(l.func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& func : snapshot)
|
||||||
|
{
|
||||||
|
func(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mtx);
|
||||||
|
_listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Listener
|
||||||
|
{
|
||||||
|
ConnectionId id;
|
||||||
|
CallbackFunc func;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Listener> _listeners;
|
||||||
|
std::mutex _mtx;
|
||||||
|
std::atomic<ConnectionId> _nextId{0};
|
||||||
|
};
|
||||||
|
} // namespace seallib
|
||||||
+66
-15
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
using namespace seallib;
|
using namespace seallib;
|
||||||
|
|
||||||
@@ -87,20 +88,32 @@ void VirtualFileSystem::unmountAll()
|
|||||||
bool VirtualFileSystem::exists(const std::string& path) const
|
bool VirtualFileSystem::exists(const std::string& path) const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(_mutex);
|
std::shared_lock lock(_mutex);
|
||||||
std::string relativePath;
|
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||||
const MountPoint* mount = findBestMount(path, relativePath);
|
|
||||||
|
|
||||||
return mount ? mount->provider->exists(relativePath) : false;
|
for (const auto& mount : _mounts)
|
||||||
|
{
|
||||||
|
std::string relativePath;
|
||||||
|
if (getRelativePath(mount, searchPath, relativePath))
|
||||||
|
{
|
||||||
|
if (mount.provider->exists(relativePath)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VFSResult VirtualFileSystem::readFile(const std::string& path, FileBuffer& outBuffer) const
|
VFSResult VirtualFileSystem::readFile(const std::string& path, FileBuffer& outBuffer) const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(_mutex);
|
std::shared_lock lock(_mutex);
|
||||||
std::string relativePath;
|
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||||
const MountPoint* mount = findBestMount(path, relativePath);
|
|
||||||
|
|
||||||
if (!mount) return VFSResult::FileNotFound;
|
for (const auto& mount : _mounts)
|
||||||
return mount->provider->readFile(relativePath, outBuffer);
|
{
|
||||||
|
std::string relativePath;
|
||||||
|
if (getRelativePath(mount, searchPath, relativePath))
|
||||||
|
if (mount.provider->exists(relativePath)) return mount.provider->readFile(relativePath, outBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return VFSResult::FileNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
VFSResult VirtualFileSystem::writeFile(const std::string& path, const FileBuffer& buffer)
|
VFSResult VirtualFileSystem::writeFile(const std::string& path, const FileBuffer& buffer)
|
||||||
@@ -118,11 +131,16 @@ VFSResult VirtualFileSystem::writeFile(const std::string& path, const FileBuffer
|
|||||||
VFSResult VirtualFileSystem::deleteFile(const std::string& path)
|
VFSResult VirtualFileSystem::deleteFile(const std::string& path)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(_mutex);
|
std::shared_lock lock(_mutex);
|
||||||
std::string relativePath;
|
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||||
MountPoint* mount = findBestMount(path, relativePath);
|
|
||||||
|
|
||||||
if (!mount) return VFSResult::FileNotFound;
|
for (auto& mount : _mounts)
|
||||||
return mount->provider->deleteFile(relativePath);
|
{
|
||||||
|
std::string relativePath;
|
||||||
|
if (getRelativePath(mount, searchPath, relativePath))
|
||||||
|
if (mount.provider->exists(relativePath)) return mount.provider->deleteFile(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return VFSResult::FileNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
VFSResult VirtualFileSystem::createDirectory(const std::string& path)
|
VFSResult VirtualFileSystem::createDirectory(const std::string& path)
|
||||||
@@ -138,11 +156,22 @@ VFSResult VirtualFileSystem::createDirectory(const std::string& path)
|
|||||||
std::vector<std::string> VirtualFileSystem::listDirectory(const std::string& path) const
|
std::vector<std::string> VirtualFileSystem::listDirectory(const std::string& path) const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(_mutex);
|
std::shared_lock lock(_mutex);
|
||||||
std::string relativePath;
|
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||||
const MountPoint* mount = findBestMount(path, relativePath);
|
|
||||||
|
|
||||||
if (!mount) return {};
|
std::set<std::string> mergedFiles;
|
||||||
return mount->provider->listDirectory(relativePath);
|
|
||||||
|
for (const auto& mount : _mounts)
|
||||||
|
{
|
||||||
|
std::string relativePath;
|
||||||
|
if (getRelativePath(mount, searchPath, relativePath))
|
||||||
|
{
|
||||||
|
auto files = mount.provider->listDirectory(relativePath);
|
||||||
|
for (const auto& f : files)
|
||||||
|
mergedFiles.insert(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {mergedFiles.begin(), mergedFiles.end()};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string VirtualFileSystem::normalizePath(const std::string& path) const
|
std::string VirtualFileSystem::normalizePath(const std::string& path) const
|
||||||
@@ -240,6 +269,28 @@ std::string VirtualFileSystem::trimPathSeparators(const std::string& path)
|
|||||||
return path.substr(start, end - start + 1);
|
return path.substr(start, end - start + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VirtualFileSystem::getRelativePath(const MountPoint& mount, const std::string& searchPath,
|
||||||
|
std::string& outRelative) const
|
||||||
|
{
|
||||||
|
if (searchPath == mount.virtualPath)
|
||||||
|
{
|
||||||
|
outRelative = "/";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (mount.virtualPath == "/")
|
||||||
|
{
|
||||||
|
outRelative = searchPath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::string prefix = mount.virtualPath + '/';
|
||||||
|
if (searchPath.compare(0, prefix.length(), prefix) == 0)
|
||||||
|
{
|
||||||
|
outRelative = searchPath.substr(prefix.length());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
mountpoint
|
mountpoint
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ namespace seallib
|
|||||||
|
|
||||||
static bool isPathSeparator(char c);
|
static bool isPathSeparator(char c);
|
||||||
static std::string trimPathSeparators(const std::string& path);
|
static std::string trimPathSeparators(const std::string& path);
|
||||||
|
|
||||||
|
bool getRelativePath(const MountPoint& mount, const std::string& searchPath,
|
||||||
|
std::string& outRelative) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::string vfsResultToString(VFSResult result)
|
inline std::string vfsResultToString(VFSResult result)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
test implementation interface
|
test implementation interface
|
||||||
@@ -13,8 +15,38 @@ class ITest
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void run() = 0;
|
virtual void run() = 0;
|
||||||
|
virtual const char* getName() = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
shamelessly yoinked from log lib
|
||||||
|
*/
|
||||||
|
template <typename... Args> void logVerbose(std::format_string<Args...> fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
writeLog("\x1b[34;40m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args> void logInfo(std::format_string<Args...> fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
writeLog("\x1b[32;40m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args> void logWarning(std::format_string<Args...> fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
writeLog("\x1b[30;43m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args> void logError(std::format_string<Args...> fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
writeLog("\x1b[97;41m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
|
}
|
||||||
|
|
||||||
friend class TestRunner;
|
friend class TestRunner;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void writeLog(const char* color, const std::string& message)
|
||||||
|
{
|
||||||
|
std::cout << color << "[" << getName() << "]" << "\x1b[0m " << message << std::endl;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -26,7 +58,11 @@ class TestRunner
|
|||||||
void runTests()
|
void runTests()
|
||||||
{
|
{
|
||||||
for (auto& test : _tests)
|
for (auto& test : _tests)
|
||||||
|
{
|
||||||
|
test->logInfo("------- Init -------");
|
||||||
test->run();
|
test->run();
|
||||||
|
test->logInfo("-------- End --------");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestRunner& get()
|
static TestRunner& get()
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#include "tests.h"
|
||||||
|
|
||||||
|
#include <seallib/events.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace seallib;
|
||||||
|
|
||||||
|
class EventTest : ITest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EventTest() : ITest() {};
|
||||||
|
|
||||||
|
virtual void run() override
|
||||||
|
{
|
||||||
|
logInfo("Instantiating event");
|
||||||
|
Event<std::string> testEvent;
|
||||||
|
|
||||||
|
logInfo("Registering listeners");
|
||||||
|
auto listener1 = testEvent.addListener([](std::string str) {
|
||||||
|
std::cout << "(Listener 1) Event has been fired with string: " << str << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto listener2 = testEvent.addListener([](std::string str) {
|
||||||
|
std::cout << "(Listener 2) Event has been fired, string: " << str << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
logInfo("Running event with both listeners");
|
||||||
|
const char* str1 = "somestring";
|
||||||
|
testEvent.run(str1);
|
||||||
|
|
||||||
|
std::string str2("hello world");
|
||||||
|
testEvent.run(str2);
|
||||||
|
|
||||||
|
logInfo("Removing listener 2");
|
||||||
|
testEvent.removeListener(listener2);
|
||||||
|
|
||||||
|
logInfo("Running event with only 1 listener");
|
||||||
|
testEvent.run("event without listener 2");
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual const char* getName() override { return "EventTest"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static EventTest g_eventTest;
|
||||||
@@ -55,14 +55,20 @@ class LogTest : ITest
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
createAndSetupConsole();
|
createAndSetupConsole();
|
||||||
#endif
|
#endif
|
||||||
|
logInfo("Instantiating logger");
|
||||||
Logger log("TestLogger");
|
Logger log("TestLogger");
|
||||||
|
|
||||||
|
logInfo("Registering sink");
|
||||||
log.addSink(std::make_shared<TestLogSink>());
|
log.addSink(std::make_shared<TestLogSink>());
|
||||||
|
|
||||||
|
logInfo("Calling log functions");
|
||||||
log.verbose("verbose log");
|
log.verbose("verbose log");
|
||||||
log.info("info log");
|
log.info("info log");
|
||||||
log.warning("warning log");
|
log.warning("warning log");
|
||||||
log.error("error log");
|
log.error("error log");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual const char* getName() override { return "LogTest"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
static LogTest g_logTest;
|
static LogTest g_logTest;
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#include "tests.h"
|
||||||
|
|
||||||
|
#include <seallib/vfs.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
using namespace seallib;
|
||||||
|
|
||||||
|
/*
|
||||||
|
in-memory file provider
|
||||||
|
*/
|
||||||
|
class MemoryFileProvider : public IFileProvider
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> files;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool exists(const std::string& path) const override { return files.count(path); }
|
||||||
|
|
||||||
|
VFSResult readFile(const std::string& path, FileBuffer& outBuffer) const override
|
||||||
|
{
|
||||||
|
if (!exists(path)) return VFSResult::FileNotFound;
|
||||||
|
outBuffer = FileBuffer::fromString(files.at(path));
|
||||||
|
return VFSResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
VFSResult writeFile(const std::string& path, const FileBuffer& buffer) override
|
||||||
|
{
|
||||||
|
files[path] = buffer.toString();
|
||||||
|
return VFSResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
VFSResult deleteFile(const std::string& path) override
|
||||||
|
{
|
||||||
|
return files.erase(path) ? VFSResult::Success : VFSResult::FileNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
VFSResult createDirectory(const std::string& path) override { return VFSResult::Success; }
|
||||||
|
|
||||||
|
std::vector<std::string> listDirectory(const std::string& path) const override
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (auto const& [key, val] : files)
|
||||||
|
result.push_back(key);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getProviderName() const override { return "MemoryProvider"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class VFSTest : ITest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VFSTest() : ITest() {};
|
||||||
|
|
||||||
|
virtual void run() override
|
||||||
|
{
|
||||||
|
VirtualFileSystem vfs;
|
||||||
|
|
||||||
|
auto prov1 = std::make_unique<MemoryFileProvider>();
|
||||||
|
auto prov2 = std::make_unique<MemoryFileProvider>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
mnt test
|
||||||
|
*/
|
||||||
|
vfs.mount("scripts", std::move(prov1));
|
||||||
|
vfs.mount("config/network", std::move(prov2));
|
||||||
|
|
||||||
|
/*
|
||||||
|
write / read tests
|
||||||
|
*/
|
||||||
|
logInfo("Writing to different mount points");
|
||||||
|
vfs.writeFile("scripts/main.lua", FileBuffer::fromString("print('hello')"));
|
||||||
|
vfs.writeFile("config/network/settings.json", FileBuffer::fromString("{ \"ip\": \"127.0.0.1\" }"));
|
||||||
|
|
||||||
|
logInfo("Reading from mount points");
|
||||||
|
FileBuffer b1, b2;
|
||||||
|
if (vfs.readFile("scripts/main.lua", b1) == VFSResult::Success) logInfo("Read Scripts: {}", b1.toString());
|
||||||
|
|
||||||
|
/*
|
||||||
|
existence ands crosstalk
|
||||||
|
*/
|
||||||
|
if (!vfs.exists("scripts/main.lua")) logError("scripts/main.lua is missing?");
|
||||||
|
if (vfs.exists("scripts/settings.json")) logError("wrong provider, settings.json should be inaccessible");
|
||||||
|
logInfo("Path isolation verified");
|
||||||
|
|
||||||
|
/*
|
||||||
|
priority / overlapping mounts
|
||||||
|
*/
|
||||||
|
auto overrideProv = std::make_unique<MemoryFileProvider>();
|
||||||
|
overrideProv->writeFile("patch.txt", FileBuffer::fromString("Hotfix Data"));
|
||||||
|
|
||||||
|
vfs.mount("scripts", std::move(overrideProv), 10); // higher priority
|
||||||
|
|
||||||
|
FileBuffer b3;
|
||||||
|
if (vfs.readFile("scripts/patch.txt", b3) == VFSResult::Success)
|
||||||
|
logInfo("Priority mount successful: {}", b3.toString());
|
||||||
|
|
||||||
|
/*
|
||||||
|
directory listing
|
||||||
|
*/
|
||||||
|
logInfo("Listing 'scripts' folder");
|
||||||
|
|
||||||
|
auto list = vfs.listDirectory("scripts");
|
||||||
|
for (const auto& file : list)
|
||||||
|
logInfo("\t* {}", file);
|
||||||
|
|
||||||
|
/*
|
||||||
|
deletion
|
||||||
|
*/
|
||||||
|
VFSResult delRes = vfs.deleteFile("scripts/main.lua");
|
||||||
|
logInfo("Deletion result: {}", vfsResultToString(delRes));
|
||||||
|
if (vfs.exists("scripts/main.lua")) logError("Failed to delete file, scrips/main.lua still accessible");
|
||||||
|
|
||||||
|
/*
|
||||||
|
unmounting
|
||||||
|
*/
|
||||||
|
vfs.unmount("scripts");
|
||||||
|
if (!vfs.exists("scripts/patch.txt")) logInfo("Unmount successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* getName() override { return "VFSTest"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static VFSTest g_vfsTest;
|
||||||
Reference in New Issue
Block a user