diff --git a/include/Nazara/Core/VirtualDirectory.hpp b/include/Nazara/Core/VirtualDirectory.hpp index bae739a04..dd2240e16 100644 --- a/include/Nazara/Core/VirtualDirectory.hpp +++ b/include/Nazara/Core/VirtualDirectory.hpp @@ -18,28 +18,33 @@ namespace Nz { + class VirtualDirectory; + + using VirtualDirectoryPtr = std::shared_ptr; + class VirtualDirectory : public std::enable_shared_from_this { public: struct DataPointerEntry; + struct DirectoryEntry; struct FileContentEntry; - using PhysicalFileEntry = std::filesystem::path; - using VirtualDirectoryEntry = std::shared_ptr; + struct PhysicalDirectoryEntry; + struct PhysicalFileEntry; - using Entry = std::variant; + using Entry = std::variant; - inline VirtualDirectory(VirtualDirectoryEntry parentDirectory = nullptr); - inline VirtualDirectory(std::filesystem::path physicalPath, VirtualDirectoryEntry parentDirectory = nullptr); + inline VirtualDirectory(std::weak_ptr parentDirectory = {}); + inline VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr parentDirectory = {}); VirtualDirectory(const VirtualDirectory&) = delete; VirtualDirectory(VirtualDirectory&&) = delete; ~VirtualDirectory() = default; - template void Foreach(F&& cb, bool includeDots = false); + template void Foreach(F&& callback, bool includeDots = false); - inline bool GetEntry(std::string_view path, Entry* entry); + template bool GetEntry(std::string_view path, F&& callback); - inline VirtualDirectoryEntry& StoreDirectory(std::string_view path, VirtualDirectoryEntry directory); - inline VirtualDirectoryEntry& StoreDirectory(std::string_view path, std::filesystem::path directoryPath); + inline DirectoryEntry& StoreDirectory(std::string_view path, VirtualDirectoryPtr directory); + inline PhysicalDirectoryEntry& StoreDirectory(std::string_view path, std::filesystem::path directoryPath); inline FileContentEntry& StoreFile(std::string_view path, std::vector file); inline PhysicalFileEntry& StoreFile(std::string_view path, std::filesystem::path filePath); inline DataPointerEntry& StoreFile(std::string_view path, const void* data, std::size_t size); @@ -53,28 +58,44 @@ namespace Nz std::size_t size; }; + struct DirectoryEntry + { + VirtualDirectoryPtr directory; + }; + struct FileContentEntry { - std::shared_ptr> data; + std::vector data; + }; + + struct PhysicalDirectoryEntry + { + std::filesystem::path filePath; + }; + + struct PhysicalFileEntry + { + std::filesystem::path filePath; }; private: - inline void EnsureDots(); - inline bool GetEntryInternal(std::string_view name, Entry* entry); + template bool GetEntryInternal(std::string_view name, F&& callback); inline bool RetrieveDirectory(std::string_view path, bool allowCreation, std::shared_ptr& directory, std::string_view& entryName); - inline VirtualDirectoryEntry& StoreDirectoryInternal(std::string name, std::filesystem::path directoryPath); - inline VirtualDirectoryEntry& StoreDirectoryInternal(std::string name, VirtualDirectoryEntry directory); - inline FileContentEntry& StoreFileInternal(std::string name, std::vector fileContent); - inline PhysicalFileEntry& StoreFileInternal(std::string name, std::filesystem::path filePath); - inline DataPointerEntry& StoreFileInternal(std::string name, const void* data, std::size_t size); + template T& StoreInternal(std::string name, T value); + template static bool CallbackReturn(F&& callback, Args&&... args); template static bool SplitPath(std::string_view path, F1&& dirCB, F2&& fileCB); - std::map> m_content; + struct ContentEntry + { + std::string name; + Entry entry; + }; + std::optional m_physicalPath; - VirtualDirectoryEntry m_parent; - bool m_wereDotRegistered; + std::vector m_content; + std::weak_ptr m_parent; }; } diff --git a/include/Nazara/Core/VirtualDirectory.inl b/include/Nazara/Core/VirtualDirectory.inl index 7ac76c9bc..06ba43053 100644 --- a/include/Nazara/Core/VirtualDirectory.inl +++ b/include/Nazara/Core/VirtualDirectory.inl @@ -8,89 +8,161 @@ namespace Nz { - inline VirtualDirectory::VirtualDirectory(VirtualDirectoryEntry parentDirectory) : - m_parent(std::move(parentDirectory)), - m_wereDotRegistered(false) + inline VirtualDirectory::VirtualDirectory(std::weak_ptr parentDirectory) : + m_parent(std::move(parentDirectory)) { } - inline VirtualDirectory::VirtualDirectory(std::filesystem::path physicalPath, VirtualDirectoryEntry parentDirectory) : + inline VirtualDirectory::VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr parentDirectory) : m_physicalPath(std::move(physicalPath)), - m_parent(std::move(parentDirectory)), - m_wereDotRegistered(false) + m_parent(std::move(parentDirectory)) { } template - void VirtualDirectory::Foreach(F&& cb, bool includeDots) + void VirtualDirectory::Foreach(F&& callback, bool includeDots) { if (includeDots) - EnsureDots(); - - for (auto&& pair : m_content) { - if (!includeDots && (pair.first == "." || pair.first == "..")) - continue; - - cb(pair.first, pair.second); + Entry ourselves = DirectoryEntry{ shared_from_this() }; + callback(std::string_view("."), ourselves); + if (VirtualDirectoryPtr parent = m_parent.lock()) + { + Entry parentEntry = DirectoryEntry{ parent }; + if (!CallbackReturn(callback, std::string_view(".."), parentEntry)) + return; + } + else if (!CallbackReturn(callback, std::string_view(".."), ourselves)) + return; + } + + for (auto&& entry : m_content) + { + if (!CallbackReturn(callback, std::string_view(entry.name), std::as_const(entry.entry))) + return; } if (m_physicalPath) { for (auto&& physicalEntry : std::filesystem::directory_iterator(*m_physicalPath)) { - Entry entry; - std::string filename = physicalEntry.path().filename().generic_u8string(); - if (m_content.find(filename) != m_content.end()) - continue; //< Physical file/directory has been overridden by a virtual one - if (physicalEntry.is_regular_file()) - entry.emplace(physicalEntry.path()); - else if (physicalEntry.is_directory()) + // Check if physical file/directory has been overridden by a virtual one + auto it = std::lower_bound(m_content.begin(), m_content.end(), filename, [](const ContentEntry& entry, std::string_view name) { - // FIXME: Allocating a shared_ptr on iteration is bad, not sure about a workaround - entry.emplace(std::make_shared(physicalEntry.path(), shared_from_this())); - } + return entry.name < name; + }); + if (it != m_content.end() && it->name == filename) + continue; + + std::filesystem::file_status status = physicalEntry.status(); + + Entry entry; + if (std::filesystem::is_regular_file(status)) + entry = PhysicalFileEntry{ physicalEntry.path() }; + else if (std::filesystem::is_directory(status)) + entry = PhysicalDirectoryEntry{ physicalEntry.path() }; else continue; - cb(physicalEntry.path().filename().generic_u8string(), entry); + if (!CallbackReturn(callback, std::string_view(filename), entry)) + return; } } } - - inline bool VirtualDirectory::GetEntry(std::string_view path, Entry* entry) + + template bool VirtualDirectory::GetEntry(std::string_view path, F&& callback) { - std::shared_ptr dir; - std::string_view entryName; - if (!RetrieveDirectory(path, false, dir, entryName)) - return false; + VirtualDirectoryPtr currentDir = shared_from_this(); + std::vector physicalDirectoryParts; + return SplitPath(path, [&](std::string_view dirName) + { + if (!physicalDirectoryParts.empty()) + { + // Special case when traversing directory + if (dirName == "..") + { + // Don't allow to escape virtual directory + if (!physicalDirectoryParts.empty()) + physicalDirectoryParts.pop_back(); + } + else if (dirName != ".") + physicalDirectoryParts.emplace_back(dirName); - if (!dir->GetEntryInternal(entryName, entry)) - return false; + return true; + } - return true; + return currentDir->GetEntryInternal(dirName, [&](const Entry& entry) + { + if (auto dirEntry = std::get_if(&entry)) + { + currentDir = dirEntry->directory; + return true; + } + else if (auto physDirEntry = std::get_if(&entry)) + { + // We're traversing a physical directory + physicalDirectoryParts.emplace_back(dirName); + return true; + } + + return false; + }); + }, + [&](std::string_view name) + { + if (!physicalDirectoryParts.empty()) + { + if (physicalDirectoryParts.empty() && (name == "." || name == "..")) + { + // Prevent escaping the virtual directory + Entry ourselves = DirectoryEntry{ shared_from_this() }; + return CallbackReturn(callback, ourselves); + } + + assert(m_physicalPath); + std::filesystem::path filePath = *m_physicalPath; + for (const auto& part : physicalDirectoryParts) + filePath /= part; + + filePath /= name; + + std::filesystem::file_status status = std::filesystem::status(filePath); //< FIXME: This will follow symlink, is this the intended behavior? (see symlink_status) + + Entry entry; + if (std::filesystem::is_regular_file(status)) + entry = PhysicalFileEntry{ std::move(filePath) }; + else if (std::filesystem::is_directory(status)) + entry = PhysicalDirectoryEntry{ std::move(filePath) }; + else + return false; //< either not known or of a special type + + return CallbackReturn(callback, entry); + } + else + return currentDir->GetEntryInternal(name, callback); + }); } - inline auto VirtualDirectory::StoreDirectory(std::string_view path, VirtualDirectoryEntry directory) -> VirtualDirectoryEntry& + inline auto VirtualDirectory::StoreDirectory(std::string_view path, VirtualDirectoryPtr directory) -> DirectoryEntry& { std::shared_ptr dir; std::string_view entryName; if (!RetrieveDirectory(path, true, dir, entryName)) throw std::runtime_error("invalid path"); - return dir->StoreDirectoryInternal(std::string(entryName), std::move(directory)); + return dir->StoreInternal(std::string(entryName), DirectoryEntry{ std::move(directory) }); } - inline auto VirtualDirectory::StoreDirectory(std::string_view path, std::filesystem::path directoryPath) -> VirtualDirectoryEntry& + inline auto VirtualDirectory::StoreDirectory(std::string_view path, std::filesystem::path directoryPath) -> PhysicalDirectoryEntry& { std::shared_ptr dir; std::string_view entryName; if (!RetrieveDirectory(path, true, dir, entryName)) throw std::runtime_error("invalid path"); - return dir->StoreDirectoryInternal(std::string(entryName), std::move(directoryPath)); + return dir->StoreInternal(std::string(entryName), PhysicalDirectoryEntry{ std::move(directoryPath) }); } inline auto VirtualDirectory::StoreFile(std::string_view path, std::vector file) -> FileContentEntry& @@ -100,7 +172,7 @@ namespace Nz if (!RetrieveDirectory(path, true, dir, entryName)) throw std::runtime_error("invalid path"); - return dir->StoreFileInternal(std::string(entryName), std::move(file)); + return dir->StoreInternal(std::string(entryName), FileContentEntry{ std::move(file) }); } inline auto VirtualDirectory::StoreFile(std::string_view path, std::filesystem::path filePath) -> PhysicalFileEntry& @@ -110,7 +182,7 @@ namespace Nz if (!RetrieveDirectory(path, true, dir, entryName)) throw std::runtime_error("invalid path"); - return dir->StoreFileInternal(std::string(entryName), std::move(filePath)); + return dir->StoreInternal(std::string(entryName), PhysicalFileEntry{ std::move(filePath) }); } inline auto VirtualDirectory::StoreFile(std::string_view path, const void* data, std::size_t size) -> DataPointerEntry& @@ -120,20 +192,55 @@ namespace Nz if (!RetrieveDirectory(path, true, dir, entryName)) throw std::runtime_error("invalid path"); - return dir->StoreFileInternal(std::string(entryName), data, size); + return dir->StoreInternal(std::string(entryName), DataPointerEntry{ data, size }); } - - inline void VirtualDirectory::EnsureDots() + + template bool VirtualDirectory::GetEntryInternal(std::string_view name, F&& callback) { - if (!m_wereDotRegistered) + if (name == ".") { - StoreDirectoryInternal(".", shared_from_this()); - if (m_parent) - StoreDirectoryInternal("..", m_parent); + Entry entry{ DirectoryEntry{ shared_from_this() } }; + return CallbackReturn(callback, entry); + } + else if (name == "..") + { + Entry entry; + if (VirtualDirectoryPtr parent = m_parent.lock()) + entry = DirectoryEntry{ std::move(parent) }; else - StoreDirectoryInternal("..", shared_from_this()); + entry = DirectoryEntry{ shared_from_this() }; - m_wereDotRegistered = true; + return CallbackReturn(callback, entry); + } + else + { + auto it = std::lower_bound(m_content.begin(), m_content.end(), name, [](const ContentEntry& entry, std::string_view name) + { + return entry.name < name; + }); + if (it == m_content.end() || it->name != name) + { + // Virtual file not found, check if it has a physical one + if (m_physicalPath) + { + std::filesystem::path filePath = *m_physicalPath / name; + std::filesystem::file_status status = std::filesystem::status(filePath); //< FIXME: This will follow symlink, is this the intended behavior? (see symlink_status) + + Entry entry; + if (std::filesystem::is_regular_file(status)) + entry = PhysicalFileEntry{ std::move(filePath) }; + else if (std::filesystem::is_directory(status)) + entry = PhysicalDirectoryEntry{ std::move(filePath) }; + else + return false; //< either not known or of a special type + + return CallbackReturn(callback, entry); + } + + return false; + } + + return CallbackReturn(callback, it->entry); } } @@ -143,25 +250,25 @@ namespace Nz return SplitPath(path, [&](std::string_view dirName) { - Entry entry; - if (directory->GetEntryInternal(dirName, &entry)) + bool dirFound = directory->GetEntryInternal(dirName, [&](const Entry& entry) { - if (auto dir = std::get_if(&entry)) - directory = *dir; + if (auto dirEntry = std::get_if(&entry)) + directory = dirEntry->directory; else - return false; - } - else - { - if (allowCreation) - { - auto newDirectory = std::make_shared(directory); - directory = directory->StoreDirectoryInternal(std::string(dirName), newDirectory); - } - else - return false; - } + allowCreation = false; //< does exist but is not a directory + }); + if (dirFound) + return true; + + // Try to create a new directory + if (!allowCreation) + return false; + + auto newDirectory = std::make_shared(directory); + directory->StoreDirectory(dirName, newDirectory); + + directory = std::move(newDirectory); return true; }, [&](std::string_view name) @@ -170,84 +277,44 @@ namespace Nz }); } - inline bool VirtualDirectory::GetEntryInternal(std::string_view name, Entry* entry) + template + T& VirtualDirectory::StoreInternal(std::string name, T value) { - EnsureDots(); - - auto it = m_content.find(name); - if (it != m_content.end()) + auto it = std::lower_bound(m_content.begin(), m_content.end(), name, [](const ContentEntry& entry, std::string_view name) { - *entry = it->second; + return entry.name < name; + }); + + ContentEntry* entryPtr; + if (it == m_content.end() || it->name == name) + entryPtr = &*m_content.emplace(it); + else + entryPtr = &*it; + + entryPtr->entry = std::move(value); + entryPtr->name = std::move(name); + + return std::get(entryPtr->entry); + } + + template + bool VirtualDirectory::CallbackReturn(F&& callback, Args&& ...args) + { + using Ret = decltype(callback(std::forward(args)...)); + if constexpr (std::is_void_v) + { + callback(std::forward(args)...); return true; } else { - if (m_physicalPath) - { - std::filesystem::path entryPath = *m_physicalPath / name; - - if (std::filesystem::is_regular_file(entryPath)) - entry->emplace(entryPath); - else if (std::filesystem::is_directory(entryPath)) - { - // FIXME: Allocating a shared_ptr on iteration is bad, not sure about a workaround - *entry = StoreDirectoryInternal(std::string(name), entryPath); - } - else - return false; - - return true; - } - - return false; + static_assert(std::is_same_v, "callback must either return a boolean or nothing"); + return callback(std::forward(args)...); } } - inline auto VirtualDirectory::StoreDirectoryInternal(std::string name, std::filesystem::path directoryPath) -> VirtualDirectoryEntry& - { - assert(name.find_first_of("\\/:") == name.npos); - - auto it = m_content.insert_or_assign(std::move(name), std::make_shared(directoryPath, shared_from_this())).first; - return std::get(it->second); - } - - inline auto VirtualDirectory::StoreDirectoryInternal(std::string name, VirtualDirectoryEntry directory) -> VirtualDirectoryEntry& - { - assert(name.find_first_of("\\/:") == name.npos); - - auto it = m_content.insert_or_assign(std::move(name), std::move(directory)).first; - return std::get(it->second); - } - - inline auto VirtualDirectory::StoreFileInternal(std::string name, std::vector fileContent) -> FileContentEntry& - { - assert(name.find_first_of("\\/:") == name.npos); - - FileContentEntry fileEntry; - fileEntry.data = std::make_shared>(std::move(fileContent)); - - auto it = m_content.insert_or_assign(std::move(name), std::move(fileEntry)).first; - return std::get(it->second); - } - - inline auto VirtualDirectory::StoreFileInternal(std::string name, std::filesystem::path filePath) -> PhysicalFileEntry& - { - assert(name.find_first_of("\\/:") == name.npos); - - auto it = m_content.insert_or_assign(std::move(name), std::move(filePath)).first; - return std::get(it->second); - } - - inline auto VirtualDirectory::StoreFileInternal(std::string name, const void* data, std::size_t size) -> DataPointerEntry& - { - assert(name.find_first_of("\\/:") == name.npos); - - auto it = m_content.insert_or_assign(std::move(name), DataPointerEntry{ data, size }).first; - return std::get(it->second); - } - template - inline bool VirtualDirectory::SplitPath(std::string_view path, F1&& dirCB, F2&& lastCB) + bool VirtualDirectory::SplitPath(std::string_view path, F1&& dirCB, F2&& lastCB) { std::size_t pos; while ((pos = path.find_first_of("\\/:")) != std::string::npos) @@ -258,8 +325,7 @@ namespace Nz path = path.substr(pos + 1); } - lastCB(path); - return true; + return CallbackReturn(lastCB, path); } } diff --git a/src/Nazara/Shader/DirectoryModuleResolver.cpp b/src/Nazara/Shader/DirectoryModuleResolver.cpp index 87dc1e6d4..ccbd6ca39 100644 --- a/src/Nazara/Shader/DirectoryModuleResolver.cpp +++ b/src/Nazara/Shader/DirectoryModuleResolver.cpp @@ -26,26 +26,25 @@ namespace Nz if (auto it = m_knownModules.find(fullPath); it != m_knownModules.end()) return it->second; - VirtualDirectory::Entry entry; - if (!m_searchDirectory->GetEntry(fullPath, &entry)) - return {}; - ShaderAst::ModulePtr shaderModule; - if (std::holds_alternative(entry)) + m_searchDirectory->GetEntry(fullPath, [&](const VirtualDirectory::Entry& entry) { - const auto& content = std::get(entry); - shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast(content.data), content.size)); - } - else if (std::holds_alternative(entry)) - { - const auto& content = std::get(entry); - shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast(content.data->data()), content.data->size())); - } - else if (std::holds_alternative(entry)) - { - const auto& filePath = std::get(entry); - shaderModule = ShaderLang::ParseFromFile(filePath); - } + if (std::holds_alternative(entry)) + { + const auto& dataContent = std::get(entry); + shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast(dataContent.data), dataContent.size)); + } + else if (std::holds_alternative(entry)) + { + const auto& fileContent = std::get(entry); + shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast(fileContent.data.data()), fileContent.data.size())); + } + else if (std::holds_alternative(entry)) + { + const auto& physicalEntry = std::get(entry); + shaderModule = ShaderLang::ParseFromFile(physicalEntry.filePath); + } + }); if (!shaderModule) return {}; diff --git a/tests/Engine/Core/VirtualDirectoryTest.cpp b/tests/Engine/Core/VirtualDirectoryTest.cpp index 0c28289b5..eead3ba9a 100644 --- a/tests/Engine/Core/VirtualDirectoryTest.cpp +++ b/tests/Engine/Core/VirtualDirectoryTest.cpp @@ -1,71 +1,191 @@ +#include +#include +#include #include +#include #include #include +std::filesystem::path GetResourceDir(); + TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") { - std::shared_ptr virtualDir = std::make_shared(); - - WHEN("Iterating it, it only has . and ..") + SECTION("Creating a virtual directory") { - bool failed = false; - virtualDir->Foreach([&](const std::string& name, const Nz::VirtualDirectory::Entry& /*entry*/) + std::shared_ptr virtualDir = std::make_shared(); + WHEN("Iterating it, it only has . and ..") { - if (name != "." && name != "..") - failed = true; - }, true); + bool dot = false; + bool dotDot = false; + virtualDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& /*entry*/) + { + if (name == ".") + { + CHECK_FALSE(dot); + dot = true; + } + else if (name == "..") + { + CHECK_FALSE(dotDot); + dotDot = true; + } + if (name != "." && name != "..") + FAIL("Got file " << name); + }, true); - CHECK_FALSE(failed); - } - AND_WHEN("Iterating it including dots, we get them") - { - bool failed = false; - virtualDir->Foreach([&](const std::string& name, const Nz::VirtualDirectory::Entry& /*entry*/) + CHECK(dot); + CHECK(dotDot); + } + AND_WHEN("Iterating it without dots, directory it empty") { - failed = true; - }); + virtualDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& /*entry*/) + { + FAIL("There should be nothing here, got " << name); + }); + } + AND_WHEN("We try to retrieve a non-existing file, it fails") + { + CHECK_FALSE(virtualDir->GetEntry("File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/) + { + return true; + })); + + CHECK_FALSE(virtualDir->GetEntry("Foo/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/) + { + return true; + })); + + CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/) + { + return true; + })); - CHECK_FALSE(failed); - } - AND_WHEN("We try to retrieve a file, it fails") - { - Nz::VirtualDirectory::Entry entry; - CHECK_FALSE(virtualDir->GetEntry("File.bin", &entry)); - CHECK_FALSE(virtualDir->GetEntry("Foo/File.bin", &entry)); - CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", &entry)); + virtualDir->StoreDirectory("Foo/Bar", std::make_shared()); - virtualDir->StoreDirectory("Foo/Bar", std::make_shared()); - - CHECK(virtualDir->GetEntry("Foo", &entry)); - CHECK(std::holds_alternative(entry)); - CHECK(virtualDir->GetEntry("Foo/Bar", &entry)); - CHECK(std::holds_alternative(entry)); - CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", &entry)); - } - - WHEN("Storing a file") - { - std::mt19937 randGen(std::random_device{}()); - std::vector randomData; - for (std::size_t i = 0; i < 1024; ++i) - { - unsigned int data = randGen(); - randomData.push_back((data & 0x000000FF) >> 0); - randomData.push_back((data & 0x0000FF00) >> 8); - randomData.push_back((data & 0x00FF0000) >> 16); - randomData.push_back((data & 0xFF000000) >> 24); + CHECK(virtualDir->GetEntry("Foo", [](const Nz::VirtualDirectory::Entry& entry) + { + return std::holds_alternative(entry); + })); + + CHECK(virtualDir->GetEntry("Foo/Bar", [](const Nz::VirtualDirectory::Entry& entry) + { + return std::holds_alternative(entry); + })); + + CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/) + { + return true; + })); } - virtualDir->StoreFile("File.bin", randomData); - - WHEN("We retrieve it") + WHEN("Storing a file") { - Nz::VirtualDirectory::Entry entry; - REQUIRE(virtualDir->GetEntry("File.bin", &entry)); + std::mt19937 randGen(std::random_device{}()); + std::vector randomData; + for (std::size_t i = 0; i < 1024; ++i) + { + unsigned int data = randGen(); + randomData.push_back(Nz::SafeCast((data & 0x000000FF) >> 0)); + randomData.push_back(Nz::SafeCast((data & 0x0000FF00) >> 8)); + randomData.push_back(Nz::SafeCast((data & 0x00FF0000) >> 16)); + randomData.push_back(Nz::SafeCast((data & 0xFF000000) >> 24)); + } - REQUIRE(std::holds_alternative(entry)); - const auto& contentEntry = std::get(entry); - CHECK(std::equal(randomData.begin(), randomData.end(), contentEntry.data->begin(), contentEntry.data->end())); + virtualDir->StoreFile("File.bin", randomData); + + WHEN("We retrieve it") + { + CHECK(virtualDir->GetEntry("File.bin", [&](const Nz::VirtualDirectory::Entry& entry) + { + if (!std::holds_alternative(entry)) + { + FAIL("Target is not a file"); + return false; + } + + const auto& contentEntry = std::get(entry); + CHECK(std::equal(randomData.begin(), randomData.end(), contentEntry.data.begin(), contentEntry.data.end())); + return true; + })); + } + } + } + + SECTION("Accessing filesystem using a VirtualDirectory") + { + std::shared_ptr virtualDir = std::make_shared(GetResourceDir()); + + WHEN("Iterating, it's not empty") + { + bool empty = true; + virtualDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& entry) + { + CHECK_FALSE(name == "."); + CHECK_FALSE(name == ".."); + + INFO("Only physical files and directories are expected"); + CHECK((std::holds_alternative(entry) || std::holds_alternative(entry))); + empty = false; + }); + + REQUIRE_FALSE(empty); + } + + auto CheckFileHash = [&](const char* filepath, const char* expectedHash) + { + return virtualDir->GetEntry(filepath, [&](const Nz::VirtualDirectory::Entry& entry) + { + REQUIRE(std::holds_alternative(entry)); + + const auto& physFileEntry = std::get(entry); + + Nz::File file(physFileEntry.filePath); + + Nz::SHA256Hash hash; + WHEN("We compute " << hash.GetHashName() << " of " << physFileEntry.filePath << " file") + { + CHECK(Nz::ToUpper(Nz::ComputeHash(hash, file).ToHex()) == expectedHash); + } + return true; + }); + }; + + WHEN("Accessing files") + { + CHECK(CheckFileHash("Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423")); + CHECK(CheckFileHash("./Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423")); + CHECK(CheckFileHash("Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + CHECK(CheckFileHash("Engine/Audio/./The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + CHECK_FALSE(CheckFileHash("The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + CHECK_FALSE(CheckFileHash("Engine/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + CHECK_FALSE(CheckFileHash("Engine/Audio/../The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + + // We can't escape the virtual directory + CHECK(CheckFileHash("../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423")); + CHECK(CheckFileHash("../../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423")); + CHECK(CheckFileHash("Engine/../Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + CHECK(CheckFileHash("../Engine/./Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + CHECK_FALSE(CheckFileHash("../Engine/Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738")); + + auto CheckOurselves = [&](const auto& entry) + { + REQUIRE(std::holds_alternative(entry)); + const auto& dirEntry = std::get(entry); + return dirEntry.directory == virtualDir; + }; + + CHECK(virtualDir->GetEntry("..", CheckOurselves)); + CHECK(virtualDir->GetEntry("../..", CheckOurselves)); + CHECK(virtualDir->GetEntry("./..", CheckOurselves)); + CHECK(virtualDir->GetEntry("./..", CheckOurselves)); + CHECK(virtualDir->GetEntry("Engine/../..", CheckOurselves)); + CHECK(virtualDir->GetEntry("Engine/../Engine/Audio/../../..", CheckOurselves)); + } + AND_THEN("Overriding the physical file with another one") + { + virtualDir->StoreFile("Logo.png", GetResourceDir() / "ambience.ogg"); + CHECK(CheckFileHash("ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94")); + CHECK(CheckFileHash("Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94")); } } }