yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Jay KwakDisable Link-Time-Optimization by default (#7345)7f04adbfb

master
26.5 KiB913 linesraw
1#include "slang-zip-file-system.h"
2
3#include "slang-blob.h"
4#include "slang-com-helper.h"
5#include "slang-com-ptr.h"
6#include "slang-implicit-directory-collector.h"
7#include "slang-io.h"
8#include "slang-riff.h"
9#include "slang-string-slice-pool.h"
10#include "slang-string-util.h"
11#include "slang-uint-set.h"
12
13#include <miniz.h>
14
15namespace Slang
16{
17
18class ZipFileSystemImpl : public ComBaseObject,
19                          public ISlangMutableFileSystem,
20                          public IArchiveFileSystem
21{
22public:
23    // ISlangUnknown
24    SLANG_COM_BASE_IUNKNOWN_ALL
25
26    // ISlangCastable
27    virtual SLANG_NO_THROW void* SLANG_MCALL castAs(const Guid& guid) SLANG_OVERRIDE;
28
29    // ISlangFileSystem
30    virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadFile(char const* path, ISlangBlob** outBlob)
31        SLANG_OVERRIDE;
32
33    // ISlangFileSystemExt
34    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
35    getFileUniqueIdentity(const char* path, ISlangBlob** uniqueIdentityOut) SLANG_OVERRIDE;
36    virtual SLANG_NO_THROW SlangResult SLANG_MCALL calcCombinedPath(
37        SlangPathType fromPathType,
38        const char* fromPath,
39        const char* path,
40        ISlangBlob** pathOut) SLANG_OVERRIDE;
41    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
42    getPathType(const char* path, SlangPathType* pathTypeOut) SLANG_OVERRIDE;
43    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
44    getPath(PathKind pathKind, const char* path, ISlangBlob** outPath) SLANG_OVERRIDE;
45    virtual SLANG_NO_THROW void SLANG_MCALL clearCache() SLANG_OVERRIDE {}
46    virtual SLANG_NO_THROW SlangResult SLANG_MCALL enumeratePathContents(
47        const char* path,
48        FileSystemContentsCallBack callback,
49        void* userData) SLANG_OVERRIDE;
50    virtual SLANG_NO_THROW OSPathKind SLANG_MCALL getOSPathKind() SLANG_OVERRIDE
51    {
52        return OSPathKind::None;
53    }
54
55    // ISlangModifyableFileSystem
56    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
57    saveFile(const char* path, const void* data, size_t size) SLANG_OVERRIDE;
58    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
59    saveFileBlob(const char* path, ISlangBlob* dataBlob) SLANG_OVERRIDE;
60    virtual SLANG_NO_THROW SlangResult SLANG_MCALL remove(const char* path) SLANG_OVERRIDE;
61    virtual SLANG_NO_THROW SlangResult SLANG_MCALL createDirectory(const char* path) SLANG_OVERRIDE;
62
63    // IArchiveFileSystem
64    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
65    loadArchive(const void* archive, size_t archiveSizeInBytes) SLANG_OVERRIDE;
66    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
67    storeArchive(bool blobOwnsContent, ISlangBlob** outBlob) SLANG_OVERRIDE;
68    virtual SLANG_NO_THROW void SLANG_MCALL setCompressionStyle(const CompressionStyle& style)
69        SLANG_OVERRIDE;
70
71    ZipFileSystemImpl();
72    ~ZipFileSystemImpl();
73
74protected:
75    enum class Mode
76    {
77        None,      // m_archive is not initialized
78        Read,      // m_archive is a reader
79        ReadWrite, // m_archive is a writer (that can be read from)
80    };
81
82    SlangResult _requireMode(Mode mode);
83    /// Do the mode change.
84    SlangResult _requireModeImpl(Mode newMode);
85
86    bool _hasArchive() { return m_mode != Mode::None; }
87    SlangResult _getFixedPath(const char* path, String& outPath);
88    SlangResult _findEntryIndex(const char* path, mz_uint& outIndex);
89    SlangResult _findEntryIndexFromFixedPath(const String& fixedPath, mz_uint& outIndex);
90
91    SlangResult _copyToAndInitWriter(mz_zip_archive& outWriter);
92
93    /// Returns SLANG_E_NOT_FOUND if no directory or contents found
94    /// terminationState controls when search terminates. If State::Undefined, will enumerate
95    /// everything. If outContents not set, will just determine if the directory exists
96    SlangResult _getPathContents(
97        ImplicitDirectoryCollector::State terminationState,
98        ImplicitDirectoryCollector* outCollector);
99
100    void _rebuildMap();
101
102    /// Returns true if the named item is at the index
103    UnownedStringSlice _getPathAtIndex(Index index);
104
105    void* getInterface(const Guid& guid);
106    void* getObject(const Guid& guid);
107
108    void _initReadWrite(mz_zip_archive& outWriter);
109
110    // Maps from a path to an index in the m_archive
111    StringSliceIndexMap m_pathMap;
112    // If bit is set (at the archive index) this index has been deleted.
113    UIntSet m_removedSet;
114
115    ScopedAllocation m_data;
116
117    mz_uint m_compressionLevel = MZ_BEST_COMPRESSION;
118    Mode m_mode = Mode::None;
119
120    mz_file_read_func m_readFunc;
121
122    mz_zip_archive m_archive;
123};
124
125void* ZipFileSystemImpl::getInterface(const Guid& guid)
126{
127    if (guid == ISlangUnknown::getTypeGuid() || guid == ISlangCastable::getTypeGuid())
128    {
129        return static_cast<ISlangMutableFileSystem*>(this);
130    }
131    else if (
132        guid == ISlangFileSystem::getTypeGuid() || guid == ISlangFileSystemExt::getTypeGuid() ||
133        guid == ISlangMutableFileSystem::getTypeGuid())
134    {
135        return static_cast<ISlangMutableFileSystem*>(this);
136    }
137    else if (guid == IArchiveFileSystem::getTypeGuid())
138    {
139        return static_cast<IArchiveFileSystem*>(this);
140    }
141    return nullptr;
142}
143
144void* ZipFileSystemImpl::getObject(const Guid& guid)
145{
146    SLANG_UNUSED(guid);
147    return nullptr;
148}
149
150void* ZipFileSystemImpl::castAs(const Guid& guid)
151{
152    if (auto ptr = getInterface(guid))
153    {
154        return ptr;
155    }
156    return getObject(guid);
157}
158
159// This is a very awkward hack to make it so we can get a read func, without having to implement all
160// of the tracking etc. All this does is create an empty zip, convert into a reader, and then grab
161// the read function
162static mz_file_read_func _calcReadFunc()
163{
164    mz_zip_archive archive;
165    mz_zip_zero_struct(&archive);
166    mz_zip_writer_init_heap(&archive, 0, 0);
167    // Convert to reader
168
169    void* buf;
170    size_t size;
171    mz_zip_writer_finalize_heap_archive(&archive, &buf, &size);
172    ScopedAllocation alloc;
173    alloc.attach(buf, size);
174    mz_zip_writer_end(&archive);
175
176    // Read
177    mz_zip_zero_struct(&archive);
178    mz_zip_reader_init_mem(&archive, alloc.getData(), alloc.getSizeInBytes(), 0);
179
180    auto readFunc = archive.m_pRead;
181
182    mz_zip_end(&archive);
183    return readFunc;
184}
185
186static mz_file_read_func _getReadFunc()
187{
188    static const auto readFunc = _calcReadFunc();
189    return readFunc;
190}
191
192ZipFileSystemImpl::ZipFileSystemImpl()
193    : m_mode(Mode::None)
194{
195    m_readFunc = _getReadFunc();
196}
197
198ZipFileSystemImpl::~ZipFileSystemImpl()
199{
200    _requireMode(Mode::None);
201}
202
203void ZipFileSystemImpl::_rebuildMap()
204{
205    m_pathMap.clear();
206
207    const mz_uint entryCount = mz_zip_reader_get_num_files(&m_archive);
208
209    m_removedSet.resizeAndClear(0);
210
211    for (mz_uint i = 0; i < entryCount; ++i)
212    {
213        mz_zip_archive_file_stat fileStat;
214        if (!mz_zip_reader_file_stat(&m_archive, mz_uint(i), &fileStat))
215        {
216            continue;
217        }
218
219        UnownedStringSlice currentName(fileStat.m_filename);
220
221        // Get rid of '/'
222        currentName = currentName.trim('/');
223
224        m_pathMap.add(currentName, Index(i));
225    }
226}
227
228UnownedStringSlice ZipFileSystemImpl::_getPathAtIndex(Index index)
229{
230    SLANG_ASSERT(m_mode != Mode::None);
231
232    mz_zip_archive_file_stat fileStat;
233    // Check it's added at the end
234    if (!mz_zip_reader_file_stat(&m_archive, mz_uint(index), &fileStat))
235    {
236        return UnownedStringSlice();
237    }
238
239    return UnownedStringSlice(fileStat.m_filename).trim('/');
240}
241
242void ZipFileSystemImpl::_initReadWrite(mz_zip_archive& outWriter)
243{
244    mz_zip_zero_struct(&outWriter);
245    mz_zip_writer_init_heap(&outWriter, 0, 0);
246    outWriter.m_pRead = m_readFunc;
247}
248
249SlangResult ZipFileSystemImpl::_copyToAndInitWriter(mz_zip_archive& outWriter)
250{
251    mz_zip_zero_struct(&outWriter);
252    switch (m_mode)
253    {
254    case Mode::None:
255        {
256            _initReadWrite(outWriter);
257            return SLANG_OK;
258        }
259    case Mode::Read:
260    case Mode::ReadWrite:
261        {
262            _initReadWrite(outWriter);
263
264            const mz_uint entryCount = mz_zip_reader_get_num_files(&m_archive);
265
266            for (mz_uint i = 0; i < entryCount; ++i)
267            {
268                if (m_removedSet.contains(i))
269                {
270                    continue;
271                }
272
273                // It's worth noting - it's not clear if this will work, because m_archive might not
274                // be a reader, in the miniz docs. If it's a writer, it's not clear how to convert a
275                // writer to a reader *selectively* which we require if we are going to lazily
276                // handle removals.
277                //
278                // The fix to make this work is the hack that sets the m_reader, such that in effect
279                // the writer is both read and write. That works because the default writer behavior
280                // is a single block of memory for the archive, and that is compatible with the
281                // reader.
282                if (!mz_zip_writer_add_from_zip_reader(&outWriter, &m_archive, i))
283                {
284                    mz_zip_end(&outWriter);
285                    return SLANG_FAIL;
286                }
287            }
288
289            return SLANG_OK;
290        }
291
292    default:
293        break;
294    }
295    return SLANG_FAIL;
296}
297
298SlangResult ZipFileSystemImpl::_requireModeImpl(Mode newMode)
299{
300    SLANG_ASSERT(newMode != m_mode);
301
302    switch (m_mode)
303    {
304    case Mode::None:
305        {
306            switch (newMode)
307            {
308            case Mode::Read:
309                {
310                    mz_uint flags = 0;
311                    mz_zip_zero_struct(&m_archive);
312                    mz_zip_reader_init(&m_archive, 0, flags);
313                    break;
314                }
315            case Mode::ReadWrite:
316                {
317                    _initReadWrite(m_archive);
318                    break;
319                }
320            default:
321                break;
322            }
323            break;
324        }
325    case Mode::Read:
326        {
327            switch (newMode)
328            {
329            case Mode::None:
330                {
331                    m_data.deallocate();
332                    mz_zip_end(&m_archive);
333                    break;
334                }
335            case Mode::ReadWrite:
336                {
337                    // If nothing is removed, we can just convert
338                    if (m_removedSet.isEmpty())
339                    {
340                        // Convert the reader into the writer
341                        if (!mz_zip_writer_init_from_reader(&m_archive, nullptr))
342                        {
343                            return SLANG_FAIL;
344                        }
345                        // If it's now a writer the memory is owned by the m_archive
346                        m_data.detach();
347                    }
348                    else
349                    {
350                        // Copy into a new writer
351                        mz_zip_archive writer;
352                        SLANG_RETURN_ON_FAIL(_copyToAndInitWriter(writer));
353
354                        // In the process we have removed anything that was deleted
355                        m_removedSet.clear();
356                        // Don't need the read data anymore
357                        m_data.deallocate();
358
359                        // Free the current archive
360                        mz_zip_end(&m_archive);
361                        // Make the writer current
362                        m_archive = writer;
363                        break;
364                    }
365                    break;
366                }
367            }
368            break;
369        }
370    case Mode::ReadWrite:
371        {
372            switch (newMode)
373            {
374            case Mode::None:
375                {
376                    mz_zip_writer_end(&m_archive);
377                    break;
378                }
379            case Mode::Read:
380                {
381                    // If anything has been removed we copy selectively into a new writer, and then
382                    // convert that
383                    if (!m_removedSet.isEmpty())
384                    {
385                        // There are entries that are deleted... so we need to copy selectively
386                        mz_zip_archive writer;
387                        SLANG_RETURN_ON_FAIL(_copyToAndInitWriter(writer));
388
389                        // In the process we have removed anything that was deleted
390                        m_removedSet.clear();
391
392                        // Get rid of the old writer
393                        mz_zip_writer_end(&m_archive);
394                        m_archive = writer;
395                    }
396
397                    void* buf;
398                    size_t size;
399                    mz_zip_writer_finalize_heap_archive(&m_archive, &buf, &size);
400                    m_data.attach(buf, size);
401
402                    mz_zip_writer_end(&m_archive);
403
404                    // Read
405                    mz_zip_zero_struct(&m_archive);
406                    if (!mz_zip_reader_init_mem(
407                            &m_archive,
408                            m_data.getData(),
409                            m_data.getSizeInBytes(),
410                            0))
411                    {
412                        m_data.deallocate();
413                        return SLANG_FAIL;
414                    }
415                    break;
416                }
417            default:
418                break;
419            }
420        }
421    }
422
423    // Set the new mode
424    m_mode = newMode;
425    return SLANG_OK;
426}
427
428SlangResult ZipFileSystemImpl::_requireMode(Mode newMode)
429{
430    if (newMode == m_mode)
431    {
432        return SLANG_OK;
433    }
434
435    SlangResult res = _requireModeImpl(newMode);
436    if (SLANG_SUCCEEDED(res))
437    {
438        m_mode = newMode;
439    }
440
441    _rebuildMap();
442    return res;
443}
444
445SlangResult ZipFileSystemImpl::_getFixedPath(const char* path, String& outPath)
446{
447    StringBuilder simplifiedPath;
448    SLANG_RETURN_ON_FAIL(
449        Path::simplify(path, Path::SimplifyStyle::AbsoluteOnlyAndNoRoot, simplifiedPath));
450    outPath = simplifiedPath;
451    return SLANG_OK;
452}
453
454SlangResult ZipFileSystemImpl::_findEntryIndexFromFixedPath(
455    const String& fixedPath,
456    mz_uint& outIndex)
457{
458    const Index index = m_pathMap.getValue(fixedPath.getUnownedSlice());
459
460    // If not in list or deleted - it is removed
461    if (index < 0 || m_removedSet.contains(index))
462    {
463        return SLANG_E_NOT_FOUND;
464    }
465
466    outIndex = mz_uint(index);
467    return SLANG_OK;
468}
469
470SlangResult ZipFileSystemImpl::_findEntryIndex(const char* path, mz_uint& outIndex)
471{
472    String fixedPath;
473    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
474    SLANG_RETURN_ON_FAIL(_findEntryIndexFromFixedPath(fixedPath, outIndex));
475    return SLANG_OK;
476}
477
478SlangResult ZipFileSystemImpl::loadFile(char const* path, ISlangBlob** outBlob)
479{
480    mz_uint index;
481    SLANG_RETURN_ON_FAIL(_findEntryIndex(path, index));
482
483    // Check it's a file
484    mz_zip_archive_file_stat fileStat;
485    if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat) || fileStat.m_is_directory)
486    {
487        return SLANG_E_NOT_FOUND;
488    }
489
490    ScopedAllocation alloc;
491    if (!alloc.allocateTerminated(size_t(fileStat.m_uncomp_size)))
492    {
493        return SLANG_E_OUT_OF_MEMORY;
494    }
495
496    const mz_uint flags = 0;
497
498    // Extract to memory
499    if (!mz_zip_reader_extract_to_mem(
500            &m_archive,
501            index,
502            alloc.getData(),
503            alloc.getSizeInBytes(),
504            flags))
505    {
506        return SLANG_FAIL;
507    }
508
509    *outBlob = RawBlob::moveCreate(alloc).detach();
510    return SLANG_OK;
511}
512
513SlangResult ZipFileSystemImpl::getPathType(const char* path, SlangPathType* outPathType)
514{
515    if (!_hasArchive())
516    {
517        return SLANG_E_NOT_FOUND;
518    }
519
520    String fixedPath;
521    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
522
523    // First look if there is an *explicit* entry - either file or directory
524    mz_uint index;
525    if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
526    {
527        mz_zip_archive_file_stat fileStat;
528        if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat))
529        {
530            return SLANG_FAIL;
531        }
532
533        *outPathType = fileStat.m_is_directory ? SLANG_PATH_TYPE_DIRECTORY : SLANG_PATH_TYPE_FILE;
534        return SLANG_OK;
535    }
536    else
537    {
538        // It could be an *implicit* directory (ie as part of a path). So lets look for that...
539        ImplicitDirectoryCollector collector(fixedPath);
540        SLANG_RETURN_ON_FAIL(
541            _getPathContents(ImplicitDirectoryCollector::State::DirectoryExists, &collector));
542        if (collector.getDirectoryExists())
543        {
544            *outPathType = SLANG_PATH_TYPE_DIRECTORY;
545            return SLANG_OK;
546        }
547    }
548
549    return SLANG_E_NOT_FOUND;
550}
551
552SlangResult ZipFileSystemImpl::getPath(PathKind pathKind, const char* path, ISlangBlob** outPath)
553{
554    switch (pathKind)
555    {
556    case PathKind::Display:
557    case PathKind::Canonical:
558        {
559            // Get the fixed path
560            String fixedPath;
561            SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
562
563            // See if we can find in the zip explicitly
564            mz_uint index;
565            if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
566            {
567                mz_zip_archive_file_stat fileStat;
568                if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat))
569                {
570                    return SLANG_FAIL;
571                }
572
573                // Use the path in the archive itself
574                *outPath = StringUtil::createStringBlob(fileStat.m_filename).detach();
575                return SLANG_OK;
576            }
577
578            // Else output the fixed path
579            *outPath = StringUtil::createStringBlob(fixedPath).detach();
580            return SLANG_OK;
581        }
582    case PathKind::Simplified:
583        {
584            *outPath = StringUtil::createStringBlob(Path::simplify(path)).detach();
585            return SLANG_OK;
586        }
587    default:
588        break;
589    }
590
591    return SLANG_E_NOT_AVAILABLE;
592}
593
594SlangResult ZipFileSystemImpl::getFileUniqueIdentity(
595    const char* path,
596    ISlangBlob** outUniqueIdentity)
597{
598    return getPath(PathKind::Canonical, path, outUniqueIdentity);
599}
600
601SlangResult ZipFileSystemImpl::calcCombinedPath(
602    SlangPathType fromPathType,
603    const char* fromPath,
604    const char* path,
605    ISlangBlob** pathOut)
606{
607    String relPath;
608    switch (fromPathType)
609    {
610    case SLANG_PATH_TYPE_FILE:
611        {
612            relPath = Path::combine(Path::getParentDirectory(fromPath), path);
613            break;
614        }
615    case SLANG_PATH_TYPE_DIRECTORY:
616        {
617            relPath = Path::combine(fromPath, path);
618            break;
619        }
620    }
621
622    *pathOut = StringUtil::createStringBlob(relPath).detach();
623    return SLANG_OK;
624}
625
626SlangResult ZipFileSystemImpl::_getPathContents(
627    ImplicitDirectoryCollector::State terminationState,
628    ImplicitDirectoryCollector* outCollector)
629{
630    if (!_hasArchive())
631    {
632        return SLANG_E_NOT_FOUND;
633    }
634
635    // Okay - I want to iterate through all of the entries and look for the ones with this prefix
636    const Index entryCount = Index(mz_zip_reader_get_num_files(&m_archive));
637    for (Index i = 0; i < entryCount; ++i)
638    {
639
640        // Skip if it's been deleted.
641        if (m_removedSet.contains(i))
642        {
643            continue;
644        }
645
646        mz_zip_archive_file_stat fileStat;
647        if (!mz_zip_reader_file_stat(&m_archive, mz_uint(i), &fileStat))
648        {
649            continue;
650        }
651
652        UnownedStringSlice currentPath(fileStat.m_filename);
653        SlangPathType pathType =
654            fileStat.m_is_directory ? SLANG_PATH_TYPE_DIRECTORY : SLANG_PATH_TYPE_FILE;
655        outCollector->addPath(pathType, currentPath);
656
657        // If a termination state is defined, and we reach it, we are done
658        if (terminationState != ImplicitDirectoryCollector::State::None &&
659            outCollector->hasState(terminationState))
660        {
661            return SLANG_OK;
662        }
663    }
664    // Check we found the directory at all...
665    return outCollector->getDirectoryExists() ? SLANG_OK : SLANG_E_NOT_FOUND;
666}
667
668SlangResult ZipFileSystemImpl::enumeratePathContents(
669    const char* path,
670    FileSystemContentsCallBack callback,
671    void* userData)
672{
673    if (!_hasArchive())
674    {
675        return SLANG_E_NOT_FOUND;
676    }
677
678    String fixedPath;
679    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
680    ImplicitDirectoryCollector collector(fixedPath);
681    SLANG_RETURN_ON_FAIL(_getPathContents(ImplicitDirectoryCollector::State::None, &collector));
682    return collector.enumerate(callback, userData);
683}
684
685SlangResult ZipFileSystemImpl::saveFileBlob(const char* path, ISlangBlob* dataBlob)
686{
687    if (!dataBlob)
688    {
689        return SLANG_E_INVALID_ARG;
690    }
691
692    return saveFile(path, dataBlob->getBufferPointer(), dataBlob->getBufferSize());
693}
694
695SlangResult ZipFileSystemImpl::saveFile(const char* path, const void* data, size_t size)
696{
697    String fixedPath;
698    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
699
700    mz_uint32 index;
701    if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
702    {
703        // Mark as removed
704        m_removedSet.add(index);
705    }
706
707    // We need to be able to write to the archive
708    _requireMode(Mode::ReadWrite);
709
710    // TODO(JS):
711    // We may want to check the directory exists that holds the path exists
712    // Which is easy to do. Without this check it allows directories to come into existence
713    // when the path to the file is used.
714    // This behaviour *isn't* strictly the same as the file system, which requires the path
715    // to a file to exist before it is written.
716    //
717    // Not enforcing this allows zips that don't explicitly specify paths - which saves space
718    // and is simpler.
719    //
720    // NOTE! This also means that if a file that produces an implicit path is *removed* that
721    // the implicit directories are also in effect removed.
722
723    // Need to add to the end of the file
724    const mz_uint32 entryCount = mz_zip_reader_get_num_files(&m_archive);
725    if (!mz_zip_writer_add_mem(&m_archive, fixedPath.getBuffer(), data, size, m_compressionLevel))
726    {
727        return SLANG_FAIL;
728    }
729
730    // Make sure it is added at expended index
731    SLANG_ASSERT(_getPathAtIndex(entryCount) == fixedPath.getUnownedSlice());
732
733    // Set in the map
734    m_pathMap.add(fixedPath.getUnownedSlice(), entryCount);
735    return SLANG_OK;
736}
737
738SlangResult ZipFileSystemImpl::remove(const char* path)
739{
740    String fixedPath;
741    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
742
743    mz_uint32 index;
744    SLANG_RETURN_ON_FAIL(_findEntryIndexFromFixedPath(fixedPath, index));
745
746    mz_zip_archive_file_stat fileStat;
747    if (!mz_zip_reader_file_stat(&m_archive, index, &fileStat))
748    {
749        return SLANG_FAIL;
750    }
751
752    if (fileStat.m_is_directory)
753    {
754        // Find the directory contents
755        ImplicitDirectoryCollector collector(fixedPath);
756        SLANG_RETURN_ON_FAIL(
757            _getPathContents(ImplicitDirectoryCollector::State::HasContent, &collector));
758
759        if (collector.hasContent())
760        {
761            // If it contains children we can't remove it
762            return SLANG_FAIL;
763        }
764    }
765
766    // Mark as removed
767    m_removedSet.add(index);
768    return SLANG_OK;
769}
770
771SlangResult ZipFileSystemImpl::createDirectory(const char* path)
772{
773    String fixedPath;
774    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
775
776    // If we find something with this name, we can't create it
777    mz_uint32 index;
778    if (SLANG_SUCCEEDED(_findEntryIndexFromFixedPath(fixedPath, index)))
779    {
780        return SLANG_FAIL;
781    }
782
783    // Make writable
784    SLANG_RETURN_ON_FAIL(_requireMode(Mode::ReadWrite));
785
786    const mz_uint entryCount = mz_zip_reader_get_num_files(&m_archive);
787
788    // The terminating / in the path indicates it's a directory
789    {
790        String dirPath(fixedPath);
791        dirPath.appendChar('/');
792        if (!mz_zip_writer_add_mem(&m_archive, dirPath.getBuffer(), nullptr, 0, m_compressionLevel))
793        {
794            return SLANG_FAIL;
795        }
796    }
797
798    SLANG_ASSERT(_getPathAtIndex(entryCount) == fixedPath.getUnownedSlice());
799
800    // Set the index, that we added at end
801    m_pathMap.add(fixedPath.getUnownedSlice(), entryCount);
802    return SLANG_OK;
803}
804
805SlangResult ZipFileSystemImpl::storeArchive(bool blobOwnsContent, ISlangBlob** outBlob)
806{
807    // If we have anything deleted in 'Read', we need to convert to 'Write' and then back to read
808    if (m_mode == Mode::Read && !m_removedSet.isEmpty())
809    {
810        _requireMode(Mode::ReadWrite);
811    }
812
813    _requireMode(Mode::Read);
814
815    ComPtr<ISlangBlob> blob;
816
817    if (blobOwnsContent)
818    {
819        // Takes a copy
820        blob = RawBlob::create(m_data.getData(), Index(m_data.getSizeInBytes()));
821    }
822    else
823    {
824        // Doesn't take a copy... Must use with care(!)
825        blob = UnownedRawBlob::create(m_data.getData(), Index(m_data.getSizeInBytes()));
826    }
827    *outBlob = blob.detach();
828    return SLANG_OK;
829}
830
831SlangResult ZipFileSystemImpl::loadArchive(const void* archive, size_t archiveSizeInBytes)
832{
833    // Making the mode None empties the archive
834    SLANG_RETURN_ON_FAIL(_requireMode(Mode::None));
835
836    // Store a copy of the archive contents
837    if (!m_data.set(archive, archiveSizeInBytes))
838    {
839        return SLANG_E_OUT_OF_MEMORY;
840    }
841
842    // Initialize archive
843    mz_zip_zero_struct(&m_archive);
844
845    // Read the contents of the archive, and make m_archive own it
846    if (!mz_zip_reader_init_mem(&m_archive, m_data.getData(), archiveSizeInBytes, 0))
847    {
848        return SLANG_FAIL;
849    }
850
851    m_mode = Mode::Read;
852
853    // Set up the mapping from paths to indices
854    _rebuildMap();
855
856    return SLANG_OK;
857}
858
859void ZipFileSystemImpl::setCompressionStyle(const CompressionStyle& style)
860{
861    switch (style.m_type)
862    {
863    case CompressionStyle::Type::BestSpeed:
864        m_compressionLevel = MZ_BEST_SPEED;
865        break;
866    case CompressionStyle::Type::BestCompression:
867        m_compressionLevel = MZ_BEST_COMPRESSION;
868        break;
869    case CompressionStyle::Type::Default:
870        m_compressionLevel = MZ_DEFAULT_LEVEL;
871        break;
872    case CompressionStyle::Type::Level:
873        {
874            int level = int(style.m_level * 10.0f + 0.5);
875            level = (level < 0) ? 0 : level;
876            level = (level > MZ_UBER_COMPRESSION) ? MZ_UBER_COMPRESSION : level;
877            m_compressionLevel = level;
878            break;
879        }
880    }
881}
882
883/* static */ SlangResult ZipFileSystem::create(ComPtr<ISlangMutableFileSystem>& out)
884{
885    out = new ZipFileSystemImpl;
886    return SLANG_OK;
887}
888
889/* static */ bool ZipFileSystem::isArchive(const void* data, size_t dataSizeInBytes)
890{
891    if (dataSizeInBytes < sizeof(FourCC))
892    {
893        return false;
894    }
895
896    FourCC fourCC = 0;
897    ::memcpy(&fourCC, data, sizeof(FourCC));
898
899    // https://en.wikipedia.org/wiki/List_of_file_signatures
900    switch (fourCC)
901    {
902    case SLANG_FOUR_CC(0x50, 0x4B, 0x03, 0x04):
903    case SLANG_FOUR_CC(0x50, 0x4B, 0x05, 0x06):
904    case SLANG_FOUR_CC(0x50, 0x4B, 0x07, 0x08):
905        {
906            // It's a zip
907            return true;
908        }
909    }
910    return false;
911}
912
913} // namespace Slang