Skip to content

Commit b8ff132

Browse files
committed
Add a new .readOnly file mode
1 parent 506fb6e commit b8ff132

2 files changed

Lines changed: 40 additions & 6 deletions

File tree

Sources/Zip/ZipArchive+OnDisk.swift

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,70 @@ import Miniz
44
public extension ZipArchive<URL> {
55
/// Creates a `ZipArchive` instance for a disk-based zip archive at the specified file URL.
66
///
7-
/// If the file does not exist, a new archive is created at the specified location.
7+
/// If the file does not exist and the mode is `readAdd`, a new archive is created at the specified location.
8+
/// If the mode is `overwrite`, any existing archive at the location is replaced.
9+
/// If the mode is `readOnly`, the archive is opened strictly for reading, and must already exist.
810
///
911
/// - Parameter fileURL: The file URL of the zip archive to be read or created.
10-
/// - Parameter mode: The mode to use. The default, `Mode.readWrite`, lets you read an archive and, optionally, add new files to it. Use `Mode.overwrite` to create a new empty archive and overwrite an existing file if it exists.
12+
/// - Parameter mode: The mode to use. Use `.readOnly` to read an existing archive, `.readAdd` to read and add
13+
/// files (creating the archive if it doesn’t exist), or `.overwrite` to create a new empty archive, replacing
14+
/// any existing file.
1115
/// - Throws: An error if the initialization fails, such as if the file cannot be read or written.
16+
///
1217
convenience init(url fileURL: URL, mode: Mode = .readAdd) throws {
1318
self.init(archive: .init())
1419
try fileURL.withUnsafeFileSystemRepresentation { path in
15-
if mode == .readAdd {
20+
switch mode {
21+
case .readOnly:
22+
try get { mz_zip_reader_init_file(&$0, path, 0) }
23+
24+
case .readAdd:
1625
do {
1726
try get { mz_zip_reader_init_file(&$0, path, mz_uint32(MZ_ZIP_FLAG_WRITE_ALLOW_READING.rawValue)) }
1827
try get { mz_zip_writer_init_from_reader_v2(&$0, path, 0) }
1928
} catch ZipError.fileOpenFailed {
2029
try get { mz_zip_writer_init_file_v2(&$0, path, 0, mz_uint32(MZ_ZIP_FLAG_WRITE_ALLOW_READING.rawValue)) }
2130
}
22-
} else {
31+
32+
case .overwrite:
2333
try get { mz_zip_writer_init_file_v2(&$0, path, 0, 0) }
2434
}
2535
}
2636
}
27-
37+
2838
/// Closes the zip archive without writing the central directory to disk.
2939
///
3040
/// This method ends the zip archive session without finalizing changes. Use this if you haven't
3141
/// made changes and do not need to save the archive.
3242
///
3343
/// After calling this method, the archive is no longer usable, and you must not perform additional
3444
/// operations on it.
45+
///
3546
func close() {
3647
mz_zip_writer_end(&archive)
3748
}
38-
49+
3950
/// Finalizes the zip archive and writes it to disk.
4051
///
4152
/// This method ensures all changes are saved and closes the archive. After this method is called,
4253
/// no further modifications to the archive can be made.
4354
///
4455
/// - Throws: An error if the finalization process fails.
56+
///
4557
func finalize() throws {
4658
try get { mz_zip_writer_finalize_archive(&$0) }
4759
mz_zip_writer_end(&archive)
4860
}
4961

62+
/// Describes the mode to use when opening a zip archive.
5063
enum Mode {
64+
/// Opens an existing archive for reading only. No changes can be made.
65+
case readOnly
66+
67+
/// Opens an existing archive for reading and allows adding new files. If the archive doesn't exist, a new one will be created.
5168
case readAdd
69+
70+
/// Creates a new empty archive, overwriting any existing file at the specified location.
5271
case overwrite
5372
}
5473
}

Tests/Tests.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,19 @@ struct Tests {
124124

125125
#expect(capturedData == randomData)
126126
}
127+
128+
@Test
129+
func readOnly() throws {
130+
let archive1 = try ZipArchive(url: fileURL, mode: .overwrite)
131+
try archive1.addFile(at: filename, data: data)
132+
try archive1.finalize()
133+
134+
// Read-only should fail to modify the archive
135+
let archive2 = try ZipArchive(url: fileURL, mode: .readOnly)
136+
#expect(try archive2.fileContents(at: filename) == data)
137+
#expect(throws: ZipError.self) {
138+
try archive2.addFile(at: filename2, data: data2)
139+
}
140+
archive2.close()
141+
}
127142
}

0 commit comments

Comments
 (0)