lottie-ios 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.github/workflows/main.yml +27 -9
  2. package/Lottie.xcodeproj/project.pbxproj +158 -70
  3. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +2 -2
  4. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/IDEFindNavigatorScopes.plist +5 -0
  5. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +258 -0
  7. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +13 -2
  8. package/Package.swift +2 -1
  9. package/README.md +3 -3
  10. package/Rakefile +8 -4
  11. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +16 -2
  12. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +1 -1
  13. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +1 -1
  14. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +1 -1
  15. package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +6 -6
  16. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +76 -7
  17. package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +1 -1
  18. package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +1 -1
  19. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +66 -102
  20. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +2 -2
  21. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +3 -3
  22. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +66 -17
  23. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +55 -32
  24. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +16 -12
  25. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +3 -3
  26. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +24 -11
  27. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +2 -2
  28. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +1 -1
  29. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +13 -2
  30. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +9 -1
  31. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +22 -11
  32. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +1 -1
  33. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +13 -2
  34. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +16 -7
  35. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +1 -1
  36. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +2 -2
  37. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +1 -1
  38. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +2 -2
  39. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +20 -8
  40. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +1 -1
  41. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  42. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +1 -1
  43. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  44. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +28 -9
  45. package/Sources/Private/Model/Assets/ImageAsset.swift +4 -3
  46. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +2 -8
  47. package/Sources/Private/Model/DotLottie/DotLottieManifest.swift +3 -14
  48. package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +11 -1
  49. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+BackingConfiguration.swift +147 -0
  50. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Helpers.swift +351 -0
  51. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+MemoryFile.swift +183 -0
  52. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Progress.swift +66 -0
  53. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Reading.swift +144 -0
  54. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ReadingDeprecated.swift +49 -0
  55. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Writing.swift +385 -0
  56. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+WritingDeprecated.swift +91 -0
  57. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ZIP64.swift +170 -0
  58. package/Sources/Private/Model/DotLottie/{Zip/ZipArchive.swift → ZipFoundation/Archive.swift} +150 -227
  59. package/Sources/Private/Model/DotLottie/ZipFoundation/Data+Compression.swift +403 -0
  60. package/Sources/Private/Model/DotLottie/ZipFoundation/Data+CompressionDeprecated.swift +44 -0
  61. package/Sources/Private/Model/DotLottie/{Zip → ZipFoundation}/Data+Serialization.swift +62 -0
  62. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+Serialization.swift → ZipFoundation/Entry+Serialization.swift} +7 -7
  63. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+ZIP64.swift → ZipFoundation/Entry+ZIP64.swift} +13 -19
  64. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry.swift → ZipFoundation/Entry.swift} +141 -10
  65. package/Sources/Private/Model/DotLottie/ZipFoundation/FileManager+ZIP.swift +368 -0
  66. package/Sources/Private/Model/DotLottie/ZipFoundation/README.md +24 -0
  67. package/Sources/Private/Model/DotLottie/ZipFoundation/URL+ZIP.swift +32 -0
  68. package/Sources/Private/Model/Extensions/Bundle.swift +5 -14
  69. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +31 -8
  70. package/Sources/Private/Model/Objects/Transform.swift +58 -17
  71. package/Sources/Private/Model/ShapeItems/Repeater.swift +41 -7
  72. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +61 -7
  73. package/Sources/Private/Model/Text/TextAnimator.swift +37 -5
  74. package/Sources/Private/RootAnimationLayer.swift +3 -1
  75. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +12 -4
  76. package/Sources/Private/Utility/Extensions/DataExtension.swift +14 -4
  77. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +11 -0
  78. package/Sources/Private/Utility/Primitives/ColorExtension.swift +10 -13
  79. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +28 -6
  80. package/Sources/Public/Animation/LottieAnimationHelpers.swift +12 -10
  81. package/Sources/Public/Animation/LottieAnimationView.swift +213 -186
  82. package/Sources/Public/DotLottie/DotLottieFile.swift +11 -34
  83. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +101 -74
  84. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +90 -0
  85. package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -1
  86. package/Sources/Public/macOS/LottieAnimationViewBase.macOS.swift +1 -1
  87. package/lottie-ios.podspec +1 -1
  88. package/package.json +1 -1
  89. package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  90. package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/valentinperignon.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  91. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  92. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  93. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  94. package/Sources/Private/Model/DotLottie/Zip/Data+Compression.swift +0 -134
  95. package/Sources/Private/Model/DotLottie/Zip/FileManager+ZIP.swift +0 -130
  96. package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +0 -59
@@ -0,0 +1,144 @@
1
+ //
2
+ // Archive+Reading.swift
3
+ // ZIPFoundation
4
+ //
5
+ // Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
6
+ // Released under the MIT License.
7
+ //
8
+ // See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
9
+ //
10
+
11
+ import Foundation
12
+
13
+ extension Archive {
14
+ /// Read a ZIP `Entry` from the receiver and write it to `url`.
15
+ ///
16
+ /// - Parameters:
17
+ /// - entry: The ZIP `Entry` to read.
18
+ /// - url: The destination file URL.
19
+ /// - bufferSize: The maximum size of the read buffer and the decompression buffer (if needed).
20
+ /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance.
21
+ /// - progress: A progress object that can be used to track or cancel the extract operation.
22
+ /// - Returns: The checksum of the processed content or 0 if the `skipCRC32` flag was set to `true`.
23
+ /// - Throws: An error if the destination file cannot be written or the entry contains malformed content.
24
+ func extract(
25
+ _ entry: Entry,
26
+ to url: URL,
27
+ bufferSize: Int = defaultReadChunkSize,
28
+ skipCRC32: Bool = false,
29
+ progress: Progress? = nil)
30
+ throws -> CRC32
31
+ {
32
+ guard bufferSize > 0 else {
33
+ throw ArchiveError.invalidBufferSize
34
+ }
35
+ let fileManager = FileManager()
36
+ var checksum = CRC32(0)
37
+ switch entry.type {
38
+ case .file:
39
+ guard !fileManager.itemExists(at: url) else {
40
+ throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path])
41
+ }
42
+ try fileManager.createParentDirectoryStructure(for: url)
43
+ let destinationRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
44
+ guard let destinationFile: FILEPointer = fopen(destinationRepresentation, "wb+") else {
45
+ throw CocoaError(.fileNoSuchFile)
46
+ }
47
+ defer { fclose(destinationFile) }
48
+ let consumer = { _ = try Data.write(chunk: $0, to: destinationFile) }
49
+ checksum = try extract(
50
+ entry,
51
+ bufferSize: bufferSize,
52
+ skipCRC32: skipCRC32,
53
+ progress: progress,
54
+ consumer: consumer)
55
+ case .directory:
56
+ let consumer = { (_: Data) in
57
+ try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
58
+ }
59
+ checksum = try extract(
60
+ entry,
61
+ bufferSize: bufferSize,
62
+ skipCRC32: skipCRC32,
63
+ progress: progress,
64
+ consumer: consumer)
65
+ case .symlink:
66
+ guard !fileManager.itemExists(at: url) else {
67
+ throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path])
68
+ }
69
+ let consumer = { (data: Data) in
70
+ guard let linkPath = String(data: data, encoding: .utf8) else { throw ArchiveError.invalidEntryPath }
71
+ try fileManager.createParentDirectoryStructure(for: url)
72
+ try fileManager.createSymbolicLink(atPath: url.path, withDestinationPath: linkPath)
73
+ }
74
+ checksum = try extract(
75
+ entry,
76
+ bufferSize: bufferSize,
77
+ skipCRC32: skipCRC32,
78
+ progress: progress,
79
+ consumer: consumer)
80
+ }
81
+ let attributes = FileManager.attributes(from: entry)
82
+ try fileManager.setAttributes(attributes, ofItemAtPath: url.path)
83
+ return checksum
84
+ }
85
+
86
+ /// Read a ZIP `Entry` from the receiver and forward its contents to a `Consumer` closure.
87
+ ///
88
+ /// - Parameters:
89
+ /// - entry: The ZIP `Entry` to read.
90
+ /// - bufferSize: The maximum size of the read buffer and the decompression buffer (if needed).
91
+ /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance.
92
+ /// - progress: A progress object that can be used to track or cancel the extract operation.
93
+ /// - consumer: A closure that consumes contents of `Entry` as `Data` chunks.
94
+ /// - Returns: The checksum of the processed content or 0 if the `skipCRC32` flag was set to `true`..
95
+ /// - Throws: An error if the destination file cannot be written or the entry contains malformed content.
96
+ func extract(
97
+ _ entry: Entry,
98
+ bufferSize: Int = defaultReadChunkSize,
99
+ skipCRC32: Bool = false,
100
+ progress: Progress? = nil,
101
+ consumer: Consumer)
102
+ throws -> CRC32
103
+ {
104
+ guard bufferSize > 0 else {
105
+ throw ArchiveError.invalidBufferSize
106
+ }
107
+ var checksum = CRC32(0)
108
+ let localFileHeader = entry.localFileHeader
109
+ guard entry.dataOffset <= .max else { throw ArchiveError.invalidLocalHeaderDataOffset }
110
+ fseeko(archiveFile, off_t(entry.dataOffset), SEEK_SET)
111
+ progress?.totalUnitCount = totalUnitCountForReading(entry)
112
+ switch entry.type {
113
+ case .file:
114
+ guard let compressionMethod = CompressionMethod(rawValue: localFileHeader.compressionMethod) else {
115
+ throw ArchiveError.invalidCompressionMethod
116
+ }
117
+ switch compressionMethod {
118
+ case .none: checksum = try readUncompressed(
119
+ entry: entry,
120
+ bufferSize: bufferSize,
121
+ skipCRC32: skipCRC32,
122
+ progress: progress,
123
+ with: consumer)
124
+ case .deflate: checksum = try readCompressed(
125
+ entry: entry,
126
+ bufferSize: bufferSize,
127
+ skipCRC32: skipCRC32,
128
+ progress: progress,
129
+ with: consumer)
130
+ }
131
+ case .directory:
132
+ try consumer(Data())
133
+ progress?.completedUnitCount = totalUnitCountForReading(entry)
134
+ case .symlink:
135
+ let localFileHeader = entry.localFileHeader
136
+ let size = Int(localFileHeader.compressedSize)
137
+ let data = try Data.readChunk(of: size, from: archiveFile)
138
+ checksum = data.crc32(checksum: 0)
139
+ try consumer(data)
140
+ progress?.completedUnitCount = totalUnitCountForReading(entry)
141
+ }
142
+ return checksum
143
+ }
144
+ }
@@ -0,0 +1,49 @@
1
+ //
2
+ // Archive+ReadingDeprecated.swift
3
+ // ZIPFoundation
4
+ //
5
+ // Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
6
+ // Released under the MIT License.
7
+ //
8
+ // See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
9
+ //
10
+
11
+ import Foundation
12
+
13
+ extension Archive {
14
+
15
+ @available(
16
+ *,
17
+ deprecated,
18
+ message: "Please use `Int` for `bufferSize`.")
19
+ func extract(
20
+ _ entry: Entry,
21
+ to url: URL,
22
+ bufferSize: UInt32,
23
+ skipCRC32: Bool = false,
24
+ progress: Progress? = nil)
25
+ throws -> CRC32
26
+ {
27
+ try extract(entry, to: url, bufferSize: Int(bufferSize), skipCRC32: skipCRC32, progress: progress)
28
+ }
29
+
30
+ @available(
31
+ *,
32
+ deprecated,
33
+ message: "Please use `Int` for `bufferSize`.")
34
+ func extract(
35
+ _ entry: Entry,
36
+ bufferSize: UInt32,
37
+ skipCRC32: Bool = false,
38
+ progress: Progress? = nil,
39
+ consumer: Consumer)
40
+ throws -> CRC32
41
+ {
42
+ try extract(
43
+ entry,
44
+ bufferSize: Int(bufferSize),
45
+ skipCRC32: skipCRC32,
46
+ progress: progress,
47
+ consumer: consumer)
48
+ }
49
+ }
@@ -0,0 +1,385 @@
1
+ //
2
+ // Archive+Writing.swift
3
+ // ZIPFoundation
4
+ //
5
+ // Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
6
+ // Released under the MIT License.
7
+ //
8
+ // See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
9
+ //
10
+
11
+ import Foundation
12
+
13
+ extension Archive {
14
+ enum ModifyOperation: Int {
15
+ case remove = -1
16
+ case add = 1
17
+ }
18
+
19
+ typealias EndOfCentralDirectoryStructure = (EndOfCentralDirectoryRecord, ZIP64EndOfCentralDirectory?)
20
+
21
+ /// Write files, directories or symlinks to the receiver.
22
+ ///
23
+ /// - Parameters:
24
+ /// - path: The path that is used to identify an `Entry` within the `Archive` file.
25
+ /// - baseURL: The base URL of the resource to add.
26
+ /// The `baseURL` combined with `path` must form a fully qualified file URL.
27
+ /// - compressionMethod: Indicates the `CompressionMethod` that should be applied to `Entry`.
28
+ /// By default, no compression will be applied.
29
+ /// - bufferSize: The maximum size of the write buffer and the compression buffer (if needed).
30
+ /// - progress: A progress object that can be used to track or cancel the add operation.
31
+ /// - Throws: An error if the source file cannot be read or the receiver is not writable.
32
+ func addEntry(
33
+ with path: String,
34
+ relativeTo baseURL: URL,
35
+ compressionMethod: CompressionMethod = .none,
36
+ bufferSize: Int = defaultWriteChunkSize,
37
+ progress: Progress? = nil)
38
+ throws
39
+ {
40
+ let fileURL = baseURL.appendingPathComponent(path)
41
+
42
+ try addEntry(
43
+ with: path,
44
+ fileURL: fileURL,
45
+ compressionMethod: compressionMethod,
46
+ bufferSize: bufferSize,
47
+ progress: progress)
48
+ }
49
+
50
+ /// Write files, directories or symlinks to the receiver.
51
+ ///
52
+ /// - Parameters:
53
+ /// - path: The path that is used to identify an `Entry` within the `Archive` file.
54
+ /// - fileURL: An absolute file URL referring to the resource to add.
55
+ /// - compressionMethod: Indicates the `CompressionMethod` that should be applied to `Entry`.
56
+ /// By default, no compression will be applied.
57
+ /// - bufferSize: The maximum size of the write buffer and the compression buffer (if needed).
58
+ /// - progress: A progress object that can be used to track or cancel the add operation.
59
+ /// - Throws: An error if the source file cannot be read or the receiver is not writable.
60
+ func addEntry(
61
+ with path: String,
62
+ fileURL: URL,
63
+ compressionMethod: CompressionMethod = .none,
64
+ bufferSize: Int = defaultWriteChunkSize,
65
+ progress: Progress? = nil)
66
+ throws
67
+ {
68
+ let fileManager = FileManager()
69
+ guard fileManager.itemExists(at: fileURL) else {
70
+ throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: fileURL.path])
71
+ }
72
+ let type = try FileManager.typeForItem(at: fileURL)
73
+ // symlinks do not need to be readable
74
+ guard type == .symlink || fileManager.isReadableFile(atPath: fileURL.path) else {
75
+ throw CocoaError(.fileReadNoPermission, userInfo: [NSFilePathErrorKey: url.path])
76
+ }
77
+ let modDate = try FileManager.fileModificationDateTimeForItem(at: fileURL)
78
+ let uncompressedSize = type == .directory ? 0 : try FileManager.fileSizeForItem(at: fileURL)
79
+ let permissions = try FileManager.permissionsForItem(at: fileURL)
80
+ var provider: Provider
81
+ switch type {
82
+ case .file:
83
+ let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: fileURL.path)
84
+ guard let entryFile: FILEPointer = fopen(entryFileSystemRepresentation, "rb") else {
85
+ throw CocoaError(.fileNoSuchFile)
86
+ }
87
+ defer { fclose(entryFile) }
88
+ provider = { _, _ in try Data.readChunk(of: bufferSize, from: entryFile) }
89
+ try addEntry(
90
+ with: path,
91
+ type: type,
92
+ uncompressedSize: uncompressedSize,
93
+ modificationDate: modDate,
94
+ permissions: permissions,
95
+ compressionMethod: compressionMethod,
96
+ bufferSize: bufferSize,
97
+ progress: progress,
98
+ provider: provider)
99
+ case .directory:
100
+ provider = { _, _ in Data() }
101
+ try addEntry(
102
+ with: path.hasSuffix("/") ? path : path + "/",
103
+ type: type,
104
+ uncompressedSize: uncompressedSize,
105
+ modificationDate: modDate,
106
+ permissions: permissions,
107
+ compressionMethod: compressionMethod,
108
+ bufferSize: bufferSize,
109
+ progress: progress,
110
+ provider: provider)
111
+ case .symlink:
112
+ provider = { _, _ -> Data in
113
+ let linkDestination = try fileManager.destinationOfSymbolicLink(atPath: fileURL.path)
114
+ let linkFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: linkDestination)
115
+ let linkLength = Int(strlen(linkFileSystemRepresentation))
116
+ let linkBuffer = UnsafeBufferPointer(start: linkFileSystemRepresentation, count: linkLength)
117
+ return Data(buffer: linkBuffer)
118
+ }
119
+ try addEntry(
120
+ with: path,
121
+ type: type,
122
+ uncompressedSize: uncompressedSize,
123
+ modificationDate: modDate,
124
+ permissions: permissions,
125
+ compressionMethod: compressionMethod,
126
+ bufferSize: bufferSize,
127
+ progress: progress,
128
+ provider: provider)
129
+ }
130
+ }
131
+
132
+ /// Write files, directories or symlinks to the receiver.
133
+ ///
134
+ /// - Parameters:
135
+ /// - path: The path that is used to identify an `Entry` within the `Archive` file.
136
+ /// - type: Indicates the `Entry.EntryType` of the added content.
137
+ /// - uncompressedSize: The uncompressed size of the data that is going to be added with `provider`.
138
+ /// - modificationDate: A `Date` describing the file modification date of the `Entry`.
139
+ /// Default is the current `Date`.
140
+ /// - permissions: POSIX file permissions for the `Entry`.
141
+ /// Default is `0`o`644` for files and symlinks and `0`o`755` for directories.
142
+ /// - compressionMethod: Indicates the `CompressionMethod` that should be applied to `Entry`.
143
+ /// By default, no compression will be applied.
144
+ /// - bufferSize: The maximum size of the write buffer and the compression buffer (if needed).
145
+ /// - progress: A progress object that can be used to track or cancel the add operation.
146
+ /// - provider: A closure that accepts a position and a chunk size. Returns a `Data` chunk.
147
+ /// - Throws: An error if the source data is invalid or the receiver is not writable.
148
+ func addEntry(
149
+ with path: String,
150
+ type: Entry.EntryType,
151
+ uncompressedSize: Int64,
152
+ modificationDate: Date = Date(),
153
+ permissions: UInt16? = nil,
154
+ compressionMethod: CompressionMethod = .none,
155
+ bufferSize: Int = defaultWriteChunkSize,
156
+ progress: Progress? = nil,
157
+ provider: Provider)
158
+ throws
159
+ {
160
+ guard accessMode != .read else { throw ArchiveError.unwritableArchive }
161
+ // Directories and symlinks cannot be compressed
162
+ let compressionMethod = type == .file ? compressionMethod : .none
163
+ progress?.totalUnitCount = type == .directory ? defaultDirectoryUnitCount : uncompressedSize
164
+ let (eocdRecord, zip64EOCD) = (endOfCentralDirectoryRecord, zip64EndOfCentralDirectory)
165
+ guard offsetToStartOfCentralDirectory <= .max else { throw ArchiveError.invalidCentralDirectoryOffset }
166
+ var startOfCD = Int64(offsetToStartOfCentralDirectory)
167
+ fseeko(archiveFile, off_t(startOfCD), SEEK_SET)
168
+ let existingSize = sizeOfCentralDirectory
169
+ let existingData = try Data.readChunk(of: Int(existingSize), from: archiveFile)
170
+ fseeko(archiveFile, off_t(startOfCD), SEEK_SET)
171
+ let fileHeaderStart = Int64(ftello(archiveFile))
172
+ let modDateTime = modificationDate.fileModificationDateTime
173
+ defer { fflush(self.archiveFile) }
174
+ do {
175
+ // Local File Header
176
+ var localFileHeader = try writeLocalFileHeader(
177
+ path: path,
178
+ compressionMethod: compressionMethod,
179
+ size: (UInt64(uncompressedSize), 0),
180
+ checksum: 0,
181
+ modificationDateTime: modDateTime)
182
+ // File Data
183
+ let (written, checksum) = try writeEntry(
184
+ uncompressedSize: uncompressedSize,
185
+ type: type,
186
+ compressionMethod: compressionMethod,
187
+ bufferSize: bufferSize,
188
+ progress: progress,
189
+ provider: provider)
190
+ startOfCD = Int64(ftello(archiveFile))
191
+ // Write the local file header a second time. Now with compressedSize (if applicable) and a valid checksum.
192
+ fseeko(archiveFile, off_t(fileHeaderStart), SEEK_SET)
193
+ localFileHeader = try writeLocalFileHeader(
194
+ path: path,
195
+ compressionMethod: compressionMethod,
196
+ size: (UInt64(uncompressedSize), UInt64(written)),
197
+ checksum: checksum,
198
+ modificationDateTime: modDateTime)
199
+ // Central Directory
200
+ fseeko(archiveFile, off_t(startOfCD), SEEK_SET)
201
+ _ = try Data.writeLargeChunk(existingData, size: existingSize, bufferSize: bufferSize, to: archiveFile)
202
+ let permissions = permissions ?? (type == .directory ? defaultDirectoryPermissions : defaultFilePermissions)
203
+ let externalAttributes = FileManager.externalFileAttributesForEntry(of: type, permissions: permissions)
204
+ let centralDir = try writeCentralDirectoryStructure(
205
+ localFileHeader: localFileHeader,
206
+ relativeOffset: UInt64(fileHeaderStart),
207
+ externalFileAttributes: externalAttributes)
208
+ // End of Central Directory Record (including ZIP64 End of Central Directory Record/Locator)
209
+ let startOfEOCD = UInt64(ftello(archiveFile))
210
+ let eocd = try writeEndOfCentralDirectory(
211
+ centralDirectoryStructure: centralDir,
212
+ startOfCentralDirectory: UInt64(startOfCD),
213
+ startOfEndOfCentralDirectory: startOfEOCD,
214
+ operation: .add)
215
+ (endOfCentralDirectoryRecord, zip64EndOfCentralDirectory) = eocd
216
+ } catch ArchiveError.cancelledOperation {
217
+ try rollback(UInt64(fileHeaderStart), (existingData, existingSize), bufferSize, eocdRecord, zip64EOCD)
218
+ throw ArchiveError.cancelledOperation
219
+ }
220
+ }
221
+
222
+ /// Remove a ZIP `Entry` from the receiver.
223
+ ///
224
+ /// - Parameters:
225
+ /// - entry: The `Entry` to remove.
226
+ /// - bufferSize: The maximum size for the read and write buffers used during removal.
227
+ /// - progress: A progress object that can be used to track or cancel the remove operation.
228
+ /// - Throws: An error if the `Entry` is malformed or the receiver is not writable.
229
+ func remove(_ entry: Entry, bufferSize: Int = defaultReadChunkSize, progress: Progress? = nil) throws {
230
+ guard accessMode != .read else { throw ArchiveError.unwritableArchive }
231
+ let (tempArchive, tempDir) = try makeTempArchive()
232
+ defer { tempDir.map { try? FileManager().removeItem(at: $0) } }
233
+ progress?.totalUnitCount = totalUnitCountForRemoving(entry)
234
+ var centralDirectoryData = Data()
235
+ var offset: UInt64 = 0
236
+ for currentEntry in self {
237
+ let cds = currentEntry.centralDirectoryStructure
238
+ if currentEntry != entry {
239
+ let entryStart = cds.effectiveRelativeOffsetOfLocalHeader
240
+ fseeko(archiveFile, off_t(entryStart), SEEK_SET)
241
+ let provider: Provider = { _, chunkSize -> Data in
242
+ try Data.readChunk(of: chunkSize, from: self.archiveFile)
243
+ }
244
+ let consumer: Consumer = {
245
+ if progress?.isCancelled == true { throw ArchiveError.cancelledOperation }
246
+ _ = try Data.write(chunk: $0, to: tempArchive.archiveFile)
247
+ progress?.completedUnitCount += Int64($0.count)
248
+ }
249
+ guard currentEntry.localSize <= .max else { throw ArchiveError.invalidLocalHeaderSize }
250
+ _ = try Data.consumePart(
251
+ of: Int64(currentEntry.localSize),
252
+ chunkSize: bufferSize,
253
+ provider: provider,
254
+ consumer: consumer)
255
+ let updatedCentralDirectory = updateOffsetInCentralDirectory(
256
+ centralDirectoryStructure: cds,
257
+ updatedOffset: entryStart - offset)
258
+ centralDirectoryData.append(updatedCentralDirectory.data)
259
+ } else { offset = currentEntry.localSize }
260
+ }
261
+ let startOfCentralDirectory = UInt64(ftello(tempArchive.archiveFile))
262
+ _ = try Data.write(chunk: centralDirectoryData, to: tempArchive.archiveFile)
263
+ let startOfEndOfCentralDirectory = UInt64(ftello(tempArchive.archiveFile))
264
+ tempArchive.endOfCentralDirectoryRecord = endOfCentralDirectoryRecord
265
+ tempArchive.zip64EndOfCentralDirectory = zip64EndOfCentralDirectory
266
+ let ecodStructure = try
267
+ tempArchive.writeEndOfCentralDirectory(
268
+ centralDirectoryStructure: entry.centralDirectoryStructure,
269
+ startOfCentralDirectory: startOfCentralDirectory,
270
+ startOfEndOfCentralDirectory: startOfEndOfCentralDirectory,
271
+ operation: .remove)
272
+ (tempArchive.endOfCentralDirectoryRecord, tempArchive.zip64EndOfCentralDirectory) = ecodStructure
273
+ (endOfCentralDirectoryRecord, zip64EndOfCentralDirectory) = ecodStructure
274
+ fflush(tempArchive.archiveFile)
275
+ try replaceCurrentArchive(with: tempArchive)
276
+ }
277
+
278
+ func replaceCurrentArchive(with archive: Archive) throws {
279
+ fclose(archiveFile)
280
+ if isMemoryArchive {
281
+ #if swift(>=5.0)
282
+ guard
283
+ let data = archive.data,
284
+ let config = Archive.makeBackingConfiguration(for: data, mode: .update) else
285
+ {
286
+ throw ArchiveError.unwritableArchive
287
+ }
288
+ archiveFile = config.file
289
+ memoryFile = config.memoryFile
290
+ endOfCentralDirectoryRecord = config.endOfCentralDirectoryRecord
291
+ zip64EndOfCentralDirectory = config.zip64EndOfCentralDirectory
292
+ #endif
293
+ } else {
294
+ let fileManager = FileManager()
295
+ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
296
+ do {
297
+ _ = try fileManager.replaceItemAt(url, withItemAt: archive.url)
298
+ } catch {
299
+ _ = try fileManager.removeItem(at: url)
300
+ _ = try fileManager.moveItem(at: archive.url, to: url)
301
+ }
302
+ #else
303
+ _ = try fileManager.removeItem(at: url)
304
+ _ = try fileManager.moveItem(at: archive.url, to: url)
305
+ #endif
306
+ let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
307
+ guard let file = fopen(fileSystemRepresentation, "rb+") else { throw ArchiveError.unreadableArchive }
308
+ archiveFile = file
309
+ }
310
+ }
311
+ }
312
+
313
+ // MARK: - Private
314
+
315
+ extension Archive {
316
+
317
+ private func updateOffsetInCentralDirectory(
318
+ centralDirectoryStructure: CentralDirectoryStructure,
319
+ updatedOffset: UInt64)
320
+ -> CentralDirectoryStructure
321
+ {
322
+ let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation(
323
+ zip64ExtendedInformation: centralDirectoryStructure.zip64ExtendedInformation, offset: updatedOffset)
324
+ let offsetInCD = updatedOffset < maxOffsetOfLocalFileHeader ? UInt32(updatedOffset) : UInt32.max
325
+ return CentralDirectoryStructure(
326
+ centralDirectoryStructure: centralDirectoryStructure,
327
+ zip64ExtendedInformation: zip64ExtendedInformation,
328
+ relativeOffset: offsetInCD)
329
+ }
330
+
331
+ private func rollback(
332
+ _ localFileHeaderStart: UInt64,
333
+ _ existingCentralDirectory: (data: Data, size: UInt64),
334
+ _ bufferSize: Int,
335
+ _ endOfCentralDirRecord: EndOfCentralDirectoryRecord,
336
+ _ zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory?)
337
+ throws
338
+ {
339
+ fflush(archiveFile)
340
+ ftruncate(fileno(archiveFile), off_t(localFileHeaderStart))
341
+ fseeko(archiveFile, off_t(localFileHeaderStart), SEEK_SET)
342
+ _ = try Data.writeLargeChunk(
343
+ existingCentralDirectory.data,
344
+ size: existingCentralDirectory.size,
345
+ bufferSize: bufferSize,
346
+ to: archiveFile)
347
+ _ = try Data.write(chunk: existingCentralDirectory.data, to: archiveFile)
348
+ if let zip64EOCD = zip64EndOfCentralDirectory {
349
+ _ = try Data.write(chunk: zip64EOCD.data, to: archiveFile)
350
+ }
351
+ _ = try Data.write(chunk: endOfCentralDirRecord.data, to: archiveFile)
352
+ }
353
+
354
+ private func makeTempArchive() throws -> (Archive, URL?) {
355
+ var archive: Archive
356
+ var url: URL?
357
+ if isMemoryArchive {
358
+ #if swift(>=5.0)
359
+ guard
360
+ let tempArchive = Archive(
361
+ data: Data(),
362
+ accessMode: .create,
363
+ preferredEncoding: preferredEncoding) else
364
+ {
365
+ throw ArchiveError.unwritableArchive
366
+ }
367
+ archive = tempArchive
368
+ #else
369
+ fatalError("Memory archives are unsupported.")
370
+ #endif
371
+ } else {
372
+ let manager = FileManager()
373
+ let tempDir = URL.temporaryReplacementDirectoryURL(for: self)
374
+ let uniqueString = ProcessInfo.processInfo.globallyUniqueString
375
+ let tempArchiveURL = tempDir.appendingPathComponent(uniqueString)
376
+ try manager.createParentDirectoryStructure(for: tempArchiveURL)
377
+ guard let tempArchive = Archive(url: tempArchiveURL, accessMode: .create) else {
378
+ throw ArchiveError.unwritableArchive
379
+ }
380
+ archive = tempArchive
381
+ url = tempDir
382
+ }
383
+ return (archive, url)
384
+ }
385
+ }
@@ -0,0 +1,91 @@
1
+ //
2
+ // Archive+WritingDeprecated.swift
3
+ // ZIPFoundation
4
+ //
5
+ // Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
6
+ // Released under the MIT License.
7
+ //
8
+ // See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
9
+ //
10
+
11
+ import Foundation
12
+
13
+ extension Archive {
14
+
15
+ @available(
16
+ *,
17
+ deprecated,
18
+ message: "Please use `Int` for `bufferSize`.")
19
+ func addEntry(
20
+ with path: String,
21
+ relativeTo baseURL: URL,
22
+ compressionMethod: CompressionMethod = .none,
23
+ bufferSize: UInt32,
24
+ progress: Progress? = nil)
25
+ throws
26
+ {
27
+ try addEntry(
28
+ with: path,
29
+ relativeTo: baseURL,
30
+ compressionMethod: compressionMethod,
31
+ bufferSize: Int(bufferSize),
32
+ progress: progress)
33
+ }
34
+
35
+ @available(
36
+ *,
37
+ deprecated,
38
+ message: "Please use `Int` for `bufferSize`.")
39
+ func addEntry(
40
+ with path: String,
41
+ fileURL: URL,
42
+ compressionMethod: CompressionMethod = .none,
43
+ bufferSize: UInt32,
44
+ progress: Progress? = nil)
45
+ throws
46
+ {
47
+ try addEntry(
48
+ with: path,
49
+ fileURL: fileURL,
50
+ compressionMethod: compressionMethod,
51
+ bufferSize: Int(bufferSize),
52
+ progress: progress)
53
+ }
54
+
55
+ @available(
56
+ *,
57
+ deprecated,
58
+ message: "Please use `Int64` for `uncompressedSize` and provider `position`. `Int` for `bufferSize`.")
59
+ func addEntry(
60
+ with path: String,
61
+ type: Entry.EntryType,
62
+ uncompressedSize: UInt32,
63
+ modificationDate: Date = Date(),
64
+ permissions: UInt16? = nil,
65
+ compressionMethod: CompressionMethod = .none,
66
+ bufferSize: Int = defaultWriteChunkSize,
67
+ progress: Progress? = nil,
68
+ provider: (_ position: Int, _ size: Int) throws -> Data)
69
+ throws
70
+ {
71
+ let newProvider: Provider = { try provider(Int($0), $1) }
72
+ try addEntry(
73
+ with: path,
74
+ type: type,
75
+ uncompressedSize: Int64(uncompressedSize),
76
+ modificationDate: modificationDate,
77
+ permissions: permissions,
78
+ compressionMethod: compressionMethod,
79
+ bufferSize: bufferSize,
80
+ progress: progress,
81
+ provider: newProvider)
82
+ }
83
+
84
+ @available(
85
+ *,
86
+ deprecated,
87
+ message: "Please use `Int` for `bufferSize`.")
88
+ func remove(_ entry: Entry, bufferSize: UInt32, progress: Progress? = nil) throws {
89
+ try remove(entry, bufferSize: Int(bufferSize), progress: progress)
90
+ }
91
+ }