@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.22
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/android/proguard-rules.pro +17 -2
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
- package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
- package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
- package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
- package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
- package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
- package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
- package/android/src/newarch/HotUpdater.kt +7 -9
- package/android/src/newarch/HotUpdaterModule.kt +109 -84
- package/android/src/oldarch/HotUpdater.kt +7 -9
- package/android/src/oldarch/HotUpdaterModule.kt +98 -74
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
- package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
- package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +125 -70
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +13 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/error.js +4 -13
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchJSON.js +5 -2
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +55 -92
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +68 -20
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +82 -62
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +44 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +20 -61
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/sdkVersion.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +3 -10
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/wrap.js +44 -27
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/checkForUpdate.js +118 -65
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +4 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/error.js +4 -13
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchJSON.js +3 -1
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +53 -90
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +55 -22
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +74 -54
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +35 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +16 -56
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +3 -10
- package/lib/module/store.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/wrap.js +45 -28
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +23 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/error.d.ts +4 -13
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +3 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +3 -32
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +7 -12
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +33 -23
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +6 -14
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +1 -3
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/updateError.d.ts +26 -0
- package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +23 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +16 -4
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +2 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/error.d.ts +4 -13
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +3 -0
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +3 -32
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +7 -12
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +33 -23
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +4 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +6 -14
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +1 -3
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +1 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/updateError.d.ts +26 -0
- package/lib/typescript/module/updateError.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +6 -7
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/appReady.ts +149 -0
- package/src/checkForUpdate.ts +152 -89
- package/src/clientPlugin.ts +17 -0
- package/src/error.ts +4 -14
- package/src/fetchJSON.ts +5 -1
- package/src/httpClient.ts +78 -157
- package/src/index.ts +87 -29
- package/src/native.ts +109 -104
- package/src/pluginHost.ts +45 -0
- package/src/releaseCatalogCache.ts +26 -140
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +41 -48
- package/src/store.ts +2 -23
- package/src/types.ts +1 -1
- package/src/updateError.ts +153 -0
- package/src/wrap.tsx +59 -28
- package/src/wrap.types.ts +6 -7
- package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
- package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
- package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
- package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
- package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
- package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
- package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
- package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
- package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
- package/lib/commonjs/catalogCacheNative.spec.js +0 -31
- package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -604
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -102
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -298
- package/lib/commonjs/index.spec.js.map +0 -1
- package/lib/commonjs/iosInterop.spec.js +0 -18
- package/lib/commonjs/iosInterop.spec.js.map +0 -1
- package/lib/commonjs/native.spec.js +0 -795
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -133
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
- package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/commonjs/store.notification.spec.js +0 -76
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -316
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -296
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/catalogCacheNative.spec.js +0 -31
- package/lib/module/catalogCacheNative.spec.js.map +0 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -603
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -102
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -297
- package/lib/module/index.spec.js.map +0 -1
- package/lib/module/iosInterop.spec.js +0 -18
- package/lib/module/iosInterop.spec.js.map +0 -1
- package/lib/module/native.spec.js +0 -794
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -128
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -245
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -195
- package/lib/module/releaseCatalogCache.spec.js.map +0 -1
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/module/store.notification.spec.js +0 -77
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -315
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -295
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -596
- package/src/httpClient.spec.ts +0 -132
- package/src/index.spec.ts +0 -331
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -855
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
- package/src/notifyAppReadyInsights.spec.ts +0 -243
- package/src/notifyAppReadyInsights.test-utils.ts +0 -31
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
- package/src/notifyAppReadyInsights.ts +0 -194
- package/src/releaseCatalogCache.spec.ts +0 -282
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -92
- package/src/store.spec.ts +0 -372
- package/src/wrap.spec.ts +0 -367
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
enum ArchiveExtractionUtilities {
|
|
4
|
-
static let bufferSize = 64 * 1024
|
|
5
|
-
|
|
6
|
-
static func readUpToCount(from handle: FileHandle, count: Int) throws -> Data? {
|
|
7
|
-
guard count >= 0 else {
|
|
8
|
-
throw NSError(
|
|
9
|
-
domain: "ArchiveExtractionUtilities",
|
|
10
|
-
code: 5,
|
|
11
|
-
userInfo: [NSLocalizedDescriptionKey: "Invalid read size: \(count)"]
|
|
12
|
-
)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if count == 0 {
|
|
16
|
-
return Data()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
|
|
20
|
-
return try handle.read(upToCount: count)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return handle.readData(ofLength: count)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
static func readExactly(from handle: FileHandle, count: Int) throws -> Data {
|
|
27
|
-
guard count >= 0 else {
|
|
28
|
-
throw NSError(
|
|
29
|
-
domain: "ArchiveExtractionUtilities",
|
|
30
|
-
code: 1,
|
|
31
|
-
userInfo: [NSLocalizedDescriptionKey: "Invalid read size: \(count)"]
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if count == 0 {
|
|
36
|
-
return Data()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
guard let data = try readUpToCount(from: handle, count: count), data.count == count else {
|
|
40
|
-
throw NSError(
|
|
41
|
-
domain: "ArchiveExtractionUtilities",
|
|
42
|
-
code: 2,
|
|
43
|
-
userInfo: [NSLocalizedDescriptionKey: "Unexpected end of archive while reading \(count) bytes"]
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return data
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static func currentOffset(for handle: FileHandle) -> UInt64 {
|
|
51
|
-
if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
|
|
52
|
-
return (try? handle.offset()) ?? 0
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return handle.offsetInFile
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static func seek(_ handle: FileHandle, to offset: UInt64) throws {
|
|
59
|
-
if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
|
|
60
|
-
try handle.seek(toOffset: offset)
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
handle.seek(toFileOffset: offset)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
static func skipBytes(_ byteCount: UInt64, in handle: FileHandle) throws {
|
|
68
|
-
guard byteCount > 0 else {
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let offset = currentOffset(for: handle)
|
|
73
|
-
let (targetOffset, overflowed) = offset.addingReportingOverflow(byteCount)
|
|
74
|
-
|
|
75
|
-
guard !overflowed else {
|
|
76
|
-
throw NSError(
|
|
77
|
-
domain: "ArchiveExtractionUtilities",
|
|
78
|
-
code: 6,
|
|
79
|
-
userInfo: [NSLocalizedDescriptionKey: "Archive offset overflow while skipping \(byteCount) bytes from offset \(offset)"]
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
try seek(handle, to: targetOffset)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
static func normalizedRelativePath(from rawPath: String) -> String? {
|
|
87
|
-
let candidate = rawPath.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
88
|
-
guard !candidate.isEmpty,
|
|
89
|
-
!candidate.contains("\0"),
|
|
90
|
-
!candidate.contains("\\"),
|
|
91
|
-
!candidate.hasPrefix("/"),
|
|
92
|
-
candidate.range(of: #"^[A-Za-z]:"#, options: .regularExpression) == nil
|
|
93
|
-
else {
|
|
94
|
-
return nil
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let components = candidate
|
|
98
|
-
.split(separator: "/")
|
|
99
|
-
.map(String.init)
|
|
100
|
-
|
|
101
|
-
guard !components.isEmpty else {
|
|
102
|
-
return nil
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
guard components.count == candidate.split(separator: "/", omittingEmptySubsequences: false).count,
|
|
106
|
-
!components.contains(".."),
|
|
107
|
-
!components.contains(".") else {
|
|
108
|
-
return nil
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return components.joined(separator: "/")
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
static func extractionURL(for relativePath: String, destinationRoot: String) throws -> URL {
|
|
115
|
-
let rootURL = URL(fileURLWithPath: destinationRoot, isDirectory: true)
|
|
116
|
-
let targetURL = rootURL.appendingPathComponent(relativePath)
|
|
117
|
-
let standardizedRoot = rootURL.standardizedFileURL.path
|
|
118
|
-
let standardizedTarget = targetURL.standardizedFileURL.path
|
|
119
|
-
|
|
120
|
-
guard standardizedTarget == standardizedRoot ||
|
|
121
|
-
standardizedTarget.hasPrefix(standardizedRoot + "/") else {
|
|
122
|
-
throw NSError(
|
|
123
|
-
domain: "ArchiveExtractionUtilities",
|
|
124
|
-
code: 3,
|
|
125
|
-
userInfo: [NSLocalizedDescriptionKey: "Path traversal attempt detected: \(relativePath)"]
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return targetURL
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
static func ensureDirectory(at url: URL, fileManager: FileManager = .default) throws {
|
|
133
|
-
var isDirectory = ObjCBool(false)
|
|
134
|
-
if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
|
|
135
|
-
if isDirectory.boolValue {
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try fileManager.removeItem(at: url)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
static func createOutputFile(at url: URL, fileManager: FileManager = .default) throws -> FileHandle {
|
|
146
|
-
try ensureDirectory(at: url.deletingLastPathComponent(), fileManager: fileManager)
|
|
147
|
-
|
|
148
|
-
if fileManager.fileExists(atPath: url.path) {
|
|
149
|
-
try fileManager.removeItem(at: url)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
guard fileManager.createFile(atPath: url.path, contents: nil) else {
|
|
153
|
-
throw NSError(
|
|
154
|
-
domain: "ArchiveExtractionUtilities",
|
|
155
|
-
code: 4,
|
|
156
|
-
userInfo: [NSLocalizedDescriptionKey: "Failed to create output file at \(url.path)"]
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return try FileHandle(forWritingTo: url)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
extension Data {
|
|
165
|
-
func archiveUInt16LE(at offset: Int) -> UInt16 {
|
|
166
|
-
let byte0 = UInt16(self[offset])
|
|
167
|
-
let byte1 = UInt16(self[offset + 1]) << 8
|
|
168
|
-
return byte0 | byte1
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
func archiveUInt32LE(at offset: Int) -> UInt32 {
|
|
172
|
-
let byte0 = UInt32(self[offset])
|
|
173
|
-
let byte1 = UInt32(self[offset + 1]) << 8
|
|
174
|
-
let byte2 = UInt32(self[offset + 2]) << 16
|
|
175
|
-
let byte3 = UInt32(self[offset + 3]) << 24
|
|
176
|
-
return byte0 | byte1 | byte2 | byte3
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Unified decompression service that uses Strategy pattern to handle multiple compression formats.
|
|
5
|
-
* Automatically detects format by trying each strategy's validation and delegates to appropriate decompression strategy.
|
|
6
|
-
*/
|
|
7
|
-
class DecompressService {
|
|
8
|
-
/// Strategies with reliable file signatures that can be validated cheaply.
|
|
9
|
-
private let signatureStrategies: [DecompressionStrategy]
|
|
10
|
-
/// TAR.BR has no reliable magic bytes, so it is attempted as the final fallback.
|
|
11
|
-
private let tarBrStrategy: DecompressionStrategy
|
|
12
|
-
|
|
13
|
-
init() {
|
|
14
|
-
// Order matters: Try ZIP first (clear magic bytes), then TAR.GZ (GZIP magic bytes).
|
|
15
|
-
// TAR.BR is attempted only after signature-based formats are ruled out.
|
|
16
|
-
self.signatureStrategies = [
|
|
17
|
-
ZipDecompressionStrategy(),
|
|
18
|
-
TarGzDecompressionStrategy()
|
|
19
|
-
]
|
|
20
|
-
self.tarBrStrategy = TarBrDecompressionStrategy()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Extracts a compressed file to the destination directory.
|
|
25
|
-
* Automatically detects compression format by trying each strategy's validation.
|
|
26
|
-
* @param file Path to the compressed file
|
|
27
|
-
* @param destination Path to the destination directory
|
|
28
|
-
* @param progressHandler Callback for progress updates (0.0 - 1.0)
|
|
29
|
-
* @throws Error if decompression fails or no valid strategy found
|
|
30
|
-
*/
|
|
31
|
-
func unzip(file: String, to destination: String, progressHandler: @escaping (Double) -> Void) throws {
|
|
32
|
-
// Collect file information for better error messages
|
|
33
|
-
let fileURL = URL(fileURLWithPath: file)
|
|
34
|
-
let fileName = fileURL.lastPathComponent
|
|
35
|
-
let fileSize = (try? FileManager.default.attributesOfItem(atPath: file)[.size] as? UInt64) ?? 0
|
|
36
|
-
|
|
37
|
-
// Try each signature-based strategy first.
|
|
38
|
-
for strategy in signatureStrategies {
|
|
39
|
-
if strategy.isValid(file: file) {
|
|
40
|
-
NSLog("[DecompressService] Using strategy for \(fileName)")
|
|
41
|
-
try strategy.decompress(file: file, to: destination, progressHandler: progressHandler)
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
NSLog("[DecompressService] No ZIP/TAR.GZ signature matched for \(fileName), trying TAR.BR fallback")
|
|
47
|
-
|
|
48
|
-
do {
|
|
49
|
-
try tarBrStrategy.decompress(file: file, to: destination, progressHandler: progressHandler)
|
|
50
|
-
NSLog("[DecompressService] Using TAR.BR fallback for \(fileName)")
|
|
51
|
-
return
|
|
52
|
-
} catch {
|
|
53
|
-
let invalidArchiveError = createInvalidArchiveError(
|
|
54
|
-
fileName: fileName,
|
|
55
|
-
fileSize: fileSize,
|
|
56
|
-
underlyingError: error
|
|
57
|
-
)
|
|
58
|
-
NSLog("[DecompressService] \(invalidArchiveError.localizedDescription)")
|
|
59
|
-
throw invalidArchiveError
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Extracts a compressed file to the destination directory (without progress tracking).
|
|
65
|
-
* @param file Path to the compressed file
|
|
66
|
-
* @param destination Path to the destination directory
|
|
67
|
-
* @throws Error if decompression fails or no valid strategy found
|
|
68
|
-
*/
|
|
69
|
-
func unzip(file: String, to destination: String) throws {
|
|
70
|
-
try unzip(file: file, to: destination, progressHandler: { _ in })
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Validates if a file matches one of the signature-based archive formats.
|
|
75
|
-
* @param file Path to the file to validate
|
|
76
|
-
* @return true if the file is valid for any strategy
|
|
77
|
-
*/
|
|
78
|
-
func isValid(file: String) -> Bool {
|
|
79
|
-
for strategy in signatureStrategies {
|
|
80
|
-
if strategy.isValid(file: file) {
|
|
81
|
-
return true
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
NSLog("[DecompressService] No ZIP/TAR.GZ signature matched for file: \(file). TAR.BR is handled during extraction fallback.")
|
|
85
|
-
return false
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private func createInvalidArchiveError(fileName: String, fileSize: UInt64, underlyingError: Error? = nil) -> NSError {
|
|
89
|
-
let errorMessage = """
|
|
90
|
-
The downloaded bundle file is not a valid compressed archive: \(fileName) (\(fileSize) bytes)
|
|
91
|
-
|
|
92
|
-
Supported formats:
|
|
93
|
-
- ZIP archives (.zip)
|
|
94
|
-
- GZIP compressed TAR archives (.tar.gz)
|
|
95
|
-
- Brotli compressed TAR archives (.tar.br)
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
var userInfo: [String: Any] = [
|
|
99
|
-
NSLocalizedDescriptionKey: errorMessage
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
if let underlyingError {
|
|
103
|
-
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return NSError(
|
|
107
|
-
domain: "DecompressService",
|
|
108
|
-
code: 1,
|
|
109
|
-
userInfo: userInfo
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Protocol for decompression strategies
|
|
5
|
-
*/
|
|
6
|
-
protocol DecompressionStrategy {
|
|
7
|
-
/**
|
|
8
|
-
* Validates if a file can be decompressed by this strategy
|
|
9
|
-
* @param file Path to the file to validate
|
|
10
|
-
* @return true if the file is valid for this strategy
|
|
11
|
-
*/
|
|
12
|
-
func isValid(file: String) -> Bool
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Decompresses a file to the destination directory
|
|
16
|
-
* @param file Path to the compressed file
|
|
17
|
-
* @param destination Path to the destination directory
|
|
18
|
-
* @param progressHandler Callback for progress updates (0.0 - 1.0)
|
|
19
|
-
* @throws Error if decompression fails
|
|
20
|
-
*/
|
|
21
|
-
func decompress(file: String, to destination: String, progressHandler: @escaping (Double) -> Void) throws
|
|
22
|
-
}
|
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
import Compression
|
|
2
|
-
import Foundation
|
|
3
|
-
import zlib
|
|
4
|
-
|
|
5
|
-
enum CompressedTarAlgorithm {
|
|
6
|
-
case gzip
|
|
7
|
-
case brotli
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum StreamingTarArchiveExtractor {
|
|
11
|
-
private static let bufferSize = 64 * 1024
|
|
12
|
-
private static let decompressionProgressWeight = 0.45
|
|
13
|
-
|
|
14
|
-
static func extractCompressedTar(
|
|
15
|
-
file: String,
|
|
16
|
-
to destination: String,
|
|
17
|
-
algorithm: CompressedTarAlgorithm,
|
|
18
|
-
progressHandler: @escaping (Double) -> Void
|
|
19
|
-
) throws {
|
|
20
|
-
try withTemporaryTarFile { temporaryTarURL in
|
|
21
|
-
switch algorithm {
|
|
22
|
-
case .gzip:
|
|
23
|
-
try decompressGzipArchive(
|
|
24
|
-
from: file,
|
|
25
|
-
to: temporaryTarURL.path,
|
|
26
|
-
progressHandler: { progress in
|
|
27
|
-
progressHandler(progress * decompressionProgressWeight)
|
|
28
|
-
}
|
|
29
|
-
)
|
|
30
|
-
case .brotli:
|
|
31
|
-
try decompressBrotliArchive(
|
|
32
|
-
from: file,
|
|
33
|
-
to: temporaryTarURL.path,
|
|
34
|
-
progressHandler: { progress in
|
|
35
|
-
progressHandler(progress * decompressionProgressWeight)
|
|
36
|
-
}
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try extractTarArchive(
|
|
41
|
-
from: temporaryTarURL.path,
|
|
42
|
-
to: destination,
|
|
43
|
-
progressHandler: { progress in
|
|
44
|
-
let extractionStart = decompressionProgressWeight
|
|
45
|
-
let extractionWeight = 1.0 - decompressionProgressWeight
|
|
46
|
-
progressHandler(extractionStart + (progress * extractionWeight))
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static func decompressBrotliFile(
|
|
53
|
-
from sourcePath: String,
|
|
54
|
-
to outputPath: String
|
|
55
|
-
) throws {
|
|
56
|
-
try decompressBrotliArchive(
|
|
57
|
-
from: sourcePath,
|
|
58
|
-
to: outputPath,
|
|
59
|
-
progressHandler: { _ in }
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static func containsTarEntries(file: String, algorithm: CompressedTarAlgorithm) -> Bool {
|
|
64
|
-
do {
|
|
65
|
-
return try withTemporaryTarFile { temporaryTarURL in
|
|
66
|
-
switch algorithm {
|
|
67
|
-
case .gzip:
|
|
68
|
-
try decompressGzipArchive(
|
|
69
|
-
from: file,
|
|
70
|
-
to: temporaryTarURL.path,
|
|
71
|
-
progressHandler: { _ in }
|
|
72
|
-
)
|
|
73
|
-
case .brotli:
|
|
74
|
-
try decompressBrotliArchive(
|
|
75
|
-
from: file,
|
|
76
|
-
to: temporaryTarURL.path,
|
|
77
|
-
progressHandler: { _ in }
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return try tarArchiveHasEntries(at: temporaryTarURL.path)
|
|
82
|
-
}
|
|
83
|
-
} catch {
|
|
84
|
-
NSLog("[TarStreamExtractor] Validation failed: \(error.localizedDescription)")
|
|
85
|
-
return false
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private static func withTemporaryTarFile<T>(
|
|
90
|
-
perform: (URL) throws -> T
|
|
91
|
-
) throws -> T {
|
|
92
|
-
let temporaryTarURL = FileManager.default.temporaryDirectory
|
|
93
|
-
.appendingPathComponent("hot-updater-\(UUID().uuidString)")
|
|
94
|
-
.appendingPathExtension("tar")
|
|
95
|
-
|
|
96
|
-
defer {
|
|
97
|
-
try? FileManager.default.removeItem(at: temporaryTarURL)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return try perform(temporaryTarURL)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private static func tarArchiveHasEntries(at tarPath: String) throws -> Bool {
|
|
104
|
-
try TarArchiveExtractor.containsEntries(at: tarPath)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private static func extractTarArchive(
|
|
108
|
-
from tarPath: String,
|
|
109
|
-
to destination: String,
|
|
110
|
-
progressHandler: @escaping (Double) -> Void
|
|
111
|
-
) throws {
|
|
112
|
-
try TarArchiveExtractor.extract(
|
|
113
|
-
from: tarPath,
|
|
114
|
-
to: destination,
|
|
115
|
-
progressHandler: progressHandler
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private static func decompressGzipArchive(
|
|
120
|
-
from sourcePath: String,
|
|
121
|
-
to outputPath: String,
|
|
122
|
-
progressHandler: @escaping (Double) -> Void
|
|
123
|
-
) throws {
|
|
124
|
-
let totalSourceSize = try fileSize(atPath: sourcePath)
|
|
125
|
-
try prepareEmptyFile(at: outputPath)
|
|
126
|
-
|
|
127
|
-
let outputHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
|
|
128
|
-
|
|
129
|
-
defer {
|
|
130
|
-
try? outputHandle.close()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
guard let gzipFile = sourcePath.withCString({ pathPointer in
|
|
134
|
-
"rb".withCString { modePointer in
|
|
135
|
-
gzopen(pathPointer, modePointer)
|
|
136
|
-
}
|
|
137
|
-
}) else {
|
|
138
|
-
throw NSError(
|
|
139
|
-
domain: "StreamingTarArchiveExtractor",
|
|
140
|
-
code: 5,
|
|
141
|
-
userInfo: [NSLocalizedDescriptionKey: "Failed to open gzip archive"]
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
defer {
|
|
146
|
-
gzclose(gzipFile)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
var buffer = [UInt8](repeating: 0, count: bufferSize)
|
|
150
|
-
|
|
151
|
-
while true {
|
|
152
|
-
let bytesRead = gzread(gzipFile, &buffer, UInt32(buffer.count))
|
|
153
|
-
|
|
154
|
-
if bytesRead > 0 {
|
|
155
|
-
outputHandle.write(Data(buffer[0..<Int(bytesRead)]))
|
|
156
|
-
|
|
157
|
-
if totalSourceSize > 0 {
|
|
158
|
-
let compressedOffset = max(Int64(gzoffset(gzipFile)), 0)
|
|
159
|
-
let progress = min(Double(compressedOffset) / Double(totalSourceSize), 1.0)
|
|
160
|
-
progressHandler(progress)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
continue
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if bytesRead == 0 {
|
|
167
|
-
progressHandler(1.0)
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
var errorCode: Int32 = 0
|
|
172
|
-
let messagePointer = gzerror(gzipFile, &errorCode)
|
|
173
|
-
let message = messagePointer.map { String(cString: $0) } ?? "Unknown gzip error"
|
|
174
|
-
|
|
175
|
-
throw NSError(
|
|
176
|
-
domain: "StreamingTarArchiveExtractor",
|
|
177
|
-
code: Int(errorCode),
|
|
178
|
-
userInfo: [NSLocalizedDescriptionKey: "GZIP decompression failed: \(message)"]
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private static func decompressBrotliArchive(
|
|
184
|
-
from sourcePath: String,
|
|
185
|
-
to outputPath: String,
|
|
186
|
-
progressHandler: @escaping (Double) -> Void
|
|
187
|
-
) throws {
|
|
188
|
-
let totalSourceSize = try fileSize(atPath: sourcePath)
|
|
189
|
-
try prepareEmptyFile(at: outputPath)
|
|
190
|
-
|
|
191
|
-
let inputHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: sourcePath))
|
|
192
|
-
let outputHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
|
|
193
|
-
|
|
194
|
-
defer {
|
|
195
|
-
try? inputHandle.close()
|
|
196
|
-
try? outputHandle.close()
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
var stream = compression_stream(
|
|
200
|
-
dst_ptr: UnsafeMutablePointer<UInt8>(bitPattern: 1)!,
|
|
201
|
-
dst_size: 0,
|
|
202
|
-
src_ptr: UnsafePointer<UInt8>(bitPattern: 1)!,
|
|
203
|
-
src_size: 0,
|
|
204
|
-
state: nil
|
|
205
|
-
)
|
|
206
|
-
let status = compression_stream_init(&stream, COMPRESSION_STREAM_DECODE, COMPRESSION_BROTLI)
|
|
207
|
-
|
|
208
|
-
guard status == COMPRESSION_STATUS_OK else {
|
|
209
|
-
throw NSError(
|
|
210
|
-
domain: "StreamingTarArchiveExtractor",
|
|
211
|
-
code: 6,
|
|
212
|
-
userInfo: [NSLocalizedDescriptionKey: "Failed to initialize Brotli decompression stream"]
|
|
213
|
-
)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
defer {
|
|
217
|
-
compression_stream_destroy(&stream)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
let outputBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
|
|
221
|
-
|
|
222
|
-
defer {
|
|
223
|
-
outputBuffer.deallocate()
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
var processedSourceBytes: UInt64 = 0
|
|
227
|
-
var reachedStreamEnd = false
|
|
228
|
-
|
|
229
|
-
while !reachedStreamEnd {
|
|
230
|
-
let chunk = try ArchiveExtractionUtilities.readUpToCount(
|
|
231
|
-
from: inputHandle,
|
|
232
|
-
count: bufferSize
|
|
233
|
-
) ?? Data()
|
|
234
|
-
processedSourceBytes += UInt64(chunk.count)
|
|
235
|
-
|
|
236
|
-
let streamStatus: compression_status
|
|
237
|
-
if chunk.isEmpty {
|
|
238
|
-
stream.src_ptr = UnsafePointer<UInt8>(bitPattern: 1)!
|
|
239
|
-
stream.src_size = 0
|
|
240
|
-
streamStatus = try flushCompressionStream(
|
|
241
|
-
&stream,
|
|
242
|
-
into: outputHandle,
|
|
243
|
-
outputBuffer: outputBuffer,
|
|
244
|
-
finalize: true
|
|
245
|
-
)
|
|
246
|
-
} else {
|
|
247
|
-
streamStatus = try chunk.withUnsafeBytes { rawBuffer in
|
|
248
|
-
guard let baseAddress = rawBuffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
|
249
|
-
return COMPRESSION_STATUS_OK
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
stream.src_ptr = baseAddress
|
|
253
|
-
stream.src_size = chunk.count
|
|
254
|
-
|
|
255
|
-
return try flushCompressionStream(
|
|
256
|
-
&stream,
|
|
257
|
-
into: outputHandle,
|
|
258
|
-
outputBuffer: outputBuffer,
|
|
259
|
-
finalize: false
|
|
260
|
-
)
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if totalSourceSize > 0 {
|
|
265
|
-
let progress = min(Double(processedSourceBytes) / Double(totalSourceSize), 1.0)
|
|
266
|
-
progressHandler(progress)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if streamStatus == COMPRESSION_STATUS_END {
|
|
270
|
-
reachedStreamEnd = true
|
|
271
|
-
} else if chunk.isEmpty {
|
|
272
|
-
throw NSError(
|
|
273
|
-
domain: "StreamingTarArchiveExtractor",
|
|
274
|
-
code: 7,
|
|
275
|
-
userInfo: [NSLocalizedDescriptionKey: "Brotli decompression ended before reaching the end of stream"]
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
progressHandler(1.0)
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private static func flushCompressionStream(
|
|
284
|
-
_ stream: inout compression_stream,
|
|
285
|
-
into outputHandle: FileHandle,
|
|
286
|
-
outputBuffer: UnsafeMutablePointer<UInt8>,
|
|
287
|
-
finalize: Bool
|
|
288
|
-
) throws -> compression_status {
|
|
289
|
-
let flags = finalize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0
|
|
290
|
-
var lastStatus = COMPRESSION_STATUS_OK
|
|
291
|
-
|
|
292
|
-
repeat {
|
|
293
|
-
let previousSourceSize = stream.src_size
|
|
294
|
-
|
|
295
|
-
stream.dst_ptr = outputBuffer
|
|
296
|
-
stream.dst_size = bufferSize
|
|
297
|
-
|
|
298
|
-
lastStatus = compression_stream_process(&stream, flags)
|
|
299
|
-
|
|
300
|
-
switch lastStatus {
|
|
301
|
-
case COMPRESSION_STATUS_OK, COMPRESSION_STATUS_END:
|
|
302
|
-
let producedBytes = bufferSize - stream.dst_size
|
|
303
|
-
if producedBytes > 0 {
|
|
304
|
-
outputHandle.write(
|
|
305
|
-
Data(bytes: outputBuffer, count: producedBytes)
|
|
306
|
-
)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if finalize,
|
|
310
|
-
lastStatus == COMPRESSION_STATUS_OK,
|
|
311
|
-
producedBytes == 0,
|
|
312
|
-
previousSourceSize == stream.src_size {
|
|
313
|
-
throw NSError(
|
|
314
|
-
domain: "StreamingTarArchiveExtractor",
|
|
315
|
-
code: 8,
|
|
316
|
-
userInfo: [NSLocalizedDescriptionKey: "Brotli decompression stalled before reaching the end of stream"]
|
|
317
|
-
)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
default:
|
|
321
|
-
throw NSError(
|
|
322
|
-
domain: "StreamingTarArchiveExtractor",
|
|
323
|
-
code: 9,
|
|
324
|
-
userInfo: [NSLocalizedDescriptionKey: "Brotli decompression failed"]
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
} while stream.src_size > 0 || stream.dst_size == 0 || (finalize && lastStatus == COMPRESSION_STATUS_OK)
|
|
328
|
-
|
|
329
|
-
return lastStatus
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private static func prepareEmptyFile(at path: String) throws {
|
|
333
|
-
let fileManager = FileManager.default
|
|
334
|
-
let parentDirectory = (path as NSString).deletingLastPathComponent
|
|
335
|
-
|
|
336
|
-
if !fileManager.fileExists(atPath: parentDirectory) {
|
|
337
|
-
try fileManager.createDirectory(
|
|
338
|
-
atPath: parentDirectory,
|
|
339
|
-
withIntermediateDirectories: true,
|
|
340
|
-
attributes: nil
|
|
341
|
-
)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if fileManager.fileExists(atPath: path) {
|
|
345
|
-
try fileManager.removeItem(atPath: path)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
guard fileManager.createFile(atPath: path, contents: nil) else {
|
|
349
|
-
throw NSError(
|
|
350
|
-
domain: "StreamingTarArchiveExtractor",
|
|
351
|
-
code: 10,
|
|
352
|
-
userInfo: [NSLocalizedDescriptionKey: "Failed to create temporary archive file"]
|
|
353
|
-
)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private static func fileSize(atPath path: String) throws -> UInt64 {
|
|
358
|
-
let attributes = try FileManager.default.attributesOfItem(atPath: path)
|
|
359
|
-
if let value = attributes[.size] as? NSNumber {
|
|
360
|
-
return value.uint64Value
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if let value = attributes[.size] as? UInt64 {
|
|
364
|
-
return value
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return 0
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
}
|