diff --git a/include/Nazara/Core/VirtualDirectory.hpp b/include/Nazara/Core/VirtualDirectory.hpp index dd2240e16..6d9fa25ee 100644 --- a/include/Nazara/Core/VirtualDirectory.hpp +++ b/include/Nazara/Core/VirtualDirectory.hpp @@ -39,6 +39,8 @@ namespace Nz VirtualDirectory(VirtualDirectory&&) = delete; ~VirtualDirectory() = default; + bool Exists(std::string_view path); + template void Foreach(F&& callback, bool includeDots = false); template bool GetEntry(std::string_view path, F&& callback); @@ -80,7 +82,7 @@ namespace Nz private: 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 bool CreateOrRetrieveDirectory(std::string_view path, std::shared_ptr& directory, std::string_view& entryName); template T& StoreInternal(std::string name, T value); diff --git a/include/Nazara/Core/VirtualDirectory.inl b/include/Nazara/Core/VirtualDirectory.inl index 743537164..cede180cb 100644 --- a/include/Nazara/Core/VirtualDirectory.inl +++ b/include/Nazara/Core/VirtualDirectory.inl @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include #include #include @@ -20,6 +21,11 @@ namespace Nz { } + inline bool VirtualDirectory::Exists(std::string_view path) + { + return GetEntry(path, [](const auto&) {}); + } + template void VirtualDirectory::Foreach(F&& callback, bool includeDots) { @@ -75,10 +81,14 @@ namespace Nz template bool VirtualDirectory::GetEntry(std::string_view path, F&& callback) { + assert(!path.empty()); + VirtualDirectoryPtr currentDir = shared_from_this(); std::vector physicalDirectoryParts; return SplitPath(path, [&](std::string_view dirName) { + assert(!dirName.empty()); + if (!physicalDirectoryParts.empty()) { // Special case when traversing directory @@ -115,13 +125,6 @@ namespace Nz { 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) @@ -148,9 +151,11 @@ namespace Nz inline auto VirtualDirectory::StoreDirectory(std::string_view path, VirtualDirectoryPtr directory) -> DirectoryEntry& { + assert(!path.empty()); + std::shared_ptr dir; std::string_view entryName; - if (!RetrieveDirectory(path, true, dir, entryName)) + if (!CreateOrRetrieveDirectory(path, dir, entryName)) throw std::runtime_error("invalid path"); return dir->StoreInternal(std::string(entryName), DirectoryEntry{ std::move(directory) }); @@ -158,9 +163,11 @@ namespace Nz inline auto VirtualDirectory::StoreDirectory(std::string_view path, std::filesystem::path directoryPath) -> PhysicalDirectoryEntry& { + assert(!path.empty()); + std::shared_ptr dir; std::string_view entryName; - if (!RetrieveDirectory(path, true, dir, entryName)) + if (!CreateOrRetrieveDirectory(path, dir, entryName)) throw std::runtime_error("invalid path"); return dir->StoreInternal(std::string(entryName), PhysicalDirectoryEntry{ std::move(directoryPath) }); @@ -168,9 +175,11 @@ namespace Nz inline auto VirtualDirectory::StoreFile(std::string_view path, std::vector file) -> FileContentEntry& { + assert(!path.empty()); + std::shared_ptr dir; std::string_view entryName; - if (!RetrieveDirectory(path, true, dir, entryName)) + if (!CreateOrRetrieveDirectory(path, dir, entryName)) throw std::runtime_error("invalid path"); return dir->StoreInternal(std::string(entryName), FileContentEntry{ std::move(file) }); @@ -178,9 +187,11 @@ namespace Nz inline auto VirtualDirectory::StoreFile(std::string_view path, std::filesystem::path filePath) -> PhysicalFileEntry& { + assert(!path.empty()); + std::shared_ptr dir; std::string_view entryName; - if (!RetrieveDirectory(path, true, dir, entryName)) + if (!CreateOrRetrieveDirectory(path, dir, entryName)) throw std::runtime_error("invalid path"); return dir->StoreInternal(std::string(entryName), PhysicalFileEntry{ std::move(filePath) }); @@ -188,9 +199,11 @@ namespace Nz inline auto VirtualDirectory::StoreFile(std::string_view path, const void* data, std::size_t size) -> DataPointerEntry& { + assert(!path.empty()); + std::shared_ptr dir; std::string_view entryName; - if (!RetrieveDirectory(path, true, dir, entryName)) + if (!CreateOrRetrieveDirectory(path, dir, entryName)) throw std::runtime_error("invalid path"); return dir->StoreInternal(std::string(entryName), DataPointerEntry{ data, size }); @@ -245,12 +258,15 @@ namespace Nz } } - inline bool VirtualDirectory::RetrieveDirectory(std::string_view path, bool allowCreation, std::shared_ptr& directory, std::string_view& entryName) - { + inline bool VirtualDirectory::CreateOrRetrieveDirectory(std::string_view path, std::shared_ptr& directory, std::string_view& entryName) +{ directory = shared_from_this(); + bool allowCreation = true; return SplitPath(path, [&](std::string_view dirName) { + assert(!dirName.empty()); + bool dirFound = directory->GetEntryInternal(dirName, [&](const Entry& entry) { if (auto dirEntry = std::get_if(&entry)) @@ -274,20 +290,26 @@ namespace Nz }, [&](std::string_view name) { + if (name.empty()) + return false; + entryName = name; + return true; }); } template T& VirtualDirectory::StoreInternal(std::string name, T value) { + assert(!name.empty()); + auto it = std::lower_bound(m_content.begin(), m_content.end(), name, [](const ContentEntry& entry, std::string_view name) { return entry.name < name; }); ContentEntry* entryPtr; - if (it == m_content.end() || it->name == name) + if (it == m_content.end() || it->name != name) entryPtr = &*m_content.emplace(it); else entryPtr = &*it; @@ -317,16 +339,20 @@ namespace Nz template bool VirtualDirectory::SplitPath(std::string_view path, F1&& dirCB, F2&& lastCB) { - std::size_t pos; - while ((pos = path.find_first_of("\\/:")) != std::string::npos) + std::string_view nextPart; + auto HandlePart = [&](std::string_view part) { - if (!dirCB(path.substr(0, pos))) + if (part.empty()) + return true; //< "a//b" == "a/b" + + if (!nextPart.empty() && !CallbackReturn(dirCB, nextPart)) return false; - path = path.substr(pos + 1); - } + nextPart = part; + return true; + }; - return CallbackReturn(lastCB, path); + return SplitStringAny(path, R"(\/:)", HandlePart) && CallbackReturn(lastCB, nextPart); } } diff --git a/tests/Engine/Core/VirtualDirectoryTest.cpp b/tests/Engine/Core/VirtualDirectoryTest.cpp index eead3ba9a..04879f4af 100644 --- a/tests/Engine/Core/VirtualDirectoryTest.cpp +++ b/tests/Engine/Core/VirtualDirectoryTest.cpp @@ -78,11 +78,11 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") })); } - WHEN("Storing a file") + std::mt19937 randGen(std::random_device{}()); + auto GenerateRandomData = [&] { - std::mt19937 randGen(std::random_device{}()); std::vector randomData; - for (std::size_t i = 0; i < 1024; ++i) + for (std::size_t i = 0; i < 256; ++i) { unsigned int data = randGen(); randomData.push_back(Nz::SafeCast((data & 0x000000FF) >> 0)); @@ -91,22 +91,80 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") randomData.push_back(Nz::SafeCast((data & 0xFF000000) >> 24)); } + return randomData; + }; + + auto CheckFile = [&](std::string_view path, const std::vector& expectedData) + { + return virtualDir->GetEntry(path, [&](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(expectedData.begin(), expectedData.end(), contentEntry.data.begin(), contentEntry.data.end())); + return true; + }); + }; + + WHEN("Storing a file") + { + auto randomData = GenerateRandomData(); 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; - } + CHECK(CheckFile("File.bin", randomData)); + } + } - const auto& contentEntry = std::get(entry); - CHECK(std::equal(randomData.begin(), randomData.end(), contentEntry.data.begin(), contentEntry.data.end())); - return true; - })); + WHEN("Storing multiples files") + { + std::array paths = { + "Abc", + "Ab/cd", + "po/mme\\de/terre.o", + "Nazara", + "Engine.exe", + "Un/Deux/Trois", + "Gnocchi.fromage", + "Karmeliet.triple", + "Mogwai.gremlins" + }; + + struct File + { + std::string path; + std::vector data; + }; + + std::vector files; + std::unordered_map filePathToIndex; + + for (const char* path : paths) + { + auto& file = files.emplace_back(); + file.data = GenerateRandomData(); + file.path = path; + + filePathToIndex[file.path] = files.size() - 1; + } + + // Insert files into the virtual directory + for (const File& file : files) + { + INFO("Storing " << file.path); + CHECK_NOTHROW(virtualDir->StoreFile(file.path, file.data)); + } + + // Try to retrieve them + for (const File& file : files) + { + INFO("Retrieving " << file.path); + CHECK(CheckFile(file.path, file.data)); } } }