react-native-nitro-unzip 0.1.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.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/android/CMakeLists.txt +16 -0
- package/android/build.gradle +70 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/unzip/HybridUnzip.kt +29 -0
- package/android/src/main/java/com/margelo/nitro/unzip/HybridUnzipTask.kt +256 -0
- package/android/src/main/java/com/margelo/nitro/unzip/HybridZipTask.kt +157 -0
- package/ios/HybridUnzip.swift +33 -0
- package/ios/HybridUnzipTask.swift +238 -0
- package/ios/HybridZipTask.swift +266 -0
- package/lib/commonjs/index.js +31 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/specs/Unzip.nitro.js +6 -0
- package/lib/commonjs/specs/Unzip.nitro.js.map +1 -0
- package/lib/module/index.js +27 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/Unzip.nitro.js +4 -0
- package/lib/module/specs/Unzip.nitro.js.map +1 -0
- package/lib/typescript/index.d.ts +24 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/specs/Unzip.nitro.d.ts +170 -0
- package/lib/typescript/specs/Unzip.nitro.d.ts.map +1 -0
- package/nitro.json +26 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroUnzip+autolinking.cmake +85 -0
- package/nitrogen/generated/android/NitroUnzip+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroUnzipOnLoad.cpp +71 -0
- package/nitrogen/generated/android/NitroUnzipOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JFunc_void_UnzipProgress.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_ZipProgress.hpp +77 -0
- package/nitrogen/generated/android/c++/JHybridUnzipSpec.cpp +82 -0
- package/nitrogen/generated/android/c++/JHybridUnzipSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JHybridUnzipTaskSpec.cpp +94 -0
- package/nitrogen/generated/android/c++/JHybridUnzipTaskSpec.hpp +68 -0
- package/nitrogen/generated/android/c++/JHybridZipTaskSpec.cpp +94 -0
- package/nitrogen/generated/android/c++/JHybridZipTaskSpec.hpp +68 -0
- package/nitrogen/generated/android/c++/JUnzipProgress.hpp +73 -0
- package/nitrogen/generated/android/c++/JUnzipResult.hpp +73 -0
- package/nitrogen/generated/android/c++/JZipProgress.hpp +69 -0
- package/nitrogen/generated/android/c++/JZipResult.hpp +73 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/Func_void_UnzipProgress.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/Func_void_ZipProgress.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/HybridUnzipSpec.kt +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/HybridUnzipTaskSpec.kt +73 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/HybridZipTaskSpec.kt +73 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/NitroUnzipOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/UnzipProgress.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/UnzipResult.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/ZipProgress.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/unzip/ZipResult.kt +50 -0
- package/nitrogen/generated/ios/NitroUnzip+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroUnzip-Swift-Cxx-Bridge.cpp +107 -0
- package/nitrogen/generated/ios/NitroUnzip-Swift-Cxx-Bridge.hpp +270 -0
- package/nitrogen/generated/ios/NitroUnzip-Swift-Cxx-Umbrella.hpp +68 -0
- package/nitrogen/generated/ios/NitroUnzipAutolinking.mm +49 -0
- package/nitrogen/generated/ios/NitroUnzipAutolinking.swift +50 -0
- package/nitrogen/generated/ios/c++/HybridUnzipSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridUnzipSpecSwift.hpp +112 -0
- package/nitrogen/generated/ios/c++/HybridUnzipTaskSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridUnzipTaskSpecSwift.hpp +104 -0
- package/nitrogen/generated/ios/c++/HybridZipTaskSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridZipTaskSpecSwift.hpp +104 -0
- package/nitrogen/generated/ios/swift/Func_void_UnzipProgress.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_UnzipResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_ZipProgress.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_ZipResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridUnzipSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridUnzipSpec_cxx.swift +186 -0
- package/nitrogen/generated/ios/swift/HybridUnzipTaskSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridUnzipTaskSpec_cxx.swift +177 -0
- package/nitrogen/generated/ios/swift/HybridZipTaskSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridZipTaskSpec_cxx.swift +177 -0
- package/nitrogen/generated/ios/swift/UnzipProgress.swift +49 -0
- package/nitrogen/generated/ios/swift/UnzipResult.swift +49 -0
- package/nitrogen/generated/ios/swift/ZipProgress.swift +44 -0
- package/nitrogen/generated/ios/swift/ZipResult.swift +49 -0
- package/nitrogen/generated/shared/c++/HybridUnzipSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridUnzipSpec.hpp +71 -0
- package/nitrogen/generated/shared/c++/HybridUnzipTaskSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridUnzipTaskSpec.hpp +71 -0
- package/nitrogen/generated/shared/c++/HybridZipTaskSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridZipTaskSpec.hpp +71 -0
- package/nitrogen/generated/shared/c++/UnzipProgress.hpp +99 -0
- package/nitrogen/generated/shared/c++/UnzipResult.hpp +99 -0
- package/nitrogen/generated/shared/c++/ZipProgress.hpp +95 -0
- package/nitrogen/generated/shared/c++/ZipResult.hpp +99 -0
- package/package.json +165 -0
- package/react-native-nitro-unzip.podspec +24 -0
- package/src/index.ts +36 -0
- package/src/specs/Unzip.nitro.ts +193 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import SSZipArchive
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A single extraction operation as a proper HybridObject instance.
|
|
8
|
+
*
|
|
9
|
+
* Each call to `HybridUnzip.extract()` creates one of these.
|
|
10
|
+
* The caller can observe progress, cancel, or await the result — all scoped
|
|
11
|
+
* to this specific extraction, no global event emitters needed.
|
|
12
|
+
*
|
|
13
|
+
* Performance (350MB archive, 10k+ files):
|
|
14
|
+
* - Speed: 400-500 files/second
|
|
15
|
+
* - Memory: <30MB peak via SSZipArchive streaming
|
|
16
|
+
* - Background task support for continued extraction when app is backgrounded
|
|
17
|
+
*/
|
|
18
|
+
class HybridUnzipTask: HybridUnzipTaskSpec {
|
|
19
|
+
// MARK: - HybridObject requirements
|
|
20
|
+
|
|
21
|
+
var hybridContext = margelo.nitro.HybridContext()
|
|
22
|
+
var memorySize: Int { return getSizeOf(self) }
|
|
23
|
+
|
|
24
|
+
// MARK: - Spec properties
|
|
25
|
+
|
|
26
|
+
let taskId: String
|
|
27
|
+
|
|
28
|
+
// MARK: - Internal state
|
|
29
|
+
|
|
30
|
+
private let zipPath: String
|
|
31
|
+
private let destinationPath: String
|
|
32
|
+
private let password: String?
|
|
33
|
+
private var progressCallback: ((_ progress: UnzipProgress) -> Void)?
|
|
34
|
+
private var shouldCancel = false
|
|
35
|
+
private var backgroundTaskId: UIBackgroundTaskIdentifier = .invalid
|
|
36
|
+
private let lock = NSLock()
|
|
37
|
+
private let progressThrottle: TimeInterval = 1.0
|
|
38
|
+
|
|
39
|
+
// Promise resolution — held until extraction completes or fails
|
|
40
|
+
private var resolvePromise: ((UnzipResult) -> Void)?
|
|
41
|
+
private var rejectPromise: ((Error) -> Void)?
|
|
42
|
+
private var hasStarted = false
|
|
43
|
+
|
|
44
|
+
init(zipPath: String, destinationPath: String, password: String? = nil) {
|
|
45
|
+
self.taskId = "unzip_\(ProcessInfo.processInfo.globallyUniqueString)"
|
|
46
|
+
self.zipPath = zipPath
|
|
47
|
+
self.destinationPath = destinationPath
|
|
48
|
+
self.password = password
|
|
49
|
+
super.init()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: - Spec methods
|
|
53
|
+
|
|
54
|
+
func onProgress(callback: @escaping (_ progress: UnzipProgress) -> Void) throws {
|
|
55
|
+
lock.lock()
|
|
56
|
+
self.progressCallback = callback
|
|
57
|
+
lock.unlock()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func cancel() throws {
|
|
61
|
+
lock.lock()
|
|
62
|
+
shouldCancel = true
|
|
63
|
+
lock.unlock()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func await() throws -> Promise<UnzipResult> {
|
|
67
|
+
return Promise.async { [self] resolve, reject in
|
|
68
|
+
self.lock.lock()
|
|
69
|
+
self.resolvePromise = resolve
|
|
70
|
+
self.rejectPromise = reject
|
|
71
|
+
|
|
72
|
+
guard !self.hasStarted else {
|
|
73
|
+
self.lock.unlock()
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
self.hasStarted = true
|
|
77
|
+
self.lock.unlock()
|
|
78
|
+
|
|
79
|
+
self.startExtraction()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// MARK: - Extraction
|
|
84
|
+
|
|
85
|
+
private func startExtraction() {
|
|
86
|
+
beginBackgroundTask()
|
|
87
|
+
|
|
88
|
+
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
|
89
|
+
do {
|
|
90
|
+
let result = try self.extract()
|
|
91
|
+
self.endBackgroundTask()
|
|
92
|
+
self.lock.lock()
|
|
93
|
+
let resolve = self.resolvePromise
|
|
94
|
+
self.lock.unlock()
|
|
95
|
+
resolve?(result)
|
|
96
|
+
} catch {
|
|
97
|
+
self.endBackgroundTask()
|
|
98
|
+
self.lock.lock()
|
|
99
|
+
let reject = self.rejectPromise
|
|
100
|
+
self.lock.unlock()
|
|
101
|
+
reject?(error)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func extract() throws -> UnzipResult {
|
|
107
|
+
let startTime = Date()
|
|
108
|
+
let fileManager = FileManager.default
|
|
109
|
+
|
|
110
|
+
// Normalise file:// URIs to filesystem paths
|
|
111
|
+
let cleanZip = zipPath.replacingOccurrences(of: "file://", with: "")
|
|
112
|
+
let cleanDest = destinationPath.replacingOccurrences(of: "file://", with: "")
|
|
113
|
+
|
|
114
|
+
// Ensure destination exists
|
|
115
|
+
if !fileManager.fileExists(atPath: cleanDest) {
|
|
116
|
+
try fileManager.createDirectory(
|
|
117
|
+
atPath: cleanDest,
|
|
118
|
+
withIntermediateDirectories: true,
|
|
119
|
+
attributes: nil
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Verify source exists
|
|
124
|
+
guard fileManager.fileExists(atPath: cleanZip) else {
|
|
125
|
+
throw NSError(
|
|
126
|
+
domain: "NitroUnzip",
|
|
127
|
+
code: 1,
|
|
128
|
+
userInfo: [NSLocalizedDescriptionKey: "Source ZIP file not found: \(cleanZip)"]
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var extractedFiles = 0
|
|
133
|
+
var lastProgressUpdate = Date()
|
|
134
|
+
|
|
135
|
+
// SSZipArchive progress handler — called per file
|
|
136
|
+
let progressHandler: (String, unz_file_info, Int, Int) -> Void = { [weak self] _, _, entryNumber, total in
|
|
137
|
+
guard let self = self else { return }
|
|
138
|
+
|
|
139
|
+
// Check cancellation
|
|
140
|
+
self.lock.lock()
|
|
141
|
+
let cancelled = self.shouldCancel
|
|
142
|
+
let callback = self.progressCallback
|
|
143
|
+
self.lock.unlock()
|
|
144
|
+
if cancelled { return }
|
|
145
|
+
|
|
146
|
+
extractedFiles = entryNumber
|
|
147
|
+
|
|
148
|
+
// Throttle progress updates
|
|
149
|
+
let now = Date()
|
|
150
|
+
let shouldUpdate = now.timeIntervalSince(lastProgressUpdate) >= self.progressThrottle
|
|
151
|
+
|| entryNumber == total
|
|
152
|
+
|| entryNumber == 1
|
|
153
|
+
|
|
154
|
+
if shouldUpdate, let callback = callback {
|
|
155
|
+
let progress = total > 0 ? Double(entryNumber) / Double(total) : 0
|
|
156
|
+
let elapsed = now.timeIntervalSince(startTime)
|
|
157
|
+
let speed = elapsed > 0 ? Double(entryNumber) / elapsed : 0
|
|
158
|
+
|
|
159
|
+
callback(UnzipProgress(
|
|
160
|
+
extractedFiles: Double(entryNumber),
|
|
161
|
+
totalFiles: Double(total),
|
|
162
|
+
progress: progress,
|
|
163
|
+
speed: speed,
|
|
164
|
+
processedBytes: 0
|
|
165
|
+
))
|
|
166
|
+
lastProgressUpdate = now
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Run extraction
|
|
171
|
+
let success = SSZipArchive.unzipFile(
|
|
172
|
+
atPath: cleanZip,
|
|
173
|
+
toDestination: cleanDest,
|
|
174
|
+
overwrite: true,
|
|
175
|
+
password: password,
|
|
176
|
+
progressHandler: progressHandler,
|
|
177
|
+
completionHandler: nil
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// Check cancellation
|
|
181
|
+
lock.lock()
|
|
182
|
+
let wasCancelled = shouldCancel
|
|
183
|
+
lock.unlock()
|
|
184
|
+
|
|
185
|
+
if wasCancelled {
|
|
186
|
+
throw NSError(
|
|
187
|
+
domain: "NitroUnzip",
|
|
188
|
+
code: 2,
|
|
189
|
+
userInfo: [NSLocalizedDescriptionKey: "Extraction cancelled"]
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
guard success else {
|
|
194
|
+
throw NSError(
|
|
195
|
+
domain: "NitroUnzip",
|
|
196
|
+
code: 3,
|
|
197
|
+
userInfo: [NSLocalizedDescriptionKey: "SSZipArchive extraction failed"]
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let duration = Date().timeIntervalSince(startTime) * 1000 // ms
|
|
202
|
+
let finalCount = extractedFiles
|
|
203
|
+
let averageSpeed = duration > 0 ? Double(finalCount) / (duration / 1000) : 0
|
|
204
|
+
|
|
205
|
+
return UnzipResult(
|
|
206
|
+
success: true,
|
|
207
|
+
extractedFiles: Double(finalCount),
|
|
208
|
+
duration: duration,
|
|
209
|
+
averageSpeed: averageSpeed,
|
|
210
|
+
totalBytes: 0
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// MARK: - Background task management
|
|
215
|
+
|
|
216
|
+
private func beginBackgroundTask() {
|
|
217
|
+
let bgId = UIApplication.shared.beginBackgroundTask(withName: "NitroUnzip-\(taskId)") { [weak self] in
|
|
218
|
+
self?.lock.lock()
|
|
219
|
+
self?.shouldCancel = true
|
|
220
|
+
self?.lock.unlock()
|
|
221
|
+
self?.endBackgroundTask()
|
|
222
|
+
}
|
|
223
|
+
lock.lock()
|
|
224
|
+
backgroundTaskId = bgId
|
|
225
|
+
lock.unlock()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private func endBackgroundTask() {
|
|
229
|
+
lock.lock()
|
|
230
|
+
let bgId = backgroundTaskId
|
|
231
|
+
backgroundTaskId = .invalid
|
|
232
|
+
lock.unlock()
|
|
233
|
+
|
|
234
|
+
if bgId != .invalid {
|
|
235
|
+
UIApplication.shared.endBackgroundTask(bgId)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import SSZipArchive
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A single zip creation operation as a proper HybridObject instance.
|
|
8
|
+
*
|
|
9
|
+
* Each call to `HybridUnzip.zip()` creates one of these.
|
|
10
|
+
* The caller can observe progress, cancel, or await the result.
|
|
11
|
+
*/
|
|
12
|
+
class HybridZipTask: HybridZipTaskSpec {
|
|
13
|
+
// MARK: - HybridObject requirements
|
|
14
|
+
|
|
15
|
+
var hybridContext = margelo.nitro.HybridContext()
|
|
16
|
+
var memorySize: Int { return getSizeOf(self) }
|
|
17
|
+
|
|
18
|
+
// MARK: - Spec properties
|
|
19
|
+
|
|
20
|
+
let taskId: String
|
|
21
|
+
|
|
22
|
+
// MARK: - Internal state
|
|
23
|
+
|
|
24
|
+
private let sourcePath: String
|
|
25
|
+
private let destinationZipPath: String
|
|
26
|
+
private let password: String?
|
|
27
|
+
private var progressCallback: ((_ progress: ZipProgress) -> Void)?
|
|
28
|
+
private var shouldCancel = false
|
|
29
|
+
private var backgroundTaskId: UIBackgroundTaskIdentifier = .invalid
|
|
30
|
+
private let lock = NSLock()
|
|
31
|
+
private let progressThrottle: TimeInterval = 1.0
|
|
32
|
+
|
|
33
|
+
private var resolvePromise: ((ZipResult) -> Void)?
|
|
34
|
+
private var rejectPromise: ((Error) -> Void)?
|
|
35
|
+
private var hasStarted = false
|
|
36
|
+
|
|
37
|
+
init(sourcePath: String, destinationZipPath: String, password: String? = nil) {
|
|
38
|
+
self.taskId = "zip_\(ProcessInfo.processInfo.globallyUniqueString)"
|
|
39
|
+
self.sourcePath = sourcePath
|
|
40
|
+
self.destinationZipPath = destinationZipPath
|
|
41
|
+
self.password = password
|
|
42
|
+
super.init()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MARK: - Spec methods
|
|
46
|
+
|
|
47
|
+
func onProgress(callback: @escaping (_ progress: ZipProgress) -> Void) throws {
|
|
48
|
+
lock.lock()
|
|
49
|
+
self.progressCallback = callback
|
|
50
|
+
lock.unlock()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func cancel() throws {
|
|
54
|
+
lock.lock()
|
|
55
|
+
shouldCancel = true
|
|
56
|
+
lock.unlock()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func await() throws -> Promise<ZipResult> {
|
|
60
|
+
return Promise.async { [self] resolve, reject in
|
|
61
|
+
self.lock.lock()
|
|
62
|
+
self.resolvePromise = resolve
|
|
63
|
+
self.rejectPromise = reject
|
|
64
|
+
|
|
65
|
+
guard !self.hasStarted else {
|
|
66
|
+
self.lock.unlock()
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
self.hasStarted = true
|
|
70
|
+
self.lock.unlock()
|
|
71
|
+
|
|
72
|
+
self.startCompression()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// MARK: - Compression
|
|
77
|
+
|
|
78
|
+
private func startCompression() {
|
|
79
|
+
beginBackgroundTask()
|
|
80
|
+
|
|
81
|
+
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
|
82
|
+
do {
|
|
83
|
+
let result = try self.compress()
|
|
84
|
+
self.endBackgroundTask()
|
|
85
|
+
self.lock.lock()
|
|
86
|
+
let resolve = self.resolvePromise
|
|
87
|
+
self.lock.unlock()
|
|
88
|
+
resolve?(result)
|
|
89
|
+
} catch {
|
|
90
|
+
self.endBackgroundTask()
|
|
91
|
+
self.lock.lock()
|
|
92
|
+
let reject = self.rejectPromise
|
|
93
|
+
self.lock.unlock()
|
|
94
|
+
reject?(error)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func compress() throws -> ZipResult {
|
|
100
|
+
let startTime = Date()
|
|
101
|
+
let fileManager = FileManager.default
|
|
102
|
+
|
|
103
|
+
let cleanSource = sourcePath.replacingOccurrences(of: "file://", with: "")
|
|
104
|
+
let cleanDest = destinationZipPath.replacingOccurrences(of: "file://", with: "")
|
|
105
|
+
|
|
106
|
+
guard fileManager.fileExists(atPath: cleanSource) else {
|
|
107
|
+
throw NSError(
|
|
108
|
+
domain: "NitroUnzip",
|
|
109
|
+
code: 1,
|
|
110
|
+
userInfo: [NSLocalizedDescriptionKey: "Source path not found: \(cleanSource)"]
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Collect all files to compress for progress tracking
|
|
115
|
+
var allFiles: [String] = []
|
|
116
|
+
if var enumerator = fileManager.enumerator(atPath: cleanSource) {
|
|
117
|
+
while let file = enumerator.nextObject() as? String {
|
|
118
|
+
let fullPath = (cleanSource as NSString).appendingPathComponent(file)
|
|
119
|
+
var isDir: ObjCBool = false
|
|
120
|
+
if fileManager.fileExists(atPath: fullPath, isDirectory: &isDir), !isDir.boolValue {
|
|
121
|
+
allFiles.append(file)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let totalFiles = allFiles.count
|
|
127
|
+
var compressedCount = 0
|
|
128
|
+
var lastProgressUpdate = Date()
|
|
129
|
+
|
|
130
|
+
// SSZipArchive createZipFile with progress
|
|
131
|
+
let success: Bool
|
|
132
|
+
if let password = password {
|
|
133
|
+
success = SSZipArchive.createZipFile(
|
|
134
|
+
atPath: cleanDest,
|
|
135
|
+
withContentsOfDirectory: cleanSource,
|
|
136
|
+
keepParentDirectory: false,
|
|
137
|
+
withPassword: password,
|
|
138
|
+
andProgressHandler: { [weak self] entryNumber, total in
|
|
139
|
+
self?.handleZipProgress(
|
|
140
|
+
entryNumber: entryNumber,
|
|
141
|
+
total: total,
|
|
142
|
+
startTime: startTime,
|
|
143
|
+
compressedCount: &compressedCount,
|
|
144
|
+
lastProgressUpdate: &lastProgressUpdate
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
} else {
|
|
149
|
+
success = SSZipArchive.createZipFile(
|
|
150
|
+
atPath: cleanDest,
|
|
151
|
+
withContentsOfDirectory: cleanSource,
|
|
152
|
+
keepParentDirectory: false,
|
|
153
|
+
compressionLevel: -1,
|
|
154
|
+
password: nil,
|
|
155
|
+
aes: false,
|
|
156
|
+
progressHandler: { [weak self] entryNumber, total in
|
|
157
|
+
self?.handleZipProgress(
|
|
158
|
+
entryNumber: entryNumber,
|
|
159
|
+
total: total,
|
|
160
|
+
startTime: startTime,
|
|
161
|
+
compressedCount: &compressedCount,
|
|
162
|
+
lastProgressUpdate: &lastProgressUpdate
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
lock.lock()
|
|
169
|
+
let wasCancelled = shouldCancel
|
|
170
|
+
lock.unlock()
|
|
171
|
+
|
|
172
|
+
if wasCancelled {
|
|
173
|
+
// Clean up partial zip file
|
|
174
|
+
try? fileManager.removeItem(atPath: cleanDest)
|
|
175
|
+
throw NSError(
|
|
176
|
+
domain: "NitroUnzip",
|
|
177
|
+
code: 2,
|
|
178
|
+
userInfo: [NSLocalizedDescriptionKey: "Zip creation cancelled"]
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
guard success else {
|
|
183
|
+
throw NSError(
|
|
184
|
+
domain: "NitroUnzip",
|
|
185
|
+
code: 3,
|
|
186
|
+
userInfo: [NSLocalizedDescriptionKey: "SSZipArchive zip creation failed"]
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let duration = Date().timeIntervalSince(startTime) * 1000
|
|
191
|
+
let finalCount = totalFiles
|
|
192
|
+
let averageSpeed = duration > 0 ? Double(finalCount) / (duration / 1000) : 0
|
|
193
|
+
|
|
194
|
+
// Get output file size
|
|
195
|
+
let attrs = try? fileManager.attributesOfItem(atPath: cleanDest)
|
|
196
|
+
let totalBytes = (attrs?[.size] as? Double) ?? 0
|
|
197
|
+
|
|
198
|
+
return ZipResult(
|
|
199
|
+
success: true,
|
|
200
|
+
compressedFiles: Double(finalCount),
|
|
201
|
+
duration: duration,
|
|
202
|
+
averageSpeed: averageSpeed,
|
|
203
|
+
totalBytes: totalBytes
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private func handleZipProgress(
|
|
208
|
+
entryNumber: UInt,
|
|
209
|
+
total: UInt,
|
|
210
|
+
startTime: Date,
|
|
211
|
+
compressedCount: inout Int,
|
|
212
|
+
lastProgressUpdate: inout Date
|
|
213
|
+
) {
|
|
214
|
+
lock.lock()
|
|
215
|
+
let cancelled = shouldCancel
|
|
216
|
+
let callback = progressCallback
|
|
217
|
+
lock.unlock()
|
|
218
|
+
if cancelled { return }
|
|
219
|
+
|
|
220
|
+
compressedCount = Int(entryNumber)
|
|
221
|
+
|
|
222
|
+
let now = Date()
|
|
223
|
+
let shouldUpdate = now.timeIntervalSince(lastProgressUpdate) >= progressThrottle
|
|
224
|
+
|| entryNumber == total
|
|
225
|
+
|| entryNumber == 1
|
|
226
|
+
|
|
227
|
+
if shouldUpdate, let callback = callback {
|
|
228
|
+
let progress = total > 0 ? Double(entryNumber) / Double(total) : 0
|
|
229
|
+
let elapsed = now.timeIntervalSince(startTime)
|
|
230
|
+
let speed = elapsed > 0 ? Double(entryNumber) / elapsed : 0
|
|
231
|
+
|
|
232
|
+
callback(ZipProgress(
|
|
233
|
+
compressedFiles: Double(entryNumber),
|
|
234
|
+
totalFiles: Double(total),
|
|
235
|
+
progress: progress,
|
|
236
|
+
speed: speed
|
|
237
|
+
))
|
|
238
|
+
lastProgressUpdate = now
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// MARK: - Background task management
|
|
243
|
+
|
|
244
|
+
private func beginBackgroundTask() {
|
|
245
|
+
let bgId = UIApplication.shared.beginBackgroundTask(withName: "NitroZip-\(taskId)") { [weak self] in
|
|
246
|
+
self?.lock.lock()
|
|
247
|
+
self?.shouldCancel = true
|
|
248
|
+
self?.lock.unlock()
|
|
249
|
+
self?.endBackgroundTask()
|
|
250
|
+
}
|
|
251
|
+
lock.lock()
|
|
252
|
+
backgroundTaskId = bgId
|
|
253
|
+
lock.unlock()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private func endBackgroundTask() {
|
|
257
|
+
lock.lock()
|
|
258
|
+
let bgId = backgroundTaskId
|
|
259
|
+
backgroundTaskId = .invalid
|
|
260
|
+
lock.unlock()
|
|
261
|
+
|
|
262
|
+
if bgId != .invalid {
|
|
263
|
+
UIApplication.shared.endBackgroundTask(bgId)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getUnzip = getUnzip;
|
|
7
|
+
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
8
|
+
/**
|
|
9
|
+
* Create an `Unzip` instance. Each instance is a factory for
|
|
10
|
+
* extraction and compression tasks.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { getUnzip } from 'react-native-nitro-unzip'
|
|
15
|
+
*
|
|
16
|
+
* const unzip = getUnzip()
|
|
17
|
+
*
|
|
18
|
+
* // Extract
|
|
19
|
+
* const task = unzip.extract('/path/to/archive.zip', '/output')
|
|
20
|
+
* task.onProgress((p) => console.log(`${(p.progress * 100).toFixed(0)}%`))
|
|
21
|
+
* const result = await task.await()
|
|
22
|
+
*
|
|
23
|
+
* // Zip
|
|
24
|
+
* const zipTask = unzip.zip('/path/to/folder', '/output.zip')
|
|
25
|
+
* const zipResult = await zipTask.await()
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function getUnzip() {
|
|
29
|
+
return _reactNativeNitroModules.NitroModules.createHybridObject("Unzip");
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNativeNitroModules","require","getUnzip","NitroModules","createHybridObject"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;AAAA,IAAAA,wBAAA,GAAAC,OAAA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAAA,EAAU;EAChC,OAAOC,qCAAY,CAACC,kBAAkB,CAAQ,OAAO,CAAC;AACxD","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["specs/Unzip.nitro.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NitroModules } from "react-native-nitro-modules";
|
|
4
|
+
/**
|
|
5
|
+
* Create an `Unzip` instance. Each instance is a factory for
|
|
6
|
+
* extraction and compression tasks.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { getUnzip } from 'react-native-nitro-unzip'
|
|
11
|
+
*
|
|
12
|
+
* const unzip = getUnzip()
|
|
13
|
+
*
|
|
14
|
+
* // Extract
|
|
15
|
+
* const task = unzip.extract('/path/to/archive.zip', '/output')
|
|
16
|
+
* task.onProgress((p) => console.log(`${(p.progress * 100).toFixed(0)}%`))
|
|
17
|
+
* const result = await task.await()
|
|
18
|
+
*
|
|
19
|
+
* // Zip
|
|
20
|
+
* const zipTask = unzip.zip('/path/to/folder', '/output.zip')
|
|
21
|
+
* const zipResult = await zipTask.await()
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function getUnzip() {
|
|
25
|
+
return NitroModules.createHybridObject("Unzip");
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NitroModules","getUnzip","createHybridObject"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,4BAA4B;AAazD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAAA,EAAU;EAChC,OAAOD,YAAY,CAACE,kBAAkB,CAAQ,OAAO,CAAC;AACxD","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["specs/Unzip.nitro.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Unzip } from "./specs/Unzip.nitro";
|
|
2
|
+
export type { UnzipProgress, UnzipResult, UnzipTask, ZipProgress, ZipResult, ZipTask, Unzip, } from "./specs/Unzip.nitro";
|
|
3
|
+
/**
|
|
4
|
+
* Create an `Unzip` instance. Each instance is a factory for
|
|
5
|
+
* extraction and compression tasks.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { getUnzip } from 'react-native-nitro-unzip'
|
|
10
|
+
*
|
|
11
|
+
* const unzip = getUnzip()
|
|
12
|
+
*
|
|
13
|
+
* // Extract
|
|
14
|
+
* const task = unzip.extract('/path/to/archive.zip', '/output')
|
|
15
|
+
* task.onProgress((p) => console.log(`${(p.progress * 100).toFixed(0)}%`))
|
|
16
|
+
* const result = await task.await()
|
|
17
|
+
*
|
|
18
|
+
* // Zip
|
|
19
|
+
* const zipTask = unzip.zip('/path/to/folder', '/output.zip')
|
|
20
|
+
* const zipResult = await zipTask.await()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function getUnzip(): Unzip;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,SAAS,EACT,OAAO,EACP,KAAK,GACN,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,IAAI,KAAK,CAEhC"}
|