Core/VirtualDirectory: Fix some bugs and add more tests

This commit is contained in:
Jérôme Leclercq 2022-03-11 20:27:08 +01:00
parent bfaa428b39
commit 3d4271706a
3 changed files with 122 additions and 36 deletions

View File

@ -39,6 +39,8 @@ namespace Nz
VirtualDirectory(VirtualDirectory&&) = delete;
~VirtualDirectory() = default;
bool Exists(std::string_view path);
template<typename F> void Foreach(F&& callback, bool includeDots = false);
template<typename F> bool GetEntry(std::string_view path, F&& callback);
@ -80,7 +82,7 @@ namespace Nz
private:
template<typename F> bool GetEntryInternal(std::string_view name, F&& callback);
inline bool RetrieveDirectory(std::string_view path, bool allowCreation, std::shared_ptr<VirtualDirectory>& directory, std::string_view& entryName);
inline bool CreateOrRetrieveDirectory(std::string_view path, std::shared_ptr<VirtualDirectory>& directory, std::string_view& entryName);
template<typename T> T& StoreInternal(std::string name, T value);

View File

@ -3,6 +3,7 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Core/VirtualDirectory.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <algorithm>
#include <cassert>
#include <Nazara/Core/Debug.hpp>
@ -20,6 +21,11 @@ namespace Nz
{
}
inline bool VirtualDirectory::Exists(std::string_view path)
{
return GetEntry(path, [](const auto&) {});
}
template<typename F>
void VirtualDirectory::Foreach(F&& callback, bool includeDots)
{
@ -75,10 +81,14 @@ namespace Nz
template<typename F> bool VirtualDirectory::GetEntry(std::string_view path, F&& callback)
{
assert(!path.empty());
VirtualDirectoryPtr currentDir = shared_from_this();
std::vector<std::string> 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<VirtualDirectory> 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<VirtualDirectory> 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<UInt8> file) -> FileContentEntry&
{
assert(!path.empty());
std::shared_ptr<VirtualDirectory> 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<VirtualDirectory> 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<VirtualDirectory> 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<VirtualDirectory>& directory, std::string_view& entryName)
{
inline bool VirtualDirectory::CreateOrRetrieveDirectory(std::string_view path, std::shared_ptr<VirtualDirectory>& 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<DirectoryEntry>(&entry))
@ -274,20 +290,26 @@ namespace Nz
},
[&](std::string_view name)
{
if (name.empty())
return false;
entryName = name;
return true;
});
}
template<typename T>
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<typename F1, typename F2>
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);
}
}

View File

@ -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<Nz::UInt8> 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<Nz::UInt8>((data & 0x000000FF) >> 0));
@ -91,22 +91,80 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0xFF000000) >> 24));
}
return randomData;
};
auto CheckFile = [&](std::string_view path, const std::vector<Nz::UInt8>& expectedData)
{
return virtualDir->GetEntry(path, [&](const Nz::VirtualDirectory::Entry& entry)
{
if (!std::holds_alternative<Nz::VirtualDirectory::FileContentEntry>(entry))
{
FAIL("Target is not a file");
return false;
}
const auto& contentEntry = std::get<Nz::VirtualDirectory::FileContentEntry>(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<Nz::VirtualDirectory::FileContentEntry>(entry))
{
FAIL("Target is not a file");
return false;
}
CHECK(CheckFile("File.bin", randomData));
}
}
const auto& contentEntry = std::get<Nz::VirtualDirectory::FileContentEntry>(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<Nz::UInt8> data;
};
std::vector<File> files;
std::unordered_map<std::string, std::size_t> 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));
}
}
}