Lightweight 0.20251202.0
Loading...
Searching...
No Matches
ZipArchive.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../Api.hpp"
6#include "ZipEntry.hpp"
7#include "ZipError.hpp"
8
9#include <cstdint>
10#include <expected>
11#include <filesystem>
12#include <functional>
13#include <optional>
14#include <span>
15#include <string>
16#include <string_view>
17#include <vector>
18
19#if defined(__clang__)
20 #pragma clang diagnostic push
21 #pragma clang diagnostic ignored "-Wnullability-extension"
22#endif
23#include <zip.h>
24#if defined(__clang__)
25 #pragma clang diagnostic pop
26#endif
27
28namespace Lightweight::Zip
29{
30
31/// Mode for opening a ZIP archive.
32enum class OpenMode : uint8_t
33{
34 ReadOnly, ///< Open existing archive for reading only
35 Create, ///< Create new archive (fails if exists)
36 CreateOrTruncate ///< Create new archive or truncate existing
37};
38
39/// Compression methods supported by ZIP archives.
40/// Uses int32_t base to match libzip ZIP_CM_* constants.
41// NOLINTNEXTLINE(performance-enum-size)
42enum class CompressionMethod : int32_t
43{
44 Store = ZIP_CM_STORE, ///< No compression (store only)
45 Deflate = ZIP_CM_DEFLATE, ///< Standard deflate compression
46#if defined(ZIP_CM_ZSTD)
47 Zstd = ZIP_CM_ZSTD, ///< Zstandard compression (if available)
48#endif
49};
50
51/// Information about a ZIP archive entry.
53{
54 zip_int64_t index {}; ///< Index of the entry in the archive
55 std::string name; ///< Name of the entry
56 zip_uint64_t size {}; ///< Uncompressed size in bytes
57 zip_uint64_t compressedSize {}; ///< Compressed size in bytes
58};
59
60/// RAII wrapper for a ZIP archive (zip_t*).
61///
62/// This class provides safe, scoped access to a ZIP archive file. The archive
63/// is automatically closed when the ZipArchive object is destroyed.
64///
65/// ZipArchive objects are non-copyable but movable.
66///
67/// @code
68/// // Writing
69/// auto archiveResult = ZipArchive::CreateOrTruncate("backup.zip");
70/// if (!archiveResult) {
71/// std::cerr << "Failed: " << archiveResult.error().message << "\n";
72/// return;
73/// }
74/// auto& archive = *archiveResult;
75/// archive.AddString("metadata.json", jsonStr);
76/// archive.AddBuffer("data.bin", binaryData);
77/// archive.Close();
78///
79/// // Reading with functional chaining
80/// ZipArchive::Open("backup.zip")
81/// .and_then([](ZipArchive& ar) { return ar.ReadEntryAsString(0); })
82/// .transform([](std::string const& content) { /* process */ })
83/// .or_else([](ZipError const& e) { std::cerr << e.message; });
84/// @endcode
85class LIGHTWEIGHT_API ZipArchive final
86{
87 public:
88 ZipArchive(ZipArchive const&) = delete;
89 ZipArchive& operator=(ZipArchive const&) = delete;
90
91 /// Move constructor. Transfers ownership of the archive handle.
92 ZipArchive(ZipArchive&& other) noexcept;
93
94 /// Move assignment operator. Transfers ownership of the archive handle.
95 ZipArchive& operator=(ZipArchive&& other) noexcept;
96
97 /// Destructor. Discards the archive if not explicitly closed.
98 ~ZipArchive() noexcept;
99
100 // =========================================================================
101 // Factory methods
102 // =========================================================================
103
104 /// Opens an existing ZIP archive for reading.
105 ///
106 /// @param path The path to the archive file.
107 /// @return The opened archive, or a ZipError on failure.
108 [[nodiscard]] static std::expected<ZipArchive, ZipError> Open(std::filesystem::path const& path);
109
110 /// Creates a new ZIP archive.
111 ///
112 /// @param path The path for the new archive file.
113 /// @return The created archive, or a ZipError if the file already exists or creation fails.
114 [[nodiscard]] static std::expected<ZipArchive, ZipError> Create(std::filesystem::path const& path);
115
116 /// Creates a new ZIP archive, truncating any existing file.
117 ///
118 /// @param path The path for the archive file.
119 /// @return The created archive, or a ZipError on failure.
120 [[nodiscard]] static std::expected<ZipArchive, ZipError> CreateOrTruncate(std::filesystem::path const& path);
121
122 // =========================================================================
123 // Properties
124 // =========================================================================
125
126 /// Checks if the archive is currently open.
127 ///
128 /// @return true if the archive is open, false otherwise.
129 [[nodiscard]] bool IsOpen() const noexcept;
130
131 /// Returns the number of entries in the archive.
132 ///
133 /// @return The entry count, or -1 if the archive is not open.
134 [[nodiscard]] zip_int64_t EntryCount() const noexcept;
135
136 /// Returns the native libzip handle.
137 ///
138 /// @return The zip_t* handle, or nullptr if not open.
139 [[nodiscard]] zip_t* NativeHandle() const noexcept;
140
141 // =========================================================================
142 // Reading
143 // =========================================================================
144
145 /// Locates an entry by name.
146 ///
147 /// @param name The name of the entry to find.
148 /// @return The entry index, or std::nullopt if not found.
149 [[nodiscard]] std::optional<zip_int64_t> LocateEntry(std::string_view name) const;
150
151 /// Gets information about an entry by index.
152 ///
153 /// @param index The entry index.
154 /// @return Entry information, or a ZipError if the index is invalid.
155 [[nodiscard]] std::expected<EntryInfo, ZipError> GetEntryInfo(zip_int64_t index) const;
156
157 /// Opens an entry for streaming read access.
158 ///
159 /// @param index The entry index.
160 /// @return A ZipEntry for reading, or a ZipError on failure.
161 [[nodiscard]] std::expected<ZipEntry, ZipError> OpenEntry(zip_int64_t index) const;
162
163 /// Reads an entire entry as binary data.
164 ///
165 /// @param index The entry index.
166 /// @return The entry contents as a byte vector, or a ZipError on failure.
167 [[nodiscard]] std::expected<std::vector<uint8_t>, ZipError> ReadEntry(zip_int64_t index) const;
168
169 /// Reads an entire entry as a string.
170 ///
171 /// @param index The entry index.
172 /// @return The entry contents as a string, or a ZipError on failure.
173 [[nodiscard]] std::expected<std::string, ZipError> ReadEntryAsString(zip_int64_t index) const;
174
175 // =========================================================================
176 // Writing
177 // =========================================================================
178
179 /// Adds binary data as a new entry.
180 ///
181 /// @param name The name for the new entry.
182 /// @param data The binary data to add.
183 /// @param method The compression method to use.
184 /// @param level The compression level (0-9, where 0 is no compression).
185 /// @return The index of the new entry, or a ZipError on failure.
186 [[nodiscard]] std::expected<zip_int64_t, ZipError> AddBuffer(std::string_view name,
187 std::span<uint8_t const> data,
188 CompressionMethod method = CompressionMethod::Deflate,
189 uint32_t level = 6);
190
191 /// Adds string content as a new entry.
192 ///
193 /// @param name The name for the new entry.
194 /// @param content The string content to add.
195 /// @param method The compression method to use.
196 /// @param level The compression level (0-9, where 0 is no compression).
197 /// @return The index of the new entry, or a ZipError on failure.
198 [[nodiscard]] std::expected<zip_int64_t, ZipError> AddString(std::string_view name,
199 std::string_view content,
200 CompressionMethod method = CompressionMethod::Deflate,
201 uint32_t level = 6);
202
203 // =========================================================================
204 // Iteration
205 // =========================================================================
206
207 /// Iterates over all entries in the archive.
208 ///
209 /// @param callback Function called for each entry. Return false to stop iteration.
210 /// Parameters: (index, name, uncompressed_size)
211 void ForEachEntry(std::function<bool(zip_int64_t, std::string_view, zip_uint64_t)> const& callback) const;
212
213 /// Gets information about all entries in the archive.
214 ///
215 /// @return A vector of EntryInfo for all entries.
216 [[nodiscard]] std::vector<EntryInfo> GetAllEntries() const;
217
218 // =========================================================================
219 // Lifecycle
220 // =========================================================================
221
222 /// Closes the archive, writing all changes to disk.
223 ///
224 /// @return void on success, or a ZipError if closing fails.
225 [[nodiscard]] std::expected<void, ZipError> Close();
226
227 /// Discards the archive without writing changes.
228 ///
229 /// Use this to abandon modifications without saving them.
230 void Discard() noexcept;
231
232 private:
233 /// Private constructor. Only factory methods can create ZipArchive objects.
234 ///
235 /// @param zip The libzip archive handle.
236 explicit ZipArchive(zip_t* zip) noexcept;
237
238 zip_t* m_zip {};
239};
240
241/// Checks if a compression method is supported by the current libzip installation.
242///
243/// @param method The compression method to check.
244/// @return true if the method is supported, false otherwise.
245[[nodiscard]] LIGHTWEIGHT_API bool IsCompressionSupported(CompressionMethod method) noexcept;
246
247} // namespace Lightweight::Zip
~ZipArchive() noexcept
Destructor. Discards the archive if not explicitly closed.
ZipArchive & operator=(ZipArchive &&other) noexcept
Move assignment operator. Transfers ownership of the archive handle.
ZipArchive(ZipArchive &&other) noexcept
Move constructor. Transfers ownership of the archive handle.
Information about a ZIP archive entry.
zip_int64_t index
Index of the entry in the archive.
zip_uint64_t compressedSize
Compressed size in bytes.
zip_uint64_t size
Uncompressed size in bytes.
std::string name
Name of the entry.
Represents an error that occurred during a ZIP operation.
Definition ZipError.hpp:34