@@ -52,9 +52,7 @@ let centralDirectoryStructSignature = 0x02014b50
52
52
/// var archiveURL = URL(fileURLWithPath: "/path/file.zip")
53
53
/// var archive = Archive(url: archiveURL, accessMode: .update)
54
54
/// try archive?.addEntry("test.txt", relativeTo: baseURL, compressionMethod: .deflate)
55
- public actor Archive : AsyncSequence {
56
- public typealias Element = Entry
57
-
55
+ public actor Archive {
58
56
59
57
typealias LocalFileHeader = Entry . LocalFileHeader
60
58
typealias DataDescriptor = Entry . DefaultDataDescriptor
@@ -79,6 +77,8 @@ public actor Archive: AsyncSequence {
79
77
case invalidBufferSize
80
78
/// Thrown when uncompressedSize/compressedSize exceeds `Int64.max` (Imposed by file API).
81
79
case invalidEntrySize
80
+ /// Thrown when the local header cannot be found.
81
+ case localHeaderNotFound
82
82
/// Thrown when the offset of local header data exceeds `Int64.max` (Imposed by file API).
83
83
case invalidLocalHeaderDataOffset
84
84
/// Thrown when the size of local header exceeds `Int64.max` (Imposed by file API).
@@ -206,80 +206,42 @@ public actor Archive: AsyncSequence {
206
206
self . zip64EndOfCentralDirectory = config. zip64EndOfCentralDirectory
207
207
}
208
208
209
- public nonisolated func makeAsyncIterator( ) -> Iterator {
210
- Iterator ( archive: self )
211
- }
209
+ private var entriesTask : Task < [ Entry ] , Error > ?
212
210
213
- public actor Iterator : AsyncIteratorProtocol {
214
-
215
- private struct Input {
216
- let transaction : DataSourceTransaction
217
- let totalNumberOfEntriesInCD : UInt64
211
+ /// Returns the list of entries in the archive.
212
+ public func entries( ) async throws -> [ Entry ] {
213
+ if entriesTask == nil {
214
+ entriesTask = Task { try await readEntries ( ) }
218
215
}
219
216
220
- private let archive : Archive
221
- private var directoryIndex : UInt64 = 0
222
- private var index = 0
223
-
224
- fileprivate init ( archive : Archive ) {
225
- self . archive = archive
217
+ return try await entriesTask! . value
218
+ }
219
+
220
+ private func readEntries ( ) async throws -> [ Entry ] {
221
+ guard totalNumberOfEntriesInCentralDirectory > 0 else {
222
+ return [ ]
226
223
}
227
224
228
- private var _initializeTask : Task < DataSourceTransaction , Error > ?
225
+ var entries : [ Entry ] = [ ]
226
+ var directoryIndex = offsetToStartOfCentralDirectory
227
+ let transaction = try await dataSource. openRead ( )
229
228
230
- private func initialize( ) async throws -> DataSourceTransaction {
231
- if _initializeTask == nil {
232
- _initializeTask = Task {
233
- directoryIndex = await archive. offsetToStartOfCentralDirectory
234
- return try await archive. dataSource. openRead ( )
235
- }
229
+ for _ in 0 ..< totalNumberOfEntriesInCentralDirectory {
230
+ guard let centralDirStruct: CentralDirectoryStructure = try await transaction. readStruct ( at: directoryIndex) else {
231
+ continue
236
232
}
237
233
238
- return try await _initializeTask!. value
239
- }
240
-
241
- public func next( ) async throws -> Entry ? {
242
- let totalNumberOfEntries = await archive. totalNumberOfEntriesInCentralDirectory
243
- guard index < totalNumberOfEntries else {
244
- return nil
245
- }
246
-
247
- let dataSource = try await initialize ( )
248
-
249
- do {
250
- guard let centralDirStruct: CentralDirectoryStructure = try await dataSource. readStruct ( at: directoryIndex) else {
251
- return nil
252
- }
253
- let offset = UInt64 ( centralDirStruct. effectiveRelativeOffsetOfLocalHeader)
254
- guard let localFileHeader: LocalFileHeader = try await dataSource. readStruct ( at: offset) else { return nil }
255
- var dataDescriptor : DataDescriptor ?
256
- var zip64DataDescriptor : ZIP64DataDescriptor ?
257
- if centralDirStruct. usesDataDescriptor {
258
- let additionalSize = UInt64 ( localFileHeader. fileNameLength) + UInt64( localFileHeader. extraFieldLength)
259
- let isCompressed = centralDirStruct. compressionMethod != CompressionMethod . none. rawValue
260
- let dataSize = isCompressed
261
- ? centralDirStruct. effectiveCompressedSize
262
- : centralDirStruct. effectiveUncompressedSize
263
- let descriptorPosition = offset + UInt64( LocalFileHeader . size) + additionalSize + dataSize
264
- if centralDirStruct. isZIP64 {
265
- zip64DataDescriptor = try await dataSource. readStruct ( at: descriptorPosition)
266
- } else {
267
- dataDescriptor = try await dataSource. readStruct ( at: descriptorPosition)
268
- }
269
- }
270
- defer {
271
- directoryIndex += UInt64 ( CentralDirectoryStructure . size)
272
- directoryIndex += UInt64 ( centralDirStruct. fileNameLength)
273
- directoryIndex += UInt64 ( centralDirStruct. extraFieldLength)
274
- directoryIndex += UInt64 ( centralDirStruct. fileCommentLength)
275
- index += 1
276
- }
277
- return Entry ( centralDirectoryStructure: centralDirStruct, localFileHeader: localFileHeader,
278
- dataDescriptor: dataDescriptor, zip64DataDescriptor: zip64DataDescriptor)
279
- } catch {
280
- return nil
234
+ if let entry = Entry ( centralDirectoryStructure: centralDirStruct) {
235
+ entries. append ( entry)
281
236
}
237
+
238
+ directoryIndex += UInt64 ( CentralDirectoryStructure . size)
239
+ directoryIndex += UInt64 ( centralDirStruct. fileNameLength)
240
+ directoryIndex += UInt64 ( centralDirStruct. extraFieldLength)
241
+ directoryIndex += UInt64 ( centralDirStruct. fileCommentLength)
282
242
}
243
+
244
+ return entries
283
245
}
284
246
285
247
/// Retrieve the ZIP `Entry` with the given `path` from the receiver.
@@ -291,10 +253,64 @@ public actor Archive: AsyncSequence {
291
253
/// - Parameter path: A relative file path identifying the corresponding `Entry`.
292
254
/// - Returns: An `Entry` with the given `path`. Otherwise, `nil`.
293
255
public func get( _ path: String ) async throws -> Entry ? {
294
- if let encoding = self . pathEncoding {
295
- return try await self . first { $0. path ( using: encoding) == path }
256
+ let result = try await entries ( ) . first {
257
+ if let encoding = self . pathEncoding {
258
+ return $0. path ( using: encoding) == path
259
+ } else {
260
+ return $0. path == path
261
+ }
296
262
}
297
- return try await self . first { $0. path == path }
263
+ return result
264
+ }
265
+
266
+ /// Cache for local file headers.
267
+ private var localFileHeaders : [ String : Task < LocalFileHeader , Error > ] = [ : ]
268
+
269
+ /// Retrieves the local file header for the given `entry`.
270
+ func localFileHeader( for entry: Entry ) async throws -> LocalFileHeader {
271
+ if let task = localFileHeaders [ entry. path] {
272
+ return try await task. value
273
+ }
274
+
275
+ let task = Task {
276
+ let transaction = try await dataSource. openRead ( )
277
+ let centralDirStruct = entry. centralDirectoryStructure
278
+ let offset = UInt64 ( centralDirStruct. effectiveRelativeOffsetOfLocalHeader)
279
+ guard
280
+ var localFileHeader: LocalFileHeader = try await transaction. readStruct ( at: offset)
281
+ else {
282
+ throw Archive . ArchiveError. localHeaderNotFound
283
+ }
284
+
285
+ /// We only load the data descriptors if we are in writing mode, because
286
+ /// they might need to be written over. In read mode it is is
287
+ /// superfluous as the same infos are in the central directory structure.
288
+ if centralDirStruct. usesDataDescriptor && accessMode != . read {
289
+ let additionalSize = UInt64 ( localFileHeader. fileNameLength) + UInt64( localFileHeader. extraFieldLength)
290
+ let isCompressed = centralDirStruct. compressionMethod != CompressionMethod . none. rawValue
291
+ let dataSize = isCompressed
292
+ ? centralDirStruct. effectiveCompressedSize
293
+ : centralDirStruct. effectiveUncompressedSize
294
+ let descriptorPosition = offset + UInt64( LocalFileHeader . size) + additionalSize + dataSize
295
+ if centralDirStruct. isZIP64 {
296
+ localFileHeader. zip64DataDescriptor = try await transaction. readStruct ( at: descriptorPosition)
297
+ } else {
298
+ localFileHeader. dataDescriptor = try await transaction. readStruct ( at: descriptorPosition)
299
+ }
300
+ }
301
+
302
+ return localFileHeader
303
+ }
304
+ localFileHeaders [ entry. path] = task
305
+
306
+ return try await task. value
307
+ }
308
+
309
+ /// Called when the archive was modified.
310
+ func didWrite( ) {
311
+ // Clears the caches.
312
+ entriesTask = nil
313
+ localFileHeaders = [ : ]
298
314
}
299
315
300
316
// MARK: - Helpers
0 commit comments