diff options
| -rw-r--r-- | build/visual-studio/core/core.vcxproj | 2 | ||||
| -rw-r--r-- | build/visual-studio/core/core.vcxproj.filters | 6 | ||||
| -rw-r--r-- | build/visual-studio/slang-rt/slang-rt.vcxproj | 2 | ||||
| -rw-r--r-- | build/visual-studio/slang-rt/slang-rt.vcxproj.filters | 6 | ||||
| -rw-r--r-- | source/core/slang-archive-file-system.cpp | 2 | ||||
| -rw-r--r-- | source/core/slang-archive-file-system.h | 8 | ||||
| -rw-r--r-- | source/core/slang-memory-file-system.cpp | 292 | ||||
| -rw-r--r-- | source/core/slang-memory-file-system.h | 130 | ||||
| -rw-r--r-- | source/core/slang-riff-file-system.cpp | 243 | ||||
| -rw-r--r-- | source/core/slang-riff-file-system.h | 87 | ||||
| -rw-r--r-- | tools/slang-unit-test/unit-test-file-system.cpp | 31 |
11 files changed, 502 insertions, 307 deletions
diff --git a/build/visual-studio/core/core.vcxproj b/build/visual-studio/core/core.vcxproj index 59d378b0e..6311e4b8b 100644 --- a/build/visual-studio/core/core.vcxproj +++ b/build/visual-studio/core/core.vcxproj @@ -284,6 +284,7 @@ <ClInclude Include="..\..\..\source\core\slang-lz4-compression-system.h" /> <ClInclude Include="..\..\..\source\core\slang-math.h" /> <ClInclude Include="..\..\..\source\core\slang-memory-arena.h" /> + <ClInclude Include="..\..\..\source\core\slang-memory-file-system.h" /> <ClInclude Include="..\..\..\source\core\slang-offset-container.h" /> <ClInclude Include="..\..\..\source\core\slang-platform.h" /> <ClInclude Include="..\..\..\source\core\slang-process-util.h" /> @@ -336,6 +337,7 @@ <ClCompile Include="..\..\..\source\core\slang-lazy-castable-list.cpp" /> <ClCompile Include="..\..\..\source\core\slang-lz4-compression-system.cpp" /> <ClCompile Include="..\..\..\source\core\slang-memory-arena.cpp" /> + <ClCompile Include="..\..\..\source\core\slang-memory-file-system.cpp" /> <ClCompile Include="..\..\..\source\core\slang-offset-container.cpp" /> <ClCompile Include="..\..\..\source\core\slang-platform.cpp" /> <ClCompile Include="..\..\..\source\core\slang-process-util.cpp" /> diff --git a/build/visual-studio/core/core.vcxproj.filters b/build/visual-studio/core/core.vcxproj.filters index 04802e905..bf553e327 100644 --- a/build/visual-studio/core/core.vcxproj.filters +++ b/build/visual-studio/core/core.vcxproj.filters @@ -111,6 +111,9 @@ <ClInclude Include="..\..\..\source\core\slang-memory-arena.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\source\core\slang-memory-file-system.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\..\..\source\core\slang-offset-container.h"> <Filter>Header Files</Filter> </ClInclude> @@ -263,6 +266,9 @@ <ClCompile Include="..\..\..\source\core\slang-memory-arena.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\..\..\source\core\slang-memory-file-system.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\..\..\source\core\slang-offset-container.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/build/visual-studio/slang-rt/slang-rt.vcxproj b/build/visual-studio/slang-rt/slang-rt.vcxproj index 6b8ef78df..a411c7c9b 100644 --- a/build/visual-studio/slang-rt/slang-rt.vcxproj +++ b/build/visual-studio/slang-rt/slang-rt.vcxproj @@ -296,6 +296,7 @@ <ClInclude Include="..\..\..\source\core\slang-lz4-compression-system.h" /> <ClInclude Include="..\..\..\source\core\slang-math.h" /> <ClInclude Include="..\..\..\source\core\slang-memory-arena.h" /> + <ClInclude Include="..\..\..\source\core\slang-memory-file-system.h" /> <ClInclude Include="..\..\..\source\core\slang-offset-container.h" /> <ClInclude Include="..\..\..\source\core\slang-platform.h" /> <ClInclude Include="..\..\..\source\core\slang-process-util.h" /> @@ -349,6 +350,7 @@ <ClCompile Include="..\..\..\source\core\slang-lazy-castable-list.cpp" /> <ClCompile Include="..\..\..\source\core\slang-lz4-compression-system.cpp" /> <ClCompile Include="..\..\..\source\core\slang-memory-arena.cpp" /> + <ClCompile Include="..\..\..\source\core\slang-memory-file-system.cpp" /> <ClCompile Include="..\..\..\source\core\slang-offset-container.cpp" /> <ClCompile Include="..\..\..\source\core\slang-platform.cpp" /> <ClCompile Include="..\..\..\source\core\slang-process-util.cpp" /> diff --git a/build/visual-studio/slang-rt/slang-rt.vcxproj.filters b/build/visual-studio/slang-rt/slang-rt.vcxproj.filters index 4f5a5da0f..b8f9b468f 100644 --- a/build/visual-studio/slang-rt/slang-rt.vcxproj.filters +++ b/build/visual-studio/slang-rt/slang-rt.vcxproj.filters @@ -111,6 +111,9 @@ <ClInclude Include="..\..\..\source\core\slang-memory-arena.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\source\core\slang-memory-file-system.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\..\..\source\core\slang-offset-container.h"> <Filter>Header Files</Filter> </ClInclude> @@ -266,6 +269,9 @@ <ClCompile Include="..\..\..\source\core\slang-memory-arena.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\..\..\source\core\slang-memory-file-system.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\..\..\source\core\slang-offset-container.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/source/core/slang-archive-file-system.cpp b/source/core/slang-archive-file-system.cpp index 98c995d23..88093239b 100644 --- a/source/core/slang-archive-file-system.cpp +++ b/source/core/slang-archive-file-system.cpp @@ -6,8 +6,6 @@ #include "slang-io.h" #include "slang-string-util.h" #include "slang-blob.h" -#include "slang-string-slice-pool.h" -#include "slang-uint-set.h" #include "slang-riff-file-system.h" diff --git a/source/core/slang-archive-file-system.h b/source/core/slang-archive-file-system.h index 03571bdc1..678791245 100644 --- a/source/core/slang-archive-file-system.h +++ b/source/core/slang-archive-file-system.h @@ -26,12 +26,6 @@ class IArchiveFileSystem : public ISlangCastable SLANG_NO_THROW virtual void SLANG_MCALL setCompressionStyle(const CompressionStyle& style) = 0; }; -class ArchiveFileSystem : public ISlangMutableFileSystem, public ComBaseObject -{ -public: - -}; - /* Maps an UnownedStringSlice to an index. All substrings are held internally in a StringSlicePool, and so owned by the type. */ class StringSliceIndexMap @@ -96,7 +90,6 @@ KeyValuePair<UnownedStringSlice, Index> StringSliceIndexMap::getAt(CountIndex co return pair; } - /* This class helps to find the contents and/or existence of an implicit directory.This finds the contents of a directory. This is achieved by using a path prefix that any contained path must at least match. If the remainder of the path contains a folder @@ -152,7 +145,6 @@ public: bool m_directoryExists; }; - SlangResult loadArchiveFileSystem(const void* data, size_t dataSizeInBytes, ComPtr<ISlangFileSystemExt>& outFileSystem); SlangResult createArchiveFileSystem(SlangArchiveType type, ComPtr<ISlangMutableFileSystem>& outFileSystem); diff --git a/source/core/slang-memory-file-system.cpp b/source/core/slang-memory-file-system.cpp new file mode 100644 index 000000000..9704542f7 --- /dev/null +++ b/source/core/slang-memory-file-system.cpp @@ -0,0 +1,292 @@ +#include "slang-memory-file-system.h" + +// For Path:: +#include "slang-io.h" +#include "slang-blob.h" + +// For ImplicitDirectoryCollector +#include "slang-archive-file-system.h" + +namespace Slang +{ + +void* MemoryFileSystem::getInterface(const Guid& guid) +{ + if ( guid == ISlangUnknown::getTypeGuid() || + guid == ISlangCastable::getTypeGuid() || + guid == ISlangFileSystem::getTypeGuid() || + guid == ISlangFileSystemExt::getTypeGuid() || + guid == ISlangMutableFileSystem::getTypeGuid()) + { + return static_cast<ISlangMutableFileSystem*>(this); + } + return nullptr; +} + +void* MemoryFileSystem::getObject(const Guid& guid) +{ + SLANG_UNUSED(guid); + return nullptr; +} + +void* MemoryFileSystem::castAs(const Guid& guid) +{ + if (auto ptr = getInterface(guid)) + { + return ptr; + } + return getObject(guid); +} + +SlangResult MemoryFileSystem::_calcCanonicalPath(const char* path, StringBuilder& out) +{ + List<UnownedStringSlice> splitPath; + Path::split(UnownedStringSlice(path), splitPath); + + // If the first part of a path is "", it means path of form "/some/path". Turn into "some/path". + if (splitPath.getCount() > 1 && splitPath[0].getLength() == 0) + { + splitPath.removeAt(0); + } + + Path::simplify(splitPath); + + if (splitPath.indexOf(UnownedStringSlice::fromLiteral("..")) >= 0) + { + return SLANG_E_NOT_FOUND; + } + + if (splitPath.getCount() == 0) + { + // It's an empty path; + return SLANG_FAIL; + } + + Path::join(splitPath.getBuffer(), splitPath.getCount(), out); + return SLANG_OK; +} + +MemoryFileSystem::Entry* MemoryFileSystem::_getEntryFromCanonicalPath(const String& canonicalPath) +{ + return m_entries.TryGetValue(canonicalPath); +} + +MemoryFileSystem::Entry* MemoryFileSystem::_getEntryFromPath(const char* path, String* outPath) +{ + StringBuilder buffer; + if (SLANG_FAILED(_calcCanonicalPath(path, buffer))) + { + return nullptr; + } + + if (outPath) + { + *outPath = buffer; + } + return _getEntryFromCanonicalPath(buffer); +} + +SlangResult MemoryFileSystem::_loadFile(const char* path, Entry** outEntry) +{ + *outEntry = nullptr; + Entry* entry = _getEntryFromPath(path); + if (entry == nullptr || entry->m_type != SLANG_PATH_TYPE_FILE) + { + return SLANG_E_NOT_FOUND; + } + *outEntry = entry; + return SLANG_OK; +} + +SlangResult MemoryFileSystem::loadFile(char const* path, ISlangBlob** outBlob) +{ + Entry* entry; + SLANG_RETURN_ON_FAIL(_loadFile(path, &entry)); + + ISlangBlob* contents = entry->m_contents; + contents->addRef(); + *outBlob = contents; + + return SLANG_OK; +} + +SlangResult MemoryFileSystem::getFileUniqueIdentity(const char* path, ISlangBlob** outUniqueIdentity) +{ + return getCanonicalPath(path, outUniqueIdentity); +} + +SlangResult MemoryFileSystem::calcCombinedPath(SlangPathType fromPathType, const char* fromPath, const char* path, ISlangBlob** pathOut) +{ + String combinedPath; + switch (fromPathType) + { + case SLANG_PATH_TYPE_FILE: + { + combinedPath = Path::combine(Path::getParentDirectory(fromPath), path); + break; + } + case SLANG_PATH_TYPE_DIRECTORY: + { + combinedPath = Path::combine(fromPath, path); + break; + } + } + + *pathOut = StringBlob::moveCreate(combinedPath).detach(); + return SLANG_OK; +} + +SlangResult MemoryFileSystem::getPathType(const char* path, SlangPathType* outPathType) +{ + String canonicalPath; + Entry* entry = _getEntryFromPath(path, &canonicalPath); + if (entry == nullptr) + { + // Could be an implicit path + ImplicitDirectoryCollector collector(canonicalPath); + for (const auto& pair : m_entries) + { + const Entry* childEntry = &pair.Value; + collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); + // If on adding a path we determine a directory exists, then we are done + if (collector.getDirectoryExists()) + { + *outPathType = SLANG_PATH_TYPE_DIRECTORY; + return SLANG_OK; + } + } + + // If not implicit or explicit we are done. + return SLANG_E_NOT_FOUND; + } + + // Explicit type + *outPathType = entry->m_type; + return SLANG_OK; +} + +SlangResult MemoryFileSystem::getSimplifiedPath(const char* path, ISlangBlob** outSimplifiedPath) +{ + String simplifiedPath = Path::simplify(path); + *outSimplifiedPath = StringBlob::moveCreate(simplifiedPath).detach(); + return SLANG_OK; +} + +SlangResult MemoryFileSystem::getCanonicalPath(const char* path, ISlangBlob** outCanonicalPath) +{ + StringBuilder buffer; + SLANG_RETURN_ON_FAIL(_calcCanonicalPath(path, buffer)); + *outCanonicalPath = StringBlob::moveCreate(buffer).detach(); + return SLANG_OK; +} + +SlangResult MemoryFileSystem::enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData) +{ + String canonicalPath; + Entry* entry = _getEntryFromPath(path, &canonicalPath); + + const bool foundDirectory = (entry && entry->m_type == SLANG_PATH_TYPE_DIRECTORY); + + // We allow implicit directories, so this works even if there isn't an explicit one + ImplicitDirectoryCollector collector(canonicalPath, foundDirectory); + + // If it is a directory, we need to see if there is anything in it + for (const auto& pair : m_entries) + { + const Entry* childEntry = &pair.Value; + collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); + } + + return collector.enumerate(callback, userData); +} + +SlangResult MemoryFileSystem::saveFile(const char* path, const void* data, size_t size) +{ + Entry* entry; + SLANG_RETURN_ON_FAIL(_requireFile(path, &entry)); + auto contents = RawBlob::create(data, size); + entry->setContents(size, contents); + return SLANG_OK; +} + +SlangResult MemoryFileSystem::_requireFile(const char* path, Entry** outEntry) +{ + *outEntry = nullptr; + + StringBuilder canonicalPath; + SLANG_RETURN_ON_FAIL(_calcCanonicalPath(path, canonicalPath)); + + Entry* foundEntry = _getEntryFromCanonicalPath(canonicalPath); + + if (foundEntry) + { + if (foundEntry->m_type != SLANG_PATH_TYPE_FILE) + { + // Can only set if it's already a file, if it's anything else it's an error + return SLANG_FAIL; + } + } + else + { + Entry entry; + entry.initFile(canonicalPath); + m_entries.Add(canonicalPath, entry); + + foundEntry = _getEntryFromCanonicalPath(canonicalPath); + } + + // It must be found and be a file + SLANG_ASSERT(foundEntry && foundEntry->m_type == SLANG_PATH_TYPE_FILE && foundEntry->m_canonicalPath == canonicalPath); + + *outEntry = foundEntry; + return SLANG_OK; +} + +SlangResult MemoryFileSystem::remove(const char* path) +{ + String canonicalPath; + Entry* entry = _getEntryFromPath(path, &canonicalPath); + + if (entry) + { + if (entry->m_type == SLANG_PATH_TYPE_DIRECTORY) + { + ImplicitDirectoryCollector collector(canonicalPath); + + // If it is a directory, we need to see if there is anything in it + for (const auto& pair : m_entries) + { + const Entry* childEntry = &pair.Value; + collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); + if (collector.hasContent()) + { + // Directory is not empty + return SLANG_FAIL; + } + } + } + + // Reset so doesn't hold references/keep memory in scope + entry->reset(); + m_entries.Remove(canonicalPath); + return SLANG_OK; + } + + return SLANG_E_NOT_FOUND; +} + +SlangResult MemoryFileSystem::createDirectory(const char* path) +{ + String canonicalPath; + if (_getEntryFromPath(path, &canonicalPath)) + { + return SLANG_FAIL; + } + + Entry entry; + entry.initDirectory(canonicalPath); + m_entries.Add(canonicalPath, entry); + return SLANG_OK; +} + +} // namespace Slang diff --git a/source/core/slang-memory-file-system.h b/source/core/slang-memory-file-system.h new file mode 100644 index 000000000..72bacf2bd --- /dev/null +++ b/source/core/slang-memory-file-system.h @@ -0,0 +1,130 @@ +#ifndef SLANG_CORE_MEMORY_FILE_SYSTEM_H +#define SLANG_CORE_MEMORY_FILE_SYSTEM_H + +#include "slang-basic.h" + +#include "../../slang-com-ptr.h" +#include "slang-com-object.h" + +namespace Slang +{ + +/* MemoryFileSystem is an implementation of ISlangMutableFileSystem that stores file contents in 'blobs' (typically) in memory. + +A derived class can change how the contents of the contents blob is interpretted (so for example the RiffFileSystem is implemented +such that the Entry.m_contents is the files contents compressed). + +The implementation uses a map to store the file/directory based on their canonical path. This makes access relatively fast and simple - +an access only requires a path being converted into a canonical path, and then a lookup. Whilst this makes typical access fast, it means +doing an enumeration of a directory slower as it requires traversing all entries to find which are in the path. + +This is in contrast with an implementation that held items in directories 'objects'. In that scenario the path through the hierarchy +would need to be traversed to find the item. Finding all of the items in a directory is very fast - it's all the items held +in the the directory 'object'. + +The implementation allows for 'implicit' directories. If we have a file "a/b" it's existance *implicitly* implies the existance of the +directory 'a'. This is similar to how archive file formats such as zip works. + +TODO(JS): +* We may want to make saveFile take a blob, or have a version that does. Doing so would allow the application to handle memory management +around the blob. +*/ +class MemoryFileSystem : public ISlangMutableFileSystem, public ComBaseObject +{ +public: + + // ISlangUnknown + SLANG_COM_BASE_IUNKNOWN_ALL + + // ISlangCastable + virtual SLANG_NO_THROW void* SLANG_MCALL castAs(const Guid& guid) SLANG_OVERRIDE; + + // ISlangFileSystem + virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadFile(char const* path, ISlangBlob** outBlob) SLANG_OVERRIDE; + + // ISlangFileSystemExt + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getFileUniqueIdentity(const char* path, ISlangBlob** uniqueIdentityOut) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL calcCombinedPath(SlangPathType fromPathType, const char* fromPath, const char* path, ISlangBlob** pathOut) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getPathType(const char* path, SlangPathType* pathTypeOut) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getSimplifiedPath(const char* path, ISlangBlob** outSimplifiedPath) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getCanonicalPath(const char* path, ISlangBlob** outCanonicalPath) SLANG_OVERRIDE; + virtual SLANG_NO_THROW void SLANG_MCALL clearCache() SLANG_OVERRIDE {} + virtual SLANG_NO_THROW SlangResult SLANG_MCALL enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData) SLANG_OVERRIDE; + virtual SLANG_NO_THROW OSPathKind SLANG_MCALL getOSPathKind() SLANG_OVERRIDE { return OSPathKind::None; } + + // ISlangModifyableFileSystem + virtual SLANG_NO_THROW SlangResult SLANG_MCALL saveFile(const char* path, const void* data, size_t size) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL remove(const char* path) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL createDirectory(const char* path) SLANG_OVERRIDE; + +protected: + + struct Entry + { + void reset() + { + m_type = SLANG_PATH_TYPE_FILE; + m_canonicalPath = String(); + m_uncompressedSizeInBytes = 0; + m_contents.setNull(); + } + + void initDirectory(const String& canonicalPath) + { + m_type = SLANG_PATH_TYPE_DIRECTORY; + m_canonicalPath = canonicalPath; + m_uncompressedSizeInBytes = 0; + m_contents.setNull(); + } + void initFile(const String& canonicalPath, size_t uncompressedSize, ISlangBlob* blob) + { + m_type = SLANG_PATH_TYPE_FILE; + m_canonicalPath = canonicalPath; + setContents(uncompressedSize, blob); + } + void initFile(const String& canonicalPath) + { + m_type = SLANG_PATH_TYPE_FILE; + m_canonicalPath = canonicalPath; + m_contents.setNull(); + m_uncompressedSizeInBytes = 0; + } + void setContents(size_t uncompressedSize, ISlangBlob* blob) + { + SLANG_ASSERT(m_type == SLANG_PATH_TYPE_FILE); + SLANG_ASSERT(blob); + m_uncompressedSizeInBytes = uncompressedSize; + m_contents = blob; + } + + SlangPathType m_type; + String m_canonicalPath; + + /// The size as seen on the file system. Might be different from the size of m_contents + /// if it's actually being stored in some other representation (such as compressed) + size_t m_uncompressedSizeInBytes; + ComPtr<ISlangBlob> m_contents; ///< Can be compressed or not + }; + + void* getInterface(const Guid& guid); + void* getObject(const Guid& guid); + + SlangResult _calcCanonicalPath(const char* path, StringBuilder& out); + Entry* _getEntryFromPath(const char* path, String* outPath = nullptr); + Entry* _getEntryFromCanonicalPath(const String& canonicalPath); + /// Creates or returns a file entry for the given path. + /// If created the entry is empty. + SlangResult _requireFile(const char* path, Entry** outEntry); + /// Given the path returns the entry if it's a file, or returns an error + SlangResult _loadFile(const char* path, Entry** outEntry); + + /// Clear, ensures any backing memory is also freed + void _clear() { m_entries = Dictionary<String, Entry>(); } + + // Maps canonical paths to an entries (which could be files or directories) + Dictionary<String, Entry> m_entries; +}; + +} + +#endif diff --git a/source/core/slang-riff-file-system.cpp b/source/core/slang-riff-file-system.cpp index 9402a2b4e..da73cb2a5 100644 --- a/source/core/slang-riff-file-system.cpp +++ b/source/core/slang-riff-file-system.cpp @@ -3,11 +3,7 @@ #include "../../slang-com-helper.h" #include "../../slang-com-ptr.h" -#include "slang-io.h" -#include "slang-string-util.h" #include "slang-blob.h" -#include "slang-string-slice-pool.h" -#include "slang-uint-set.h" // Compression systems #include "slang-deflate-compression-system.h" @@ -23,13 +19,9 @@ RiffFileSystem::RiffFileSystem(ICompressionSystem* compressionSystem): void* RiffFileSystem::getInterface(const Guid& guid) { - if ( guid == ISlangUnknown::getTypeGuid() || - guid == ISlangCastable::getTypeGuid() || - guid == ISlangFileSystem::getTypeGuid() || - guid == ISlangFileSystemExt::getTypeGuid() || - guid == ISlangMutableFileSystem::getTypeGuid()) + if (auto ptr = Super::getInterface(guid)) { - return static_cast<ISlangMutableFileSystem*>(this); + return ptr; } else if (guid == IArchiveFileSystem::getTypeGuid()) { @@ -53,183 +45,40 @@ void* RiffFileSystem::castAs(const Guid& guid) return getObject(guid); } -SlangResult RiffFileSystem::_calcCanonicalPath(const char* path, StringBuilder& out) -{ - List<UnownedStringSlice> splitPath; - Path::split(UnownedStringSlice(path), splitPath); - - // If the first part of a path is "", it means path of form "/some/path". Turn into "some/path". - if (splitPath.getCount() > 1 && splitPath[0].getLength() == 0) - { - splitPath.removeAt(0); - } - - Path::simplify(splitPath); - - if (splitPath.indexOf(UnownedStringSlice::fromLiteral("..")) >= 0) - { - return SLANG_E_NOT_FOUND; - } - - if (splitPath.getCount() == 0) - { - // It's an empty path; - return SLANG_FAIL; - } - - Path::join(splitPath.getBuffer(), splitPath.getCount(), out); - return SLANG_OK; -} - -RiffFileSystem::Entry* RiffFileSystem::_getEntryFromCanonicalPath(const String& canonicalPath) -{ - return m_entries.TryGetValue(canonicalPath); -} - -RiffFileSystem::Entry* RiffFileSystem::_getEntryFromPath(const char* path, String* outPath) -{ - StringBuilder buffer; - if (SLANG_FAILED(_calcCanonicalPath(path, buffer))) - { - return nullptr; - } - - if (outPath) - { - *outPath = buffer; - } - return _getEntryFromCanonicalPath(buffer); -} - SlangResult RiffFileSystem::loadFile(char const* path, ISlangBlob** outBlob) { - Entry* entry = _getEntryFromPath(path); - if (entry == nullptr || entry->m_type != SLANG_PATH_TYPE_FILE) - { - return SLANG_E_NOT_FOUND; - } + Entry* entry; + SLANG_RETURN_ON_FAIL(_loadFile(path, &entry)); + + ISlangBlob* contents = entry->m_contents; if (m_compressionSystem) { // Okay lets decompress into a blob ScopedAllocation alloc; void* dst = alloc.allocateTerminated(entry->m_uncompressedSizeInBytes); - - ISlangBlob* compressedData = entry->m_contents; - SLANG_RETURN_ON_FAIL(m_compressionSystem->decompress(compressedData->getBufferPointer(), compressedData->getBufferSize(), entry->m_uncompressedSizeInBytes, dst)); + SLANG_RETURN_ON_FAIL(m_compressionSystem->decompress(contents->getBufferPointer(), contents->getBufferSize(), entry->m_uncompressedSizeInBytes, dst)); auto blob = RawBlob::moveCreate(alloc); *outBlob = blob.detach(); + return SLANG_OK; } else { - // We don't have any compression, so can just return the blob - ISlangBlob* contents = entry->m_contents; + // Just return as is contents->addRef(); *outBlob = contents; + return SLANG_OK; } - - return SLANG_OK; -} - -SlangResult RiffFileSystem::getFileUniqueIdentity(const char* path, ISlangBlob** outUniqueIdentity) -{ - return getCanonicalPath(path, outUniqueIdentity); -} - -SlangResult RiffFileSystem::calcCombinedPath(SlangPathType fromPathType, const char* fromPath, const char* path, ISlangBlob** pathOut) -{ - String combinedPath; - switch (fromPathType) - { - case SLANG_PATH_TYPE_FILE: - { - combinedPath = Path::combine(Path::getParentDirectory(fromPath), path); - break; - } - case SLANG_PATH_TYPE_DIRECTORY: - { - combinedPath = Path::combine(fromPath, path); - break; - } - } - - *pathOut = StringUtil::createStringBlob(combinedPath).detach(); - return SLANG_OK; -} - -SlangResult RiffFileSystem::getPathType(const char* path, SlangPathType* outPathType) -{ - String canonicalPath; - Entry* entry = _getEntryFromPath(path, &canonicalPath); - if (entry == nullptr) - { - // Could be an implicit path - ImplicitDirectoryCollector collector(canonicalPath); - for (const auto& pair : m_entries) - { - const Entry* childEntry = &pair.Value; - collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); - // If on adding a path we determine a directory exists, then we are done - if (collector.getDirectoryExists()) - { - *outPathType = SLANG_PATH_TYPE_DIRECTORY; - return SLANG_OK; - } - } - - // If not implicit or explicit we are done. - return SLANG_E_NOT_FOUND; - } - - // Explicit type - *outPathType = entry->m_type; - return SLANG_OK; -} - -SlangResult RiffFileSystem::getSimplifiedPath(const char* path, ISlangBlob** outSimplifiedPath) -{ - String simplifiedPath = Path::simplify(path); - *outSimplifiedPath = StringUtil::createStringBlob(simplifiedPath).detach(); - return SLANG_OK; -} - -SlangResult RiffFileSystem::getCanonicalPath(const char* path, ISlangBlob** outCanonicalPath) -{ - StringBuilder buffer; - SLANG_RETURN_ON_FAIL(_calcCanonicalPath(path, buffer)); - *outCanonicalPath = StringUtil::createStringBlob(buffer).detach(); - return SLANG_OK; -} - -SlangResult RiffFileSystem::enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData) -{ - String canonicalPath; - Entry* entry = _getEntryFromPath(path, &canonicalPath); - - const bool foundDirectory = (entry && entry->m_type == SLANG_PATH_TYPE_DIRECTORY); - - // We allow implicit directories, so this works even if there isn't an explicit one - ImplicitDirectoryCollector collector(canonicalPath, foundDirectory); - - // If it is a directory, we need to see if there is anything in it - for (const auto& pair : m_entries) - { - const Entry* childEntry = &pair.Value; - collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); - } - - return collector.enumerate(callback, userData); } SlangResult RiffFileSystem::saveFile(const char* path, const void* data, size_t size) -{ - StringBuilder canonicalPath; - SLANG_RETURN_ON_FAIL(_calcCanonicalPath(path, canonicalPath)); +{ + Entry* entry; + SLANG_RETURN_ON_FAIL(_requireFile(path, &entry)); ComPtr<ISlangBlob> contents; - if (m_compressionSystem) { // Lets try compressing the input @@ -240,71 +89,7 @@ SlangResult RiffFileSystem::saveFile(const char* path, const void* data, size_t // Just store the data directly. contents = RawBlob::create(data, size); } - - if (Entry* foundEntry = _getEntryFromCanonicalPath(canonicalPath)) - { - if (foundEntry->m_type != SLANG_PATH_TYPE_FILE) - { - // Can only set if it's already a file, if it's anything else it's an error - return SLANG_FAIL; - } - - foundEntry->setContents(size, contents); - } - else - { - Entry entry; - entry.initFile(canonicalPath, size, contents); - m_entries.Add(canonicalPath, entry); - } - - return SLANG_OK; -} - -SlangResult RiffFileSystem::remove(const char* path) -{ - String canonicalPath; - Entry* entry = _getEntryFromPath(path, &canonicalPath); - - if (entry) - { - if (entry->m_type == SLANG_PATH_TYPE_DIRECTORY) - { - ImplicitDirectoryCollector collector(canonicalPath); - - // If it is a directory, we need to see if there is anything in it - for (const auto& pair : m_entries) - { - const Entry* childEntry = &pair.Value; - collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); - if (collector.hasContent()) - { - // Directory is not empty - return SLANG_FAIL; - } - } - } - - // Reset so doesn't hold references/keep memory in scope - entry->reset(); - m_entries.Remove(canonicalPath); - return SLANG_OK; - } - - return SLANG_E_NOT_FOUND; -} - -SlangResult RiffFileSystem::createDirectory(const char* path) -{ - String canonicalPath; - if (_getEntryFromPath(path, &canonicalPath)) - { - return SLANG_FAIL; - } - - Entry entry; - entry.initDirectory(canonicalPath); - m_entries.Add(canonicalPath, entry); + entry->setContents(size, contents); return SLANG_OK; } diff --git a/source/core/slang-riff-file-system.h b/source/core/slang-riff-file-system.h index 6cd66c3d5..eb9f24c73 100644 --- a/source/core/slang-riff-file-system.h +++ b/source/core/slang-riff-file-system.h @@ -2,9 +2,9 @@ #define SLANG_RIFF_FILE_SYSTEM_H #include "slang-archive-file-system.h" +#include "slang-memory-file-system.h" #include "slang-riff.h" -#include "slang-io.h" namespace Slang { @@ -28,14 +28,29 @@ struct RiffFileSystemBinary uint32_t pathSize; ///< The size of the path in bytes, including terminating 0 uint32_t pathType; ///< One of SlangPathType - // Followed by the path (including terminating0) + // Followed by the path (including terminating 0) // Followed by the compressed data }; }; -class RiffFileSystem : public ISlangMutableFileSystem, public IArchiveFileSystem, public ComBaseObject +/* RiffFileSystem implements ISlangMutableFileSystem and can be used to save and load the whole of it's contents as an 'archive' blob. + +The 'RIFF' part provides the structure to store out the contents. The data is only accessed in the RIFF format when being +read/written to an archive. Normal operations on the file system act in memory. + +A RiffFileSystem allows for compression to be used on files. To use compression pass in a suitable ICompressionSystem +implementation on construction. If constructed without an ICompressionSystem, data is stored uncompressed. When compression is +used, files 'contents' blob is actually the *compressed* version of the contents. Calling loadFile/saveFile will +uncompress/compress as need. If there is no compression contents is identical to the file contents. + +NOTE: +* The RIFF chunk IDs are *slang specific*. It conforms to RIFF but is unlikely to be usable with other tooling. +* The RIFF chunk IDs are in RiffFileSystemBinary struct +*/ +class RiffFileSystem : public MemoryFileSystem, public IArchiveFileSystem { public: + typedef MemoryFileSystem Super; // ISlangUnknown SLANG_COM_BASE_IUNKNOWN_ALL @@ -46,84 +61,24 @@ public: // ISlangFileSystem virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadFile(char const* path, ISlangBlob** outBlob) SLANG_OVERRIDE; - // ISlangFileSystemExt - virtual SLANG_NO_THROW SlangResult SLANG_MCALL getFileUniqueIdentity(const char* path, ISlangBlob** uniqueIdentityOut) SLANG_OVERRIDE; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL calcCombinedPath(SlangPathType fromPathType, const char* fromPath, const char* path, ISlangBlob** pathOut) SLANG_OVERRIDE; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL getPathType(const char* path, SlangPathType* pathTypeOut) SLANG_OVERRIDE; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL getSimplifiedPath(const char* path, ISlangBlob** outSimplifiedPath) SLANG_OVERRIDE; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL getCanonicalPath(const char* path, ISlangBlob** outCanonicalPath) SLANG_OVERRIDE; - virtual SLANG_NO_THROW void SLANG_MCALL clearCache() SLANG_OVERRIDE {} - virtual SLANG_NO_THROW SlangResult SLANG_MCALL enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData) SLANG_OVERRIDE; - virtual SLANG_NO_THROW OSPathKind SLANG_MCALL getOSPathKind() SLANG_OVERRIDE { return OSPathKind::None; } - // ISlangModifyableFileSystem virtual SLANG_NO_THROW SlangResult SLANG_MCALL saveFile(const char* path, const void* data, size_t size) SLANG_OVERRIDE; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL remove(const char* path) SLANG_OVERRIDE; - virtual SLANG_NO_THROW SlangResult SLANG_MCALL createDirectory(const char* path) SLANG_OVERRIDE; - - // ArchiveFileSystem + + // IArchiveFileSystem virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadArchive(const void* archive, size_t archiveSizeInBytes) SLANG_OVERRIDE; virtual SLANG_NO_THROW SlangResult SLANG_MCALL storeArchive(bool blobOwnsContent, ISlangBlob** outBlob) SLANG_OVERRIDE; virtual SLANG_NO_THROW void SLANG_MCALL setCompressionStyle(const CompressionStyle& style) SLANG_OVERRIDE { m_compressionStyle = style; } - /// Pass in nullptr, if no compression is wanted. In that scenario the contents will be stored in memory as is + /// Pass in nullptr, if no compression is wanted. explicit RiffFileSystem(ICompressionSystem* compressionSystem); /// True if this appears to be Riff archive static bool isArchive(const void* data, size_t sizeInBytes); protected: - - struct Entry - { - void reset() - { - m_type = SLANG_PATH_TYPE_FILE; - m_canonicalPath = String(); - m_uncompressedSizeInBytes = 0; - m_contents.setNull(); - } - - void initDirectory(const String& canonicalPath) - { - m_type = SLANG_PATH_TYPE_DIRECTORY; - m_canonicalPath = canonicalPath; - m_uncompressedSizeInBytes = 0; - m_contents.setNull(); - } - void initFile(const String& canonicalPath, size_t uncompressedSize, ISlangBlob* blob) - { - m_type = SLANG_PATH_TYPE_FILE; - m_canonicalPath = canonicalPath; - setContents(uncompressedSize, blob); - } - void setContents(size_t uncompressedSize, ISlangBlob* blob) - { - SLANG_ASSERT(m_type == SLANG_PATH_TYPE_FILE); - SLANG_ASSERT(blob); - m_uncompressedSizeInBytes = uncompressedSize; - m_contents = blob; - } - - SlangPathType m_type; - String m_canonicalPath; - size_t m_uncompressedSizeInBytes; ///< Needed if m_contents is compressed. - ComPtr<ISlangBlob> m_contents; ///< Can be compressed or not - }; - void* getInterface(const Guid& guid); void* getObject(const Guid& guid); - SlangResult _calcCanonicalPath(const char* path, StringBuilder& out); - Entry* _getEntryFromPath(const char* path, String* outPath = nullptr); - Entry* _getEntryFromCanonicalPath(const String& canonicalPath); - - /// Clear, ensures any backing memory is also freed - void _clear() { m_entries = Dictionary<String, Entry>(); } - - // Maps a path to an entry - Dictionary<String, Entry> m_entries; - ComPtr<ICompressionSystem> m_compressionSystem; CompressionStyle m_compressionStyle; diff --git a/tools/slang-unit-test/unit-test-file-system.cpp b/tools/slang-unit-test/unit-test-file-system.cpp index f1fd2c6bc..e3e2bb3cf 100644 --- a/tools/slang-unit-test/unit-test-file-system.cpp +++ b/tools/slang-unit-test/unit-test-file-system.cpp @@ -1,9 +1,12 @@ // unit-test-file-system.cpp #include "../../source/core/slang-file-system.h" + #include "../../source/core/slang-riff-file-system.h" #include "../../source/core/slang-zip-file-system.h" +#include "../../source/core/slang-memory-file-system.h" + #include "../../source/core/slang-deflate-compression-system.h" #include "../../source/core/slang-lz4-compression-system.h" @@ -19,6 +22,7 @@ enum class FileSystemType RiffUncompressed, RiffDeflate, RiffLZ4, + Memory, Relative, CountOf, }; @@ -103,6 +107,21 @@ static SlangResult _enumeratePath(ISlangMutableFileSystem* fileSystem, const cha return SLANG_OK; } +static SlangResult _checkSimplifiedPath(ISlangMutableFileSystem* fileSystem, const char* path, const char* normalPath) +{ + ComPtr<ISlangBlob> simplifiedPathBlob; + SLANG_RETURN_ON_FAIL(fileSystem->getSimplifiedPath(path, simplifiedPathBlob.writeRef())); + + auto simplifiedPath = StringUtil::getString(simplifiedPathBlob); + + if (simplifiedPath != normalPath) + { + return SLANG_FAIL; + } + + return SLANG_OK; +} + static SlangResult _test(FileSystemType type) { ComPtr<ISlangMutableFileSystem> fileSystem; @@ -129,9 +148,14 @@ static SlangResult _test(FileSystemType type) fileSystem = new RiffFileSystem(LZ4CompressionSystem::getSingleton()); break; } + case FileSystemType::Memory: + { + fileSystem = new MemoryFileSystem; + break; + } case FileSystemType::Relative: { - ComPtr<ISlangMutableFileSystem> memoryFileSystem(new RiffFileSystem(nullptr)); + ComPtr<ISlangMutableFileSystem> memoryFileSystem(new MemoryFileSystem); memoryFileSystem->createDirectory("base"); fileSystem = new RelativeFileSystem(memoryFileSystem, "base"); @@ -139,7 +163,6 @@ static SlangResult _test(FileSystemType type) } } - SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "a", "someText")); SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "b", "A longer bit of text....")); @@ -158,6 +181,10 @@ static SlangResult _test(FileSystemType type) const Entry entries[] = { {SLANG_PATH_TYPE_FILE, "a" }, {SLANG_PATH_TYPE_FILE, "b" }, {SLANG_PATH_TYPE_DIRECTORY, "d" } }; SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, ".", makeConstArrayView(entries))); } + + { + SLANG_RETURN_ON_FAIL(_checkSimplifiedPath(fileSystem, "d/../a", "a")); + } SLANG_RETURN_ON_FAIL(fileSystem->remove("d/a")); { |
