Core: Rewrite VirtualDirectory class and add more tests
This commit is contained in:
parent
db0c1e6e8c
commit
db98b86eaf
|
|
@ -18,28 +18,33 @@
|
|||
|
||||
namespace Nz
|
||||
{
|
||||
class VirtualDirectory;
|
||||
|
||||
using VirtualDirectoryPtr = std::shared_ptr<VirtualDirectory>;
|
||||
|
||||
class VirtualDirectory : public std::enable_shared_from_this<VirtualDirectory>
|
||||
{
|
||||
public:
|
||||
struct DataPointerEntry;
|
||||
struct DirectoryEntry;
|
||||
struct FileContentEntry;
|
||||
using PhysicalFileEntry = std::filesystem::path;
|
||||
using VirtualDirectoryEntry = std::shared_ptr<VirtualDirectory>;
|
||||
struct PhysicalDirectoryEntry;
|
||||
struct PhysicalFileEntry;
|
||||
|
||||
using Entry = std::variant<DataPointerEntry, FileContentEntry, PhysicalFileEntry, VirtualDirectoryEntry>;
|
||||
using Entry = std::variant<DataPointerEntry, DirectoryEntry, FileContentEntry, PhysicalDirectoryEntry, PhysicalFileEntry>;
|
||||
|
||||
inline VirtualDirectory(VirtualDirectoryEntry parentDirectory = nullptr);
|
||||
inline VirtualDirectory(std::filesystem::path physicalPath, VirtualDirectoryEntry parentDirectory = nullptr);
|
||||
inline VirtualDirectory(std::weak_ptr<VirtualDirectory> parentDirectory = {});
|
||||
inline VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr<VirtualDirectory> parentDirectory = {});
|
||||
VirtualDirectory(const VirtualDirectory&) = delete;
|
||||
VirtualDirectory(VirtualDirectory&&) = delete;
|
||||
~VirtualDirectory() = default;
|
||||
|
||||
template<typename F> void Foreach(F&& cb, bool includeDots = false);
|
||||
template<typename F> void Foreach(F&& callback, bool includeDots = false);
|
||||
|
||||
inline bool GetEntry(std::string_view path, Entry* entry);
|
||||
template<typename F> 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<UInt8> 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<std::vector<UInt8>> data;
|
||||
std::vector<UInt8> 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<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 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<UInt8> 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<typename T> T& StoreInternal(std::string name, T value);
|
||||
|
||||
template<typename F, typename... Args> static bool CallbackReturn(F&& callback, Args&&... args);
|
||||
template<typename F1, typename F2> static bool SplitPath(std::string_view path, F1&& dirCB, F2&& fileCB);
|
||||
|
||||
std::map<std::string /*name*/, Entry, std::less<>> m_content;
|
||||
struct ContentEntry
|
||||
{
|
||||
std::string name;
|
||||
Entry entry;
|
||||
};
|
||||
|
||||
std::optional<std::filesystem::path> m_physicalPath;
|
||||
VirtualDirectoryEntry m_parent;
|
||||
bool m_wereDotRegistered;
|
||||
std::vector<ContentEntry> m_content;
|
||||
std::weak_ptr<VirtualDirectory> m_parent;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<VirtualDirectory> 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<VirtualDirectory> parentDirectory) :
|
||||
m_physicalPath(std::move(physicalPath)),
|
||||
m_parent(std::move(parentDirectory)),
|
||||
m_wereDotRegistered(false)
|
||||
m_parent(std::move(parentDirectory))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
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<PhysicalFileEntry>(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<VirtualDirectoryEntry>(std::make_shared<VirtualDirectory>(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<typename F> bool VirtualDirectory::GetEntry(std::string_view path, F&& callback)
|
||||
{
|
||||
std::shared_ptr<VirtualDirectory> dir;
|
||||
std::string_view entryName;
|
||||
if (!RetrieveDirectory(path, false, dir, entryName))
|
||||
return false;
|
||||
VirtualDirectoryPtr currentDir = shared_from_this();
|
||||
std::vector<std::string> 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<DirectoryEntry>(&entry))
|
||||
{
|
||||
currentDir = dirEntry->directory;
|
||||
return true;
|
||||
}
|
||||
else if (auto physDirEntry = std::get_if<PhysicalDirectoryEntry>(&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<VirtualDirectory> 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<VirtualDirectory> 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<UInt8> 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<typename F> 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<VirtualDirectoryEntry>(&entry))
|
||||
directory = *dir;
|
||||
if (auto dirEntry = std::get_if<DirectoryEntry>(&entry))
|
||||
directory = dirEntry->directory;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (allowCreation)
|
||||
{
|
||||
auto newDirectory = std::make_shared<VirtualDirectory>(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<VirtualDirectory>(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<typename T>
|
||||
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<T>(entryPtr->entry);
|
||||
}
|
||||
|
||||
template<typename F, typename... Args>
|
||||
bool VirtualDirectory::CallbackReturn(F&& callback, Args&& ...args)
|
||||
{
|
||||
using Ret = decltype(callback(std::forward<Args>(args)...));
|
||||
if constexpr (std::is_void_v<Ret>)
|
||||
{
|
||||
callback(std::forward<Args>(args)...);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_physicalPath)
|
||||
{
|
||||
std::filesystem::path entryPath = *m_physicalPath / name;
|
||||
|
||||
if (std::filesystem::is_regular_file(entryPath))
|
||||
entry->emplace<PhysicalFileEntry>(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<Ret, bool>, "callback must either return a boolean or nothing");
|
||||
return callback(std::forward<Args>(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<VirtualDirectory>(directoryPath, shared_from_this())).first;
|
||||
return std::get<VirtualDirectoryEntry>(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<VirtualDirectoryEntry>(it->second);
|
||||
}
|
||||
|
||||
inline auto VirtualDirectory::StoreFileInternal(std::string name, std::vector<UInt8> fileContent) -> FileContentEntry&
|
||||
{
|
||||
assert(name.find_first_of("\\/:") == name.npos);
|
||||
|
||||
FileContentEntry fileEntry;
|
||||
fileEntry.data = std::make_shared<std::vector<UInt8>>(std::move(fileContent));
|
||||
|
||||
auto it = m_content.insert_or_assign(std::move(name), std::move(fileEntry)).first;
|
||||
return std::get<FileContentEntry>(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<PhysicalFileEntry>(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<DataPointerEntry>(it->second);
|
||||
}
|
||||
|
||||
template<typename F1, typename F2>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<VirtualDirectory::DataPointerEntry>(entry))
|
||||
m_searchDirectory->GetEntry(fullPath, [&](const VirtualDirectory::Entry& entry)
|
||||
{
|
||||
const auto& content = std::get<VirtualDirectory::DataPointerEntry>(entry);
|
||||
shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast<const char*>(content.data), content.size));
|
||||
}
|
||||
else if (std::holds_alternative<VirtualDirectory::FileContentEntry>(entry))
|
||||
{
|
||||
const auto& content = std::get<VirtualDirectory::FileContentEntry>(entry);
|
||||
shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast<const char*>(content.data->data()), content.data->size()));
|
||||
}
|
||||
else if (std::holds_alternative<VirtualDirectory::PhysicalFileEntry>(entry))
|
||||
{
|
||||
const auto& filePath = std::get<VirtualDirectory::PhysicalFileEntry>(entry);
|
||||
shaderModule = ShaderLang::ParseFromFile(filePath);
|
||||
}
|
||||
if (std::holds_alternative<VirtualDirectory::DataPointerEntry>(entry))
|
||||
{
|
||||
const auto& dataContent = std::get<VirtualDirectory::DataPointerEntry>(entry);
|
||||
shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast<const char*>(dataContent.data), dataContent.size));
|
||||
}
|
||||
else if (std::holds_alternative<VirtualDirectory::FileContentEntry>(entry))
|
||||
{
|
||||
const auto& fileContent = std::get<VirtualDirectory::FileContentEntry>(entry);
|
||||
shaderModule = ShaderLang::Parse(std::string_view(reinterpret_cast<const char*>(fileContent.data.data()), fileContent.data.size()));
|
||||
}
|
||||
else if (std::holds_alternative<VirtualDirectory::PhysicalFileEntry>(entry))
|
||||
{
|
||||
const auto& physicalEntry = std::get<VirtualDirectory::PhysicalFileEntry>(entry);
|
||||
shaderModule = ShaderLang::ParseFromFile(physicalEntry.filePath);
|
||||
}
|
||||
});
|
||||
|
||||
if (!shaderModule)
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -1,71 +1,191 @@
|
|||
#include <Nazara/Core/Algorithm.hpp>
|
||||
#include <Nazara/Core/File.hpp>
|
||||
#include <Nazara/Core/StringExt.hpp>
|
||||
#include <Nazara/Core/VirtualDirectory.hpp>
|
||||
#include <Nazara/Core/Hash/SHA256.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <random>
|
||||
|
||||
std::filesystem::path GetResourceDir();
|
||||
|
||||
TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
|
||||
{
|
||||
std::shared_ptr<Nz::VirtualDirectory> virtualDir = std::make_shared<Nz::VirtualDirectory>();
|
||||
|
||||
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<Nz::VirtualDirectory> virtualDir = std::make_shared<Nz::VirtualDirectory>();
|
||||
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<Nz::VirtualDirectory>());
|
||||
|
||||
virtualDir->StoreDirectory("Foo/Bar", std::make_shared<Nz::VirtualDirectory>());
|
||||
|
||||
CHECK(virtualDir->GetEntry("Foo", &entry));
|
||||
CHECK(std::holds_alternative<Nz::VirtualDirectory::VirtualDirectoryEntry>(entry));
|
||||
CHECK(virtualDir->GetEntry("Foo/Bar", &entry));
|
||||
CHECK(std::holds_alternative<Nz::VirtualDirectory::VirtualDirectoryEntry>(entry));
|
||||
CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", &entry));
|
||||
}
|
||||
|
||||
WHEN("Storing a file")
|
||||
{
|
||||
std::mt19937 randGen(std::random_device{}());
|
||||
std::vector<Nz::UInt8> 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<Nz::VirtualDirectory::DirectoryEntry>(entry);
|
||||
}));
|
||||
|
||||
CHECK(virtualDir->GetEntry("Foo/Bar", [](const Nz::VirtualDirectory::Entry& entry)
|
||||
{
|
||||
return std::holds_alternative<Nz::VirtualDirectory::DirectoryEntry>(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<Nz::UInt8> randomData;
|
||||
for (std::size_t i = 0; i < 1024; ++i)
|
||||
{
|
||||
unsigned int data = randGen();
|
||||
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0x000000FF) >> 0));
|
||||
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0x0000FF00) >> 8));
|
||||
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0x00FF0000) >> 16));
|
||||
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0xFF000000) >> 24));
|
||||
}
|
||||
|
||||
REQUIRE(std::holds_alternative<Nz::VirtualDirectory::FileContentEntry>(entry));
|
||||
const auto& contentEntry = std::get<Nz::VirtualDirectory::FileContentEntry>(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<Nz::VirtualDirectory::FileContentEntry>(entry))
|
||||
{
|
||||
FAIL("Target is not a file");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& contentEntry = std::get<Nz::VirtualDirectory::FileContentEntry>(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<Nz::VirtualDirectory> virtualDir = std::make_shared<Nz::VirtualDirectory>(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<Nz::VirtualDirectory::PhysicalDirectoryEntry>(entry) || std::holds_alternative<Nz::VirtualDirectory::PhysicalFileEntry>(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<Nz::VirtualDirectory::PhysicalFileEntry>(entry));
|
||||
|
||||
const auto& physFileEntry = std::get<Nz::VirtualDirectory::PhysicalFileEntry>(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<Nz::VirtualDirectory::DirectoryEntry>(entry));
|
||||
const auto& dirEntry = std::get<Nz::VirtualDirectory::DirectoryEntry>(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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue