From 36dd24556425291084fe14af452a5672ff95dc3e Mon Sep 17 00:00:00 2001 From: SirLynix Date: Fri, 3 Mar 2023 13:21:48 +0100 Subject: [PATCH] Core: Rework VirtualDirectory to allow custom directory resolving --- .../Nazara/Core/AppFilesystemComponent.hpp | 6 +- .../Nazara/Core/AppFilesystemComponent.inl | 52 +--- include/Nazara/Core/VirtualDirectory.hpp | 71 +++-- include/Nazara/Core/VirtualDirectory.inl | 277 +++++++++--------- .../VirtualDirectoryFilesystemResolver.hpp | 40 +++ .../VirtualDirectoryFilesystemResolver.inl | 17 ++ src/Nazara/Core/AppFilesystemComponent.cpp | 24 ++ src/Nazara/Core/VirtualDirectory.cpp | 11 + .../VirtualDirectoryFilesystemResolver.cpp | 53 ++++ .../Engine/Core/VirtualDirectoryTest.cpp | 58 ++-- 10 files changed, 346 insertions(+), 263 deletions(-) create mode 100644 include/Nazara/Core/VirtualDirectoryFilesystemResolver.hpp create mode 100644 include/Nazara/Core/VirtualDirectoryFilesystemResolver.inl create mode 100644 src/Nazara/Core/VirtualDirectory.cpp create mode 100644 src/Nazara/Core/VirtualDirectoryFilesystemResolver.cpp diff --git a/include/Nazara/Core/AppFilesystemComponent.hpp b/include/Nazara/Core/AppFilesystemComponent.hpp index f86a01f9d..cb070d70f 100644 --- a/include/Nazara/Core/AppFilesystemComponent.hpp +++ b/include/Nazara/Core/AppFilesystemComponent.hpp @@ -40,10 +40,10 @@ namespace Nz template std::shared_ptr Load(std::string_view assetPath); template std::shared_ptr Load(std::string_view assetPath, typename T::Params params); - inline const VirtualDirectoryPtr& Mount(std::string_view name, std::filesystem::path filepath); - inline const VirtualDirectoryPtr& Mount(std::string_view name, VirtualDirectoryPtr directory); + const VirtualDirectoryPtr& Mount(std::string_view name, std::filesystem::path filepath); + const VirtualDirectoryPtr& Mount(std::string_view name, VirtualDirectoryPtr directory); - inline void MountDefaultDirectories(); + void MountDefaultDirectories(); template std::shared_ptr Open(std::string_view assetPath); template std::shared_ptr Open(std::string_view assetPath, typename T::Params params); diff --git a/include/Nazara/Core/AppFilesystemComponent.inl b/include/Nazara/Core/AppFilesystemComponent.inl index f0996898d..174be223c 100644 --- a/include/Nazara/Core/AppFilesystemComponent.inl +++ b/include/Nazara/Core/AppFilesystemComponent.inl @@ -53,30 +53,6 @@ namespace Nz return LoadImpl(assetPath, params); } - inline const VirtualDirectoryPtr& AppFilesystemComponent::Mount(std::string_view name, std::filesystem::path filepath) - { - return Mount(name, std::make_shared(std::move(filepath))); - } - - inline const VirtualDirectoryPtr& AppFilesystemComponent::Mount(std::string_view name, VirtualDirectoryPtr directory) - { - if (name.empty()) - { - m_rootDirectory = std::move(directory); - return m_rootDirectory; - } - - if (!m_rootDirectory) - m_rootDirectory = std::make_shared(); - - return m_rootDirectory->StoreDirectory(name, std::move(directory)).directory; - } - - inline void AppFilesystemComponent::MountDefaultDirectories() - { - m_rootDirectory = std::make_shared(std::filesystem::current_path()); - } - template std::shared_ptr AppFilesystemComponent::Open(std::string_view assetPath) { @@ -154,19 +130,9 @@ namespace Nz NazaraError(std::string(assetPath) + " is a directory"); return false; } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - resource = T::LoadFromMemory(arg.data, arg.size, params); - return true; - } - else if constexpr (std::is_same_v) - { - resource = T::LoadFromMemory(&arg.data[0], arg.data.size(), params); - return true; - } - else if constexpr (std::is_same_v) - { - resource = T::LoadFromFile(arg.filePath, params); + resource = T::LoadFromStream(*arg.stream, params); return true; } else @@ -195,19 +161,9 @@ namespace Nz NazaraError(std::string(assetPath) + " is a directory"); return false; } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - resource = T::OpenFromMemory(arg.data, arg.size, params); - return true; - } - else if constexpr (std::is_same_v) - { - resource = T::OpenFromMemory(&arg.data[0], arg.data.size(), params); - return true; - } - else if constexpr (std::is_same_v) - { - resource = T::OpenFromFile(arg.filePath, params); + resource = T::OpenFromStream(*arg.stream, params); return true; } else diff --git a/include/Nazara/Core/VirtualDirectory.hpp b/include/Nazara/Core/VirtualDirectory.hpp index 129432c3e..599379675 100644 --- a/include/Nazara/Core/VirtualDirectory.hpp +++ b/include/Nazara/Core/VirtualDirectory.hpp @@ -8,8 +8,8 @@ #define NAZARA_CORE_VIRTUALDIRECTORY_HPP #include -#include -#include +#include +#include #include #include #include @@ -19,23 +19,20 @@ namespace Nz { class VirtualDirectory; + class VirtualDirectoryResolver; using VirtualDirectoryPtr = std::shared_ptr; class VirtualDirectory : public std::enable_shared_from_this { public: - struct DataPointerEntry; struct DirectoryEntry; - struct FileContentEntry; - struct PhysicalDirectoryEntry; - struct PhysicalFileEntry; - struct VirtualDirectoryEntry; + struct FileEntry; - using Entry = std::variant; + using Entry = std::variant; inline VirtualDirectory(std::weak_ptr parentDirectory = {}); - inline VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr parentDirectory = {}); + inline VirtualDirectory(std::shared_ptr resolver, std::weak_ptr parentDirectory = {}); VirtualDirectory(const VirtualDirectory&) = delete; VirtualDirectory(VirtualDirectory&&) = delete; ~VirtualDirectory() = default; @@ -49,33 +46,23 @@ namespace Nz template bool GetDirectoryEntry(std::string_view path, F&& callback); template bool GetEntry(std::string_view path, F&& callback); template bool GetFileContent(std::string_view path, F&& callback); + inline const std::shared_ptr& GetResolver() const; inline bool IsUprootAllowed() const; - inline VirtualDirectoryEntry& 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); + inline DirectoryEntry& StoreDirectory(std::string_view path, std::shared_ptr resolver); + inline DirectoryEntry& StoreDirectory(std::string_view path, VirtualDirectoryPtr directory); + inline FileEntry& StoreFile(std::string_view path, std::shared_ptr stream); + inline FileEntry& StoreFile(std::string_view path, ByteArray content); + inline FileEntry& StoreFile(std::string_view path, const void* data, std::size_t size); VirtualDirectory& operator=(const VirtualDirectory&) = delete; VirtualDirectory& operator=(VirtualDirectory&&) = delete; // File entries - struct DataPointerEntry + struct FileEntry { - const void* data; - std::size_t size; - }; - - struct FileContentEntry - { - std::vector data; - }; - - struct PhysicalFileEntry - { - std::filesystem::path filePath; + std::shared_ptr stream; }; // Directory entries @@ -84,17 +71,8 @@ namespace Nz VirtualDirectoryPtr directory; }; - struct PhysicalDirectoryEntry : DirectoryEntry - { - std::filesystem::path filePath; - }; - - struct VirtualDirectoryEntry : DirectoryEntry - { - }; - private: - template bool GetEntryInternal(std::string_view name, F&& callback); + template bool GetEntryInternal(std::string_view name, bool allowResolve, F&& callback); inline bool CreateOrRetrieveDirectory(std::string_view path, std::shared_ptr& directory, std::string_view& entryName); template T& StoreInternal(std::string name, T value); @@ -108,11 +86,28 @@ namespace Nz Entry entry; }; - std::optional m_physicalPath; + std::shared_ptr m_resolver; + std::vector m_cachedDirectoryParts; std::vector m_content; std::weak_ptr m_parent; bool m_isUprootAllowed; }; + + class NAZARA_CORE_API VirtualDirectoryResolver + { + public: + VirtualDirectoryResolver() = default; + VirtualDirectoryResolver(const VirtualDirectoryResolver&) = delete; + VirtualDirectoryResolver(VirtualDirectoryResolver&&) = delete; + virtual ~VirtualDirectoryResolver(); + + virtual void ForEach(std::weak_ptr parent, FunctionRef callback) const = 0; + + virtual std::optional Resolve(std::weak_ptr parent, const std::string_view* parts, std::size_t partCount) const = 0; + + VirtualDirectoryResolver& operator=(const VirtualDirectoryResolver&) = delete; + VirtualDirectoryResolver& operator=(VirtualDirectoryResolver&&) = delete; + }; } #include diff --git a/include/Nazara/Core/VirtualDirectory.inl b/include/Nazara/Core/VirtualDirectory.inl index 4af786d5b..873bb42c5 100644 --- a/include/Nazara/Core/VirtualDirectory.inl +++ b/include/Nazara/Core/VirtualDirectory.inl @@ -4,7 +4,10 @@ #include #include +#include +#include #include +#include #include #include #include @@ -17,8 +20,8 @@ namespace Nz { } - inline VirtualDirectory::VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr parentDirectory) : - m_physicalPath(std::move(physicalPath)), + inline VirtualDirectory::VirtualDirectory(std::shared_ptr resolver, std::weak_ptr parentDirectory) : + m_resolver(std::move(resolver)), m_parent(std::move(parentDirectory)), m_isUprootAllowed(false) { @@ -39,11 +42,11 @@ namespace Nz { if (includeDots) { - Entry ourselves = VirtualDirectoryEntry{ shared_from_this() }; + Entry ourselves = DirectoryEntry{ shared_from_this() }; callback(std::string_view("."), ourselves); if (VirtualDirectoryPtr parent = m_parent.lock()) { - Entry parentEntry = VirtualDirectoryEntry{ { parent } }; + Entry parentEntry = DirectoryEntry{ { parent } }; if (!CallbackReturn(callback, std::string_view(".."), parentEntry)) return; } @@ -57,36 +60,20 @@ namespace Nz return; } - if (m_physicalPath) + if (m_resolver) { - for (auto&& physicalEntry : std::filesystem::directory_iterator(*m_physicalPath)) + m_resolver->ForEach(weak_from_this(), [&](std::string_view filename, Entry&& entry) { - std::string filename = physicalEntry.path().filename().generic_u8string(); - - // Check if physical file/directory has been overridden by a virtual one + // Check if this 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) { return entry.name < name; }); if (it != m_content.end() && it->name == filename) - continue; + return true; //< this was already returned - 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)) - { - VirtualDirectoryPtr virtualDir = std::make_shared(physicalEntry.path(), weak_from_this()); - entry = PhysicalDirectoryEntry{ { std::move(virtualDir) }, physicalEntry.path() }; - } - else - continue; - - if (!CallbackReturn(callback, std::string_view(filename), entry)) - return; - } + return CallbackReturn(callback, filename, std::move(entry)); + }); } } @@ -99,11 +86,11 @@ namespace Nz { using T = std::decay_t; - if constexpr (std::is_same_v || std::is_same_v) + if constexpr (std::is_same_v) { return CallbackReturn(callback, static_cast(entry)); } - else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + else if constexpr (std::is_same_v) { NazaraError("entry is a file"); return false; @@ -119,75 +106,70 @@ namespace Nz assert(!path.empty()); VirtualDirectoryPtr currentDir = shared_from_this(); - std::optional physicalPathBase; - std::vector physicalDirectoryParts; + m_cachedDirectoryParts.clear(); return SplitPath(path, [&](std::string_view dirName) { assert(!dirName.empty()); - if (physicalPathBase) + // If we have directory parts, we're access this path across a resolver + if (!m_cachedDirectoryParts.empty()) { // Special case when traversing directory - if (dirName == ".." && !m_isUprootAllowed) + if (dirName == ".." && !currentDir->IsUprootAllowed()) { // Don't allow to escape virtual directory - if (!physicalDirectoryParts.empty()) - physicalDirectoryParts.pop_back(); - else - physicalPathBase.reset(); + if (!m_cachedDirectoryParts.empty()) + m_cachedDirectoryParts.pop_back(); } else if (dirName != ".") - physicalDirectoryParts.emplace_back(dirName); + m_cachedDirectoryParts.emplace_back(dirName); return true; } - return currentDir->GetEntryInternal(dirName, [&](const Entry& entry) + bool isFile = false; + bool foundDir = currentDir->GetEntryInternal(dirName, false, [&](const Entry& entry) { - if (auto dirEntry = std::get_if(&entry)) + if (auto dirEntry = std::get_if(&entry)) { currentDir = dirEntry->directory; return true; } - else if (auto physDirEntry = std::get_if(&entry)) - { - assert(!physicalPathBase); - - // We're traversing a physical directory - physicalPathBase = physDirEntry->filePath; - return true; - } + // not a directory + isFile = true; return false; }); + + if (foundDir) + return true; + + if (!isFile && m_resolver) + { + assert(m_cachedDirectoryParts.empty()); + m_cachedDirectoryParts.push_back(dirName); + return true; + } + + return false; }, [&](std::string_view name) { - if (physicalPathBase) + if (!m_cachedDirectoryParts.empty()) { - std::filesystem::path filePath = *physicalPathBase; - 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)) + if (const auto& resolver = currentDir->GetResolver()) { - VirtualDirectoryPtr virtualDir = std::make_shared(filePath, weak_from_this()); - entry = PhysicalDirectoryEntry{ { std::move(virtualDir) }, std::move(filePath) }; - } - else - return false; //< either not known or of a special type + m_cachedDirectoryParts.push_back(name); - return CallbackReturn(callback, entry); + std::optional entryOpt = m_resolver->Resolve({}, m_cachedDirectoryParts.data(), m_cachedDirectoryParts.size()); + if (!entryOpt) + return false; + + return CallbackReturn(callback, *std::move(entryOpt)); + } } - else - return currentDir->GetEntryInternal(name, callback); + + return currentDir->GetEntryInternal(name, true, callback); }); } @@ -200,26 +182,65 @@ namespace Nz { using T = std::decay_t; - using P1 = const void*; - using P2 = std::size_t; + if constexpr (std::is_same_v) + { + Stream& stream = *entry.stream; - if constexpr (std::is_same_v) - { - return CallbackReturn(callback, static_cast(entry.data), SafeCast(entry.size)); - } - else if constexpr (std::is_same_v) - { - return CallbackReturn(callback, static_cast(entry.data.data()), SafeCast(entry.data.size())); - } - else if constexpr (std::is_same_v) - { - std::optional> source = File::ReadWhole(entry.filePath); - if (!source.has_value()) - return false; + if (stream.IsMemoryMapped()) + { + const void* ptr = stream.GetMappedPointer(); + UInt64 size = stream.GetSize(); + if (ptr && size > 0) + return CallbackReturn(callback, ptr, size); + } - return CallbackReturn(callback, static_cast(source->data()), SafeCast(source->size())); + // Save and restore cursor position after the call + std::size_t cursorPos = stream.GetCursorPos(); + CallOnExit restoreCursorPos([&] { stream.SetCursorPos(cursorPos); }); + + stream.SetCursorPos(0); + + UInt64 fileSize = stream.GetSize(); + + std::vector data; + + // Remember fileSize may be zero if file size isn't known in advance + if (fileSize > 0) + { + data.resize(fileSize); + if (stream.Read(&data[0], fileSize) != fileSize) + { + NazaraError("failed to read from stream"); + return false; + } + } + else + { + constexpr std::size_t blockSize = 4 * 1024; + + while (!stream.EndOfStream()) + { + std::size_t offset = data.size(); + data.resize(offset + blockSize); + if (std::size_t readSize = stream.Read(&data[offset], blockSize); readSize != blockSize) + { + if (stream.EndOfStream()) + { + data.resize(offset + readSize); + break; + } + else + { + NazaraError("failed to read from stream"); + return false; + } + } + } + } + + return CallbackReturn(callback, static_cast(&data[0]), UInt64(data.size())); } - else if constexpr (std::is_same_v || std::is_same_v) + else if constexpr (std::is_same_v) { NazaraError("entry is a directory"); return false; @@ -230,12 +251,17 @@ namespace Nz }); } + inline const std::shared_ptr& VirtualDirectory::GetResolver() const + { + return m_resolver; + } + inline bool VirtualDirectory::IsUprootAllowed() const { return m_isUprootAllowed; } - - inline auto VirtualDirectory::StoreDirectory(std::string_view path, VirtualDirectoryPtr directory) -> VirtualDirectoryEntry& + + inline auto VirtualDirectory::StoreDirectory(std::string_view path, std::shared_ptr resolver) -> DirectoryEntry& { assert(!path.empty()); @@ -247,29 +273,13 @@ namespace Nz if (entryName == "." || entryName == "..") throw std::runtime_error("invalid entry name"); - return dir->StoreInternal(std::string(entryName), VirtualDirectoryEntry{ { std::move(directory) } }); - } - - 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 (!CreateOrRetrieveDirectory(path, dir, entryName)) - throw std::runtime_error("invalid path"); - - if (entryName == "." || entryName == "..") - throw std::runtime_error("invalid entry name"); - - PhysicalDirectoryEntry entry; - entry.directory = std::make_shared(directoryPath, dir); - entry.filePath = std::move(directoryPath); + DirectoryEntry entry; + entry.directory = std::make_shared(std::move(resolver), dir); return dir->StoreInternal(std::string(entryName), std::move(entry)); } - inline auto VirtualDirectory::StoreFile(std::string_view path, std::vector file) -> FileContentEntry& + inline auto VirtualDirectory::StoreDirectory(std::string_view path, VirtualDirectoryPtr directory) -> DirectoryEntry& { assert(!path.empty()); @@ -281,10 +291,10 @@ namespace Nz if (entryName == "." || entryName == "..") throw std::runtime_error("invalid entry name"); - return dir->StoreInternal(std::string(entryName), FileContentEntry{ std::move(file) }); + return dir->StoreInternal(std::string(entryName), DirectoryEntry{ { std::move(directory) } }); } - inline auto VirtualDirectory::StoreFile(std::string_view path, std::filesystem::path filePath) -> PhysicalFileEntry& + inline auto VirtualDirectory::StoreFile(std::string_view path, std::shared_ptr stream) -> FileEntry& { assert(!path.empty()); @@ -296,29 +306,24 @@ namespace Nz if (entryName == "." || entryName == "..") throw std::runtime_error("invalid entry name"); - return dir->StoreInternal(std::string(entryName), PhysicalFileEntry{ std::move(filePath) }); + return dir->StoreInternal(std::string(entryName), FileEntry{ std::move(stream) }); } - inline auto VirtualDirectory::StoreFile(std::string_view path, const void* data, std::size_t size) -> DataPointerEntry& + inline auto VirtualDirectory::StoreFile(std::string_view path, ByteArray content) -> FileEntry& { - assert(!path.empty()); + return StoreFile(path, std::make_shared(std::move(content))); + } - std::shared_ptr dir; - std::string_view entryName; - if (!CreateOrRetrieveDirectory(path, dir, entryName)) - throw std::runtime_error("invalid path"); - - if (entryName == "." || entryName == "..") - throw std::runtime_error("invalid entry name"); - - return dir->StoreInternal(std::string(entryName), DataPointerEntry{ data, size }); + inline auto VirtualDirectory::StoreFile(std::string_view path, const void* data, std::size_t size) -> FileEntry& + { + return StoreFile(path, std::make_shared(data, size)); } - template bool VirtualDirectory::GetEntryInternal(std::string_view name, F&& callback) + template bool VirtualDirectory::GetEntryInternal(std::string_view name, bool allowResolve, F&& callback) { if (name == ".") { - Entry entry{ VirtualDirectoryEntry{ { shared_from_this() } } }; + Entry entry{ DirectoryEntry{ { shared_from_this() } } }; return CallbackReturn(callback, entry); } @@ -332,7 +337,7 @@ namespace Nz if (parentEntry) { - Entry entry = VirtualDirectoryEntry{ { std::move(parentEntry) } }; + Entry entry = DirectoryEntry{ { std::move(parentEntry) } }; return CallbackReturn(callback, entry); } } @@ -343,24 +348,14 @@ namespace Nz }); if (it == m_content.end() || it->name != name) { - // Virtual file not found, check if it has a physical one - if (m_physicalPath) + // Virtual file not found, check if it the custom resolver knows about it + if (m_resolver && allowResolve) { - 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) + std::optional entryOpt = m_resolver->Resolve(weak_from_this(), &name, 1); + if (!entryOpt) + return false; - Entry entry; - if (std::filesystem::is_regular_file(status)) - entry = PhysicalFileEntry{ std::move(filePath) }; - else if (std::filesystem::is_directory(status)) - { - VirtualDirectoryPtr virtualDir = std::make_shared(filePath, weak_from_this()); - entry = PhysicalDirectoryEntry{ { std::move(virtualDir) }, std::move(filePath) }; - } - else - return false; //< either not known or of a special type - - return CallbackReturn(callback, entry); + return CallbackReturn(callback, *std::move(entryOpt)); } return false; @@ -378,9 +373,9 @@ namespace Nz { assert(!dirName.empty()); - bool dirFound = directory->GetEntryInternal(dirName, [&](const Entry& entry) + bool dirFound = directory->GetEntryInternal(dirName, true, [&](const Entry& entry) { - if (auto dirEntry = std::get_if(&entry)) + if (auto dirEntry = std::get_if(&entry)) directory = dirEntry->directory; else allowCreation = false; //< does exist but is not a directory diff --git a/include/Nazara/Core/VirtualDirectoryFilesystemResolver.hpp b/include/Nazara/Core/VirtualDirectoryFilesystemResolver.hpp new file mode 100644 index 000000000..f3c87e9c3 --- /dev/null +++ b/include/Nazara/Core/VirtualDirectoryFilesystemResolver.hpp @@ -0,0 +1,40 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_CORE_VIRTUALDIRECTORYFILESYSTEMRESOLVER_HPP +#define NAZARA_CORE_VIRTUALDIRECTORYFILESYSTEMRESOLVER_HPP + +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_CORE_API VirtualDirectoryFilesystemResolver : public VirtualDirectoryResolver + { + public: + inline VirtualDirectoryFilesystemResolver(std::filesystem::path physicalPath, OpenModeFlags fileOpenMode = OpenMode::ReadOnly | OpenMode::Defer); + VirtualDirectoryFilesystemResolver(const VirtualDirectoryFilesystemResolver&) = delete; + VirtualDirectoryFilesystemResolver(VirtualDirectoryFilesystemResolver&&) = delete; + ~VirtualDirectoryFilesystemResolver() = default; + + void ForEach(std::weak_ptr parent, FunctionRef callback) const override; + + std::optional Resolve(std::weak_ptr parent, const std::string_view* parts, std::size_t partCount) const override; + + VirtualDirectoryFilesystemResolver& operator=(const VirtualDirectoryFilesystemResolver&) = delete; + VirtualDirectoryFilesystemResolver& operator=(VirtualDirectoryFilesystemResolver&&) = delete; + + private: + std::filesystem::path m_physicalPath; + OpenModeFlags m_fileOpenMode; + }; +} + +#include + +#endif // NAZARA_CORE_VIRTUALDIRECTORYFILESYSTEMRESOLVER_HPP diff --git a/include/Nazara/Core/VirtualDirectoryFilesystemResolver.inl b/include/Nazara/Core/VirtualDirectoryFilesystemResolver.inl new file mode 100644 index 000000000..f6b690a50 --- /dev/null +++ b/include/Nazara/Core/VirtualDirectoryFilesystemResolver.inl @@ -0,0 +1,17 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline VirtualDirectoryFilesystemResolver::VirtualDirectoryFilesystemResolver(std::filesystem::path physicalPath, OpenModeFlags fileOpenMode) : + m_physicalPath(std::move(physicalPath)), + m_fileOpenMode(fileOpenMode) + { + } +} + +#include diff --git a/src/Nazara/Core/AppFilesystemComponent.cpp b/src/Nazara/Core/AppFilesystemComponent.cpp index 04be6ad1d..1abf2036e 100644 --- a/src/Nazara/Core/AppFilesystemComponent.cpp +++ b/src/Nazara/Core/AppFilesystemComponent.cpp @@ -3,8 +3,32 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include namespace Nz { + const VirtualDirectoryPtr& AppFilesystemComponent::Mount(std::string_view name, std::filesystem::path filepath) + { + return Mount(name, std::make_shared(std::make_shared(std::move(filepath)))); + } + + const VirtualDirectoryPtr& AppFilesystemComponent::Mount(std::string_view name, VirtualDirectoryPtr directory) + { + if (name.empty()) + { + m_rootDirectory = std::move(directory); + return m_rootDirectory; + } + + if (!m_rootDirectory) + m_rootDirectory = std::make_shared(); + + return m_rootDirectory->StoreDirectory(name, std::move(directory)).directory; + } + + void AppFilesystemComponent::MountDefaultDirectories() + { + m_rootDirectory = std::make_shared(std::make_shared(std::filesystem::current_path())); + } } diff --git a/src/Nazara/Core/VirtualDirectory.cpp b/src/Nazara/Core/VirtualDirectory.cpp new file mode 100644 index 000000000..1751da887 --- /dev/null +++ b/src/Nazara/Core/VirtualDirectory.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + VirtualDirectoryResolver::~VirtualDirectoryResolver() = default; +} diff --git a/src/Nazara/Core/VirtualDirectoryFilesystemResolver.cpp b/src/Nazara/Core/VirtualDirectoryFilesystemResolver.cpp new file mode 100644 index 000000000..382d73712 --- /dev/null +++ b/src/Nazara/Core/VirtualDirectoryFilesystemResolver.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + void VirtualDirectoryFilesystemResolver::ForEach(std::weak_ptr parent, FunctionRef callback) const + { + for (auto&& physicalEntry : std::filesystem::directory_iterator(m_physicalPath)) + { + std::string filename = PathToString(physicalEntry.path().filename()); + + std::filesystem::file_status status = physicalEntry.status(); + + VirtualDirectory::Entry entry; + if (std::filesystem::is_regular_file(status)) + { + if (!callback(filename, VirtualDirectory::FileEntry{ std::make_shared(physicalEntry.path(), m_fileOpenMode) })) + return; + } + else if (std::filesystem::is_directory(status)) + { + VirtualDirectoryPtr virtualDir = std::make_shared(std::make_shared(physicalEntry.path()), parent); + if (!callback(filename, VirtualDirectory::DirectoryEntry{ { std::move(virtualDir) } })) + return; + } + } + } + + std::optional VirtualDirectoryFilesystemResolver::Resolve(std::weak_ptr parent, const std::string_view* parts, std::size_t partCount) const + { + std::filesystem::path filePath = m_physicalPath; + for (std::size_t i = 0; i < partCount; ++i) + filePath /= parts[i]; + + std::filesystem::file_status status = std::filesystem::status(filePath); //< FIXME: This will follow symlink, is this the intended behavior? (see symlink_status) + + VirtualDirectory::Entry entry; + if (std::filesystem::is_regular_file(status)) + return VirtualDirectory::FileEntry{ std::make_shared(std::move(filePath), m_fileOpenMode) }; + else if (std::filesystem::is_directory(status)) + { + VirtualDirectoryPtr virtualDir = std::make_shared(std::make_shared(std::move(filePath)), parent); + return VirtualDirectory::DirectoryEntry{ { std::move(virtualDir) } }; + } + else + return std::nullopt; //< either not known or of a special type + } +} diff --git a/tests/UnitTests/Engine/Core/VirtualDirectoryTest.cpp b/tests/UnitTests/Engine/Core/VirtualDirectoryTest.cpp index aa7975b02..81edf873b 100644 --- a/tests/UnitTests/Engine/Core/VirtualDirectoryTest.cpp +++ b/tests/UnitTests/Engine/Core/VirtualDirectoryTest.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -65,12 +66,12 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") CHECK(virtualDir->GetEntry("Foo", [](const Nz::VirtualDirectory::Entry& entry) { - return std::holds_alternative(entry); + return std::holds_alternative(entry); })); CHECK(virtualDir->GetEntry("Foo/Bar", [](const Nz::VirtualDirectory::Entry& entry) { - return std::holds_alternative(entry); + return std::holds_alternative(entry); })); CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/) @@ -82,35 +83,28 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") std::mt19937 randGen(std::random_device{}()); auto GenerateRandomData = [&] { - std::vector randomData; + Nz::ByteArray randomData; for (std::size_t i = 0; i < 256; ++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)); + randomData.PushBack(Nz::SafeCast((data & 0x000000FF) >> 0)); + randomData.PushBack(Nz::SafeCast((data & 0x0000FF00) >> 8)); + randomData.PushBack(Nz::SafeCast((data & 0x00FF0000) >> 16)); + randomData.PushBack(Nz::SafeCast((data & 0xFF000000) >> 24)); } return randomData; }; - auto CheckFile = [&](std::string_view path, const std::vector& expectedData) + auto CheckFile = [&](std::string_view path, const Nz::ByteArray& expectedData) { - return virtualDir->GetEntry(path, [&](const Nz::VirtualDirectory::Entry& entry) + return virtualDir->GetFileContent(path, [&](const void* data, std::size_t length) { - if (!std::holds_alternative(entry)) - { - FAIL("Target is not a file"); - return false; - } - - const auto& contentEntry = std::get(entry); - return std::equal(expectedData.begin(), expectedData.end(), contentEntry.data.begin(), contentEntry.data.end()); + return length == expectedData.size() && std::memcmp(data, &expectedData[0], length) == 0; }); }; - auto CheckFileContent = [&](std::string_view path, const std::vector& expectedData) + auto CheckFileContent = [&](std::string_view path, const Nz::ByteArray& expectedData) { return virtualDir->GetFileContent(path, [&](const void* data, std::size_t size) { @@ -120,7 +114,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") return false; } - return std::memcmp(expectedData.data(), data, expectedData.size()) == 0; + return std::memcmp(&expectedData[0], data, expectedData.size()) == 0; }); }; @@ -153,7 +147,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") struct File { std::string path; - std::vector data; + Nz::ByteArray data; }; std::vector files; @@ -188,7 +182,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") SECTION("Accessing filesystem using a VirtualDirectory") { - std::shared_ptr resourceDir = std::make_shared(GetAssetDir()); + std::shared_ptr resourceDir = std::make_shared(std::make_shared(GetAssetDir())); WHEN("Iterating, it's not empty") { @@ -199,7 +193,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") CHECK_FALSE(name == ".."); INFO("Only physical files and directories are expected"); - CHECK((std::holds_alternative(entry) || std::holds_alternative(entry))); + CHECK((std::holds_alternative(entry) || std::holds_alternative(entry))); empty = false; }); @@ -210,16 +204,14 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") { return dir->GetEntry(filepath, [&](const Nz::VirtualDirectory::Entry& entry) { - REQUIRE(std::holds_alternative(entry)); + REQUIRE(std::holds_alternative(entry)); - const auto& physFileEntry = std::get(entry); - - Nz::File file(physFileEntry.filePath); + const auto& physFileEntry = std::get(entry); Nz::SHA256Hash hash; - WHEN("We compute " << hash.GetHashName() << " of " << physFileEntry.filePath << " file") + WHEN("We compute " << hash.GetHashName() << " of " << physFileEntry.stream->GetPath() << " file") { - CHECK(Nz::ToUpper(Nz::ComputeHash(hash, file).ToHex()) == expectedHash); + CHECK(Nz::ToUpper(Nz::ComputeHash(hash, *physFileEntry.stream).ToHex()) == expectedHash); } return true; }); @@ -258,8 +250,8 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") auto CheckOurselves = [&](const auto& entry) { - REQUIRE(std::holds_alternative(entry)); - const auto& dirEntry = std::get(entry); + REQUIRE(std::holds_alternative(entry)); + const auto& dirEntry = std::get(entry); return dirEntry.directory == resourceDir; }; @@ -272,7 +264,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") } AND_THEN("Overriding the physical file with another one") { - resourceDir->StoreFile("Logo.png", GetAssetDir() / "Audio/ambience.ogg"); + resourceDir->StoreFile("Logo.png", std::make_shared(GetAssetDir() / "Audio/ambience.ogg", Nz::OpenMode::ReadOnly)); CHECK(CheckFileHash(resourceDir, "Audio/ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94")); CHECK(CheckFileHash(resourceDir, "Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94")); @@ -289,7 +281,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") { if (entryName == "GIF") { - CHECK(std::holds_alternative(entry)); + CHECK(std::holds_alternative(entry)); found = true; } }); @@ -300,7 +292,7 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") WHEN("Testing uproot escape") { - std::shared_ptr engineDir = std::make_shared(GetAssetDir() / "Audio"); + std::shared_ptr engineDir = std::make_shared(std::make_shared(GetAssetDir() / "Audio")); CHECK_FALSE(engineDir->IsUprootAllowed());