/ lib / ZipFile / ZipFile.h
ZipFile.h
 1  #pragma once
 2  #include <SdFat.h>
 3  
 4  #include <string>
 5  #include <unordered_map>
 6  #include <vector>
 7  
 8  class ZipFile {
 9   public:
10    struct FileStatSlim {
11      uint16_t method;             // Compression method
12      uint32_t compressedSize;     // Compressed size
13      uint32_t uncompressedSize;   // Uncompressed size
14      uint32_t localHeaderOffset;  // Offset of local file header
15    };
16  
17    struct ZipDetails {
18      uint32_t centralDirOffset;
19      uint16_t totalEntries;
20      bool isSet;
21    };
22  
23    struct SizeTarget {
24      uint64_t hash;   // FNV-1a 64-bit hash of normalized path
25      uint16_t len;    // Length for collision reduction
26      uint16_t index;  // Caller's index (e.g. spine index)
27  
28      bool operator<(const SizeTarget& other) const {
29        return hash < other.hash || (hash == other.hash && len < other.len);
30      }
31    };
32  
33    // FNV-1a 64-bit hash (no std::string allocation)
34    // Combined with 16-bit length provides ~80 bits of entropy;
35    // collision probability negligible for typical EPUB file counts
36    static uint64_t fnvHash64(const char* s, size_t len) {
37      uint64_t hash = 14695981039346656037ull;
38      for (size_t i = 0; i < len; i++) {
39        hash ^= static_cast<uint8_t>(s[i]);
40        hash *= 1099511628211ull;
41      }
42      return hash;
43    }
44  
45   private:
46    const std::string& filePath;
47    FsFile file;
48    ZipDetails zipDetails = {0, 0, false};
49    std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;
50  
51    bool loadFileStatSlim(const char* filename, FileStatSlim* fileStat);
52    long getDataOffset(const FileStatSlim& fileStat);
53    bool loadZipDetails();
54  
55   public:
56    explicit ZipFile(const std::string& filePath) : filePath(filePath) {}
57    ~ZipFile() = default;
58    // Zip file can be opened and closed by hand in order to allow for quick calculation of inflated file size
59    // It is NOT recommended to pre-open it for any kind of inflation due to memory constraints
60    bool isOpen() const { return !!file; }
61    bool open();
62    bool close();
63    bool loadAllFileStatSlims();
64    uint16_t getTotalEntries();
65    bool getInflatedFileSize(const char* filename, size_t* size);
66    // Batch lookup: scan ZIP central dir once and fill sizes for matching targets.
67    // targets must be sorted by (hash, len). sizes[target.index] receives uncompressedSize.
68    // Returns number of targets matched.
69    int fillUncompressedSizes(std::vector<SizeTarget>& targets, std::vector<uint32_t>& sizes);
70    // Find first existing file from a list of paths. Returns index into paths array, or -1 if none found.
71    // More efficient than calling getInflatedFileSize() for each path individually.
72    int findFirstExisting(const char* const* paths, int pathCount);
73    // Due to the memory required to run each of these, it is recommended to not preopen the zip file for multiple
74    // These functions will open and close the zip as needed
75    uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false);
76    bool readFileToStream(const char* filename, Print& out, size_t chunkSize);
77  };