@hot-updater/react-native 1.0.0-rc.15 → 1.0.0-rc.17
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/src/main/java/com/hotupdater/BundleFileStorageService.kt +491 -388
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +5 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +290 -12
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +35 -19
- 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 +110 -52
- 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/newarch/HotUpdaterModule.kt +74 -69
- package/android/src/oldarch/HotUpdaterModule.kt +63 -59
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +1 -1
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +446 -61
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +220 -0
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +19 -27
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +71 -0
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +106 -0
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +11 -0
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +309 -23
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +57 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +238 -157
- package/ios/HotUpdater/Internal/BundleMetadata.swift +3 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +8 -18
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +48 -29
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +108 -1
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/appReady.spec.js +360 -0
- package/lib/commonjs/appReady.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/commonjs/appReady.test-utils.js.map +1 -0
- package/lib/commonjs/baseURL.js +16 -0
- package/lib/commonjs/baseURL.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +104 -56
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +49 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/entryImportGraph.spec.js +50 -0
- package/lib/commonjs/entryImportGraph.spec.js.map +1 -0
- package/lib/commonjs/fetchJSON.js +2 -6
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +15 -67
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/httpClient.spec.js +26 -9
- package/lib/commonjs/httpClient.spec.js.map +1 -1
- package/lib/commonjs/index.js +49 -25
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +126 -40
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +67 -48
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +80 -58
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/pluginHost.js +208 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/pluginHost.spec.js +292 -0
- package/lib/commonjs/pluginHost.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.js +129 -0
- package/lib/commonjs/plugins/insights/eventId.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js +47 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/index.js +272 -0
- package/lib/commonjs/plugins/insights/index.js.map +1 -0
- package/lib/commonjs/plugins/insights/insights.spec.js +640 -0
- package/lib/commonjs/plugins/insights/insights.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.js +128 -0
- package/lib/commonjs/plugins/insights/sender.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.spec.js +265 -0
- package/lib/commonjs/plugins/insights/sender.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/state.js +145 -0
- package/lib/commonjs/plugins/insights/state.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +13 -2
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +18 -0
- package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/updateError.spec.js +149 -0
- package/lib/commonjs/updateError.spec.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -25
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.progress.spec.js +195 -0
- package/lib/commonjs/wrap.progress.spec.js.map +1 -0
- package/lib/commonjs/wrap.spec.js +94 -230
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/appReady.spec.js +359 -0
- package/lib/module/appReady.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/module/appReady.test-utils.js.map +1 -0
- package/lib/module/baseURL.js +11 -0
- package/lib/module/baseURL.js.map +1 -0
- package/lib/module/checkForUpdate.js +103 -57
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/clientPlugin.js +44 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/entryImportGraph.spec.js +50 -0
- package/lib/module/entryImportGraph.spec.js.map +1 -0
- package/lib/module/fetchJSON.js +2 -6
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +14 -66
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/httpClient.spec.js +26 -9
- package/lib/module/httpClient.spec.js.map +1 -1
- package/lib/module/index.js +44 -27
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +127 -41
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +63 -44
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +80 -58
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/pluginHost.js +200 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/pluginHost.spec.js +291 -0
- package/lib/module/pluginHost.spec.js.map +1 -0
- package/lib/module/plugins/insights/eventId.js +122 -0
- package/lib/module/plugins/insights/eventId.js.map +1 -0
- package/lib/module/plugins/insights/eventId.spec.js +47 -0
- package/lib/module/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/module/plugins/insights/index.js +268 -0
- package/lib/module/plugins/insights/index.js.map +1 -0
- package/lib/module/plugins/insights/insights.spec.js +639 -0
- package/lib/module/plugins/insights/insights.spec.js.map +1 -0
- package/lib/module/plugins/insights/sender.js +123 -0
- package/lib/module/plugins/insights/sender.js.map +1 -0
- package/lib/module/plugins/insights/sender.spec.js +265 -0
- package/lib/module/plugins/insights/sender.spec.js.map +1 -0
- package/lib/module/plugins/insights/state.js +139 -0
- package/lib/module/plugins/insights/state.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +13 -2
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/releaseCatalogCache.spec.js +18 -0
- package/lib/module/releaseCatalogCache.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/updateError.spec.js +149 -0
- package/lib/module/updateError.spec.js.map +1 -0
- package/lib/module/wrap.js +43 -25
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.progress.spec.js +196 -0
- package/lib/module/wrap.progress.spec.js.map +1 -0
- package/lib/module/wrap.spec.js +93 -229
- package/lib/module/wrap.spec.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/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/baseURL.d.ts +4 -0
- package/lib/typescript/commonjs/baseURL.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +15 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +185 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts +2 -3
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +2 -30
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -6
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +29 -15
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +29 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/commonjs/specs/NativeHotUpdater.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 -5
- 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/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/module/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/baseURL.d.ts +4 -0
- package/lib/typescript/module/baseURL.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +15 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +185 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/fetchJSON.d.ts +2 -3
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +2 -30
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -6
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +29 -15
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +29 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/module/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/module/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/module/specs/NativeHotUpdater.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 -5
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +14 -2
- package/plugins/insights/package.json +8 -0
- package/src/appReady.spec.ts +393 -0
- package/src/{notifyAppReadyInsights.test-utils.ts → appReady.test-utils.ts} +7 -7
- package/src/appReady.ts +149 -0
- package/src/baseURL.ts +15 -0
- package/src/checkForUpdate.releaseCatalog.spec.ts +370 -76
- package/src/checkForUpdate.ts +135 -80
- package/src/clientPlugin.ts +231 -0
- package/src/entryImportGraph.spec.ts +65 -0
- package/src/fetchJSON.ts +3 -10
- package/src/httpClient.spec.ts +28 -9
- package/src/httpClient.ts +16 -109
- package/src/index.spec.ts +129 -36
- package/src/index.ts +67 -37
- package/src/native.spec.ts +98 -72
- package/src/native.ts +93 -77
- package/src/pluginHost.spec.ts +297 -0
- package/src/pluginHost.ts +262 -0
- package/src/plugins/insights/eventId.spec.ts +60 -0
- package/src/plugins/insights/eventId.ts +171 -0
- package/src/plugins/insights/index.ts +367 -0
- package/src/plugins/insights/insights.spec.ts +732 -0
- package/src/plugins/insights/sender.spec.ts +287 -0
- package/src/plugins/insights/sender.ts +221 -0
- package/src/plugins/insights/state.ts +184 -0
- package/src/releaseCatalogCache.spec.ts +22 -0
- package/src/releaseCatalogCache.ts +19 -4
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +34 -13
- package/src/updateError.spec.ts +144 -0
- package/src/updateError.ts +153 -0
- package/src/wrap.progress.spec.ts +232 -0
- package/src/wrap.spec.ts +100 -283
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -5
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -225
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -188
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -378
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -124
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -224
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -181
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -377
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -123
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -25
- 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 -25
- 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/notifyAppReadyInsights.boundary.spec.ts +0 -242
- package/src/notifyAppReadyInsights.spec.ts +0 -362
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -138
- package/src/notifyAppReadyInsights.ts +0 -293
|
@@ -14,9 +14,8 @@ import kotlinx.coroutines.withContext
|
|
|
14
14
|
import org.json.JSONObject
|
|
15
15
|
import java.io.File
|
|
16
16
|
import java.io.FileInputStream
|
|
17
|
-
import java.io.FileOutputStream
|
|
18
17
|
import java.net.URL
|
|
19
|
-
import
|
|
18
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
20
19
|
|
|
21
20
|
data class ChangedAssetDescriptor(
|
|
22
21
|
val fileUrl: String?,
|
|
@@ -56,6 +55,30 @@ data class DiffProgressDetails(
|
|
|
56
55
|
val files: List<DiffProgressFileSnapshot> = emptyList(),
|
|
57
56
|
)
|
|
58
57
|
|
|
58
|
+
/**
|
|
59
|
+
* How a staged bundle arrived, the `delivery` of the `updateBundle` result:
|
|
60
|
+
* [PATCH] when a bsdiff patch produced at least one file, [MANIFEST] when only
|
|
61
|
+
* the changed files were downloaded (possibly none), and [ARCHIVE] when the
|
|
62
|
+
* full tar.br archive was downloaded.
|
|
63
|
+
*/
|
|
64
|
+
enum class BundleDelivery(
|
|
65
|
+
val value: String,
|
|
66
|
+
) {
|
|
67
|
+
PATCH("patch"),
|
|
68
|
+
MANIFEST("manifest"),
|
|
69
|
+
ARCHIVE("archive"),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The `updateBundle` result. [patchFallback] is true when a patch for the
|
|
74
|
+
* running bundle could not produce its file, so the whole file was downloaded
|
|
75
|
+
* instead.
|
|
76
|
+
*/
|
|
77
|
+
data class UpdateBundleResult(
|
|
78
|
+
val delivery: BundleDelivery,
|
|
79
|
+
val patchFallback: Boolean,
|
|
80
|
+
)
|
|
81
|
+
|
|
59
82
|
/**
|
|
60
83
|
* Interface for bundle storage operations
|
|
61
84
|
*/
|
|
@@ -92,6 +115,7 @@ interface BundleStorageService {
|
|
|
92
115
|
* @param manifestUrl URL of the target bundle manifest
|
|
93
116
|
* @param manifestFileHash Manifest hash or signature
|
|
94
117
|
* @param progressCallback Callback for download progress updates
|
|
118
|
+
* @return how the staged bundle arrived
|
|
95
119
|
* @throws HotUpdaterException if the update fails
|
|
96
120
|
*/
|
|
97
121
|
suspend fun updateBundle(
|
|
@@ -101,7 +125,7 @@ interface BundleStorageService {
|
|
|
101
125
|
assets: Map<String, ChangedAssetDescriptor>,
|
|
102
126
|
archiveUrl: String? = null,
|
|
103
127
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
104
|
-
)
|
|
128
|
+
): UpdateBundleResult
|
|
105
129
|
|
|
106
130
|
fun stageReleaseSelection(selection: PersistedSelection): Boolean = false
|
|
107
131
|
|
|
@@ -137,30 +161,6 @@ interface BundleStorageService {
|
|
|
137
161
|
*/
|
|
138
162
|
fun notifyAppReady(): Map<String, Any?>
|
|
139
163
|
|
|
140
|
-
/**
|
|
141
|
-
* Returns the stable install ID for this app installation.
|
|
142
|
-
*/
|
|
143
|
-
fun getInstallId(): String
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Returns the persisted nullable user id for this app installation.
|
|
147
|
-
*/
|
|
148
|
-
fun getUserId(): String?
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Returns the persisted nullable username for this app installation.
|
|
152
|
-
*/
|
|
153
|
-
fun getUsername(): String?
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Persists the optional user envelope for insights.
|
|
157
|
-
* Passing null clears the stored user identity.
|
|
158
|
-
*/
|
|
159
|
-
fun setUser(
|
|
160
|
-
userId: String?,
|
|
161
|
-
username: String?,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
164
|
/**
|
|
165
165
|
* Gets the crashed bundle history
|
|
166
166
|
* @return CrashedHistory containing crashed bundles
|
|
@@ -231,6 +231,10 @@ class BundleFileStorageService(
|
|
|
231
231
|
private const val TAG = "BundleStorage"
|
|
232
232
|
private const val MAX_SAFE_INTEGER = 9_007_199_254_740_991L
|
|
233
233
|
private val SHA256_PATTERN = Regex("^[0-9a-f]{64}$")
|
|
234
|
+
|
|
235
|
+
/** A patch produced a file that does not match the target, or none at all. */
|
|
236
|
+
private val PATCH_FAILURE =
|
|
237
|
+
UpdateFailure(UpdateFailureStage.INSTALL, UpdateFailureReason.PATCH, resource = UpdateFailureResource.PATCH)
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
private val releaseStateLock = Any()
|
|
@@ -395,6 +399,10 @@ class BundleFileStorageService(
|
|
|
395
399
|
return File(compressedDir, "$safeName.$compression")
|
|
396
400
|
}
|
|
397
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Decompresses a downloaded `.br` asset. A decoding failure is reported as
|
|
404
|
+
* an extract failure and a failure writing the output as a storage failure.
|
|
405
|
+
*/
|
|
398
406
|
private fun decompressBrotliFile(
|
|
399
407
|
sourceFile: File,
|
|
400
408
|
targetFile: File,
|
|
@@ -409,7 +417,7 @@ class BundleFileStorageService(
|
|
|
409
417
|
try {
|
|
410
418
|
FileInputStream(sourceFile).use { fileInputStream ->
|
|
411
419
|
BrotliInputStream(fileInputStream).use { brotliInputStream ->
|
|
412
|
-
|
|
420
|
+
LocalStorageOutputStream.open(tempOutputFile).use { outputStream ->
|
|
413
421
|
brotliInputStream.copyTo(outputStream)
|
|
414
422
|
}
|
|
415
423
|
}
|
|
@@ -419,15 +427,30 @@ class BundleFileStorageService(
|
|
|
419
427
|
targetFile.delete()
|
|
420
428
|
}
|
|
421
429
|
if (!tempOutputFile.renameTo(targetFile)) {
|
|
422
|
-
tempOutputFile.copyTo(targetFile, overwrite = true)
|
|
430
|
+
storageOperation { tempOutputFile.copyTo(targetFile, overwrite = true) }
|
|
423
431
|
tempOutputFile.delete()
|
|
424
432
|
}
|
|
433
|
+
} catch (error: Exception) {
|
|
434
|
+
tempOutputFile.delete()
|
|
435
|
+
throw HotUpdaterException.downloadFailed(
|
|
436
|
+
error,
|
|
437
|
+
if (error is LocalStorageException) {
|
|
438
|
+
UpdateFailure.install(UpdateFailureReason.STORAGE)
|
|
439
|
+
} else {
|
|
440
|
+
UpdateFailure.install(UpdateFailureReason.EXTRACT)
|
|
441
|
+
},
|
|
442
|
+
)
|
|
425
443
|
} catch (error: Throwable) {
|
|
426
444
|
tempOutputFile.delete()
|
|
427
445
|
throw error
|
|
428
446
|
}
|
|
429
447
|
}
|
|
430
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Tries to produce [targetFile] from a bsdiff patch against the running
|
|
451
|
+
* bundle. A patch for another base bundle or algorithm is not attempted.
|
|
452
|
+
* Once attempted, a failure leaves the file to be downloaded instead.
|
|
453
|
+
*/
|
|
431
454
|
private suspend fun applyPatchAssetIfPossible(
|
|
432
455
|
assetPath: String,
|
|
433
456
|
changedAsset: ChangedAssetDescriptor,
|
|
@@ -438,81 +461,97 @@ class BundleFileStorageService(
|
|
|
438
461
|
tempDir: File,
|
|
439
462
|
diffFiles: MutableList<DiffProgressFileSnapshot>,
|
|
440
463
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
441
|
-
):
|
|
442
|
-
val patch = changedAsset.patch ?: return
|
|
464
|
+
): PatchOutcome {
|
|
465
|
+
val patch = changedAsset.patch ?: return PatchOutcome.NOT_ATTEMPTED
|
|
443
466
|
if (patch.algorithm != "bsdiff" || currentBundleId != patch.baseBundleId) {
|
|
444
|
-
return
|
|
467
|
+
return PatchOutcome.NOT_ATTEMPTED
|
|
445
468
|
}
|
|
446
469
|
|
|
447
|
-
|
|
448
|
-
val
|
|
470
|
+
// The patch targets the running bundle, so from here a miss is a fallback.
|
|
471
|
+
val sourceDir = activeBundleDir ?: return PatchOutcome.FAILED
|
|
472
|
+
val sourceFile = RelativePathResolver.resolveInside(sourceDir, assetPath) ?: return PatchOutcome.FAILED
|
|
449
473
|
if (!sourceFile.exists() || !HashUtils.verifyHash(sourceFile, patch.baseFileHash)) {
|
|
450
|
-
return
|
|
474
|
+
return PatchOutcome.FAILED
|
|
451
475
|
}
|
|
452
476
|
|
|
453
477
|
val patchFile = patchTempFile(tempDir, assetPath)
|
|
454
478
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
479
|
+
val patched =
|
|
480
|
+
try {
|
|
481
|
+
when (
|
|
482
|
+
val patchDownloadResult =
|
|
483
|
+
downloadService.downloadFile(
|
|
484
|
+
URL(patch.patchUrl),
|
|
485
|
+
patchFile,
|
|
486
|
+
) { downloadProgress ->
|
|
487
|
+
updateDiffProgressFile(
|
|
488
|
+
files = diffFiles,
|
|
489
|
+
assetPath = assetPath,
|
|
490
|
+
status = "downloading",
|
|
491
|
+
progress = downloadProgress.progress,
|
|
492
|
+
downloadPath = patchDownloadPath(assetPath),
|
|
493
|
+
downloadedBytes = downloadProgress.downloadedBytes,
|
|
494
|
+
totalBytes = downloadProgress.totalBytes,
|
|
495
|
+
)
|
|
496
|
+
emitDiffProgress(
|
|
497
|
+
progressCallback = progressCallback,
|
|
498
|
+
phase = "downloading",
|
|
499
|
+
files = diffFiles,
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
) {
|
|
503
|
+
is DownloadResult.Error -> {
|
|
504
|
+
false
|
|
476
505
|
}
|
|
477
|
-
) {
|
|
478
|
-
is DownloadResult.Error -> {
|
|
479
|
-
false
|
|
480
|
-
}
|
|
481
506
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
507
|
+
is DownloadResult.Success -> {
|
|
508
|
+
if (!HashUtils.verifyHash(patchDownloadResult.file, patch.patchFileHash)) {
|
|
509
|
+
false
|
|
510
|
+
} else {
|
|
511
|
+
withContext(Dispatchers.IO) {
|
|
512
|
+
BsdiffPatch.apply(sourceFile, patchDownloadResult.file, targetFile)
|
|
513
|
+
}
|
|
514
|
+
HashUtils.verifyHash(targetFile, expectedHash).also { matches ->
|
|
515
|
+
if (matches) {
|
|
516
|
+
Log.d(
|
|
517
|
+
TAG,
|
|
518
|
+
"HotUpdaterBsdiffPatchApplied asset=$assetPath baseBundleId=${patch.baseBundleId}",
|
|
519
|
+
)
|
|
520
|
+
}
|
|
495
521
|
}
|
|
496
522
|
}
|
|
497
523
|
}
|
|
498
524
|
}
|
|
525
|
+
} catch (_: Exception) {
|
|
526
|
+
false
|
|
527
|
+
} finally {
|
|
528
|
+
patchFile.delete()
|
|
529
|
+
if (!targetFile.exists() || !HashUtils.verifyHash(targetFile, expectedHash)) {
|
|
530
|
+
targetFile.delete()
|
|
531
|
+
}
|
|
499
532
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}.also { patched ->
|
|
508
|
-
if (!patched) {
|
|
509
|
-
resetDiffProgressFile(
|
|
510
|
-
files = diffFiles,
|
|
511
|
-
assetPath = assetPath,
|
|
512
|
-
progressCallback = progressCallback,
|
|
513
|
-
)
|
|
514
|
-
}
|
|
533
|
+
if (!patched) {
|
|
534
|
+
resetDiffProgressFile(
|
|
535
|
+
files = diffFiles,
|
|
536
|
+
assetPath = assetPath,
|
|
537
|
+
progressCallback = progressCallback,
|
|
538
|
+
)
|
|
539
|
+
return PatchOutcome.FAILED
|
|
515
540
|
}
|
|
541
|
+
return PatchOutcome.APPLIED
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private enum class PatchOutcome {
|
|
545
|
+
NOT_ATTEMPTED,
|
|
546
|
+
APPLIED,
|
|
547
|
+
FAILED,
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** How one file arrived under the per-file plan. */
|
|
551
|
+
private enum class FileDelivery {
|
|
552
|
+
PATCHED,
|
|
553
|
+
DOWNLOADED,
|
|
554
|
+
DOWNLOADED_AFTER_PATCH_FAILURE,
|
|
516
555
|
}
|
|
517
556
|
|
|
518
557
|
private data class ParsedBundleManifest(
|
|
@@ -553,10 +592,6 @@ class BundleFileStorageService(
|
|
|
553
592
|
private var hasPreparedLaunch = false
|
|
554
593
|
private var currentLaunchReport: LaunchReport? = null
|
|
555
594
|
|
|
556
|
-
@Volatile
|
|
557
|
-
private var currentInstallationIdentity: InstallationIdentity? = null
|
|
558
|
-
private val installationIdentityLock = Any()
|
|
559
|
-
|
|
560
595
|
@Volatile
|
|
561
596
|
private var activeBundleMetadataSnapshot: ActiveBundleMetadataSnapshot? = null
|
|
562
597
|
private val activeBundleMetadataLock = Any()
|
|
@@ -574,8 +609,6 @@ class BundleFileStorageService(
|
|
|
574
609
|
|
|
575
610
|
private fun getLaunchReportFile(): File = File(getBundleStoreDir(), LaunchReport.LAUNCH_REPORT_FILENAME)
|
|
576
611
|
|
|
577
|
-
private fun getInstallationIdentityFile(): File = File(getBundleStoreDir(), InstallationIdentity.IDENTITY_FILENAME)
|
|
578
|
-
|
|
579
612
|
// MARK: - Metadata Operations
|
|
580
613
|
|
|
581
614
|
private fun loadMetadataOrNull(): BundleMetadata? = BundleMetadata.loadFromFile(getMetadataFile(), isolationKey)
|
|
@@ -613,25 +646,6 @@ class BundleFileStorageService(
|
|
|
613
646
|
"appVersion"
|
|
614
647
|
}
|
|
615
648
|
|
|
616
|
-
private fun loadInstallationIdentity(): InstallationIdentity? =
|
|
617
|
-
currentInstallationIdentity ?: InstallationIdentity.loadFromFile(getInstallationIdentityFile())?.also {
|
|
618
|
-
currentInstallationIdentity = it
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
private fun saveInstallationIdentity(identity: InstallationIdentity): Boolean {
|
|
622
|
-
currentInstallationIdentity = identity
|
|
623
|
-
return identity.saveToFile(getInstallationIdentityFile())
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private fun getOrCreateInstallationIdentity(): InstallationIdentity =
|
|
627
|
-
synchronized(installationIdentityLock) {
|
|
628
|
-
loadInstallationIdentity()?.let { return@synchronized it }
|
|
629
|
-
|
|
630
|
-
InstallationIdentity(installId = UUID.randomUUID().toString()).also {
|
|
631
|
-
saveInstallationIdentity(it)
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
649
|
private fun getKnownLaunchBundleId(metadata: BundleMetadata): String =
|
|
636
650
|
getCurrentVerifiedBundleId(metadata) ?: HotUpdaterImpl.getMinBundleId()
|
|
637
651
|
|
|
@@ -949,14 +963,27 @@ class BundleFileStorageService(
|
|
|
949
963
|
SignatureVerifier.verifyHashSignature(context, asset.fileHash, signature)
|
|
950
964
|
}
|
|
951
965
|
|
|
966
|
+
/**
|
|
967
|
+
* @param hashMismatch how a hash mismatch is reported: a downloaded file
|
|
968
|
+
* that does not match is a hash mismatch, while a patched or extracted file
|
|
969
|
+
* that does not match is a patch or extract failure.
|
|
970
|
+
*/
|
|
952
971
|
private fun verifyManifestAssetFileOrThrow(
|
|
953
972
|
file: File,
|
|
954
973
|
asset: ParsedManifestAsset,
|
|
974
|
+
hashMismatch: UpdateFailure = UpdateFailure.download(UpdateFailureReason.HASH_MISMATCH),
|
|
955
975
|
) {
|
|
956
976
|
try {
|
|
957
977
|
verifyManifestAssetFile(file, asset)
|
|
958
978
|
} catch (e: SignatureVerificationException) {
|
|
959
|
-
throw HotUpdaterException.signatureVerificationFailed(
|
|
979
|
+
throw HotUpdaterException.signatureVerificationFailed(
|
|
980
|
+
e,
|
|
981
|
+
if (e is SignatureVerificationException.FileHashMismatch) {
|
|
982
|
+
hashMismatch
|
|
983
|
+
} else {
|
|
984
|
+
UpdateFailure.ofVerificationError(e)
|
|
985
|
+
},
|
|
986
|
+
)
|
|
960
987
|
}
|
|
961
988
|
}
|
|
962
989
|
|
|
@@ -1033,7 +1060,7 @@ class BundleFileStorageService(
|
|
|
1033
1060
|
getCurrentVerifiedBundleId(metadata)?.takeIf { it != bundleId }
|
|
1034
1061
|
val incomingSelection = pendingInstallSelections.remove(bundleId)
|
|
1035
1062
|
if (incomingSelection != null && !isSelectionCurrent(incomingSelection)) {
|
|
1036
|
-
throw
|
|
1063
|
+
throw StaleReleaseSelectionException()
|
|
1037
1064
|
}
|
|
1038
1065
|
val currentVerifiedSelection =
|
|
1039
1066
|
when {
|
|
@@ -1406,25 +1433,6 @@ class BundleFileStorageService(
|
|
|
1406
1433
|
}
|
|
1407
1434
|
}
|
|
1408
1435
|
|
|
1409
|
-
override fun getInstallId(): String = getOrCreateInstallationIdentity().installId
|
|
1410
|
-
|
|
1411
|
-
override fun getUserId(): String? = getOrCreateInstallationIdentity().userId
|
|
1412
|
-
|
|
1413
|
-
override fun getUsername(): String? = getOrCreateInstallationIdentity().username
|
|
1414
|
-
|
|
1415
|
-
override fun setUser(
|
|
1416
|
-
userId: String?,
|
|
1417
|
-
username: String?,
|
|
1418
|
-
) = synchronized(installationIdentityLock) {
|
|
1419
|
-
saveInstallationIdentity(
|
|
1420
|
-
getOrCreateInstallationIdentity().copy(
|
|
1421
|
-
userId = userId?.trim()?.takeIf { it.isNotEmpty() },
|
|
1422
|
-
username = username?.trim()?.takeIf { it.isNotEmpty() },
|
|
1423
|
-
),
|
|
1424
|
-
)
|
|
1425
|
-
Unit
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
1436
|
// MARK: - Bundle URL Operations
|
|
1429
1437
|
|
|
1430
1438
|
override fun setBundleURL(localPath: String?): Boolean {
|
|
@@ -1500,16 +1508,17 @@ class BundleFileStorageService(
|
|
|
1500
1508
|
assets: Map<String, ChangedAssetDescriptor>,
|
|
1501
1509
|
archiveUrl: String?,
|
|
1502
1510
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
1503
|
-
) =
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1511
|
+
): UpdateBundleResult =
|
|
1512
|
+
updateMutex.withLock {
|
|
1513
|
+
updateBundleSerialized(
|
|
1514
|
+
bundleId = bundleId,
|
|
1515
|
+
manifestUrl = manifestUrl,
|
|
1516
|
+
manifestFileHash = manifestFileHash,
|
|
1517
|
+
changedAssets = assets,
|
|
1518
|
+
archiveUrl = archiveUrl,
|
|
1519
|
+
progressCallback = progressCallback,
|
|
1520
|
+
)
|
|
1521
|
+
}
|
|
1513
1522
|
|
|
1514
1523
|
private suspend fun updateBundleSerialized(
|
|
1515
1524
|
bundleId: String,
|
|
@@ -1518,7 +1527,7 @@ class BundleFileStorageService(
|
|
|
1518
1527
|
changedAssets: Map<String, ChangedAssetDescriptor>,
|
|
1519
1528
|
archiveUrl: String?,
|
|
1520
1529
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
1521
|
-
) {
|
|
1530
|
+
): UpdateBundleResult {
|
|
1522
1531
|
Log.d(
|
|
1523
1532
|
TAG,
|
|
1524
1533
|
"updateBundle bundleId $bundleId manifestUrl $manifestUrl",
|
|
@@ -1531,15 +1540,17 @@ class BundleFileStorageService(
|
|
|
1531
1540
|
}
|
|
1532
1541
|
|
|
1533
1542
|
val bundleStoreDir = getBundleStoreDir()
|
|
1534
|
-
|
|
1535
|
-
bundleStoreDir.
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1543
|
+
updateStep(UpdateFailureStage.INSTALL, resource = null) {
|
|
1544
|
+
if (!bundleStoreDir.exists()) {
|
|
1545
|
+
bundleStoreDir.mkdirs()
|
|
1546
|
+
}
|
|
1547
|
+
if (!recoverInterruptedPromotions()) {
|
|
1548
|
+
throw HotUpdaterException.moveOperationFailed()
|
|
1549
|
+
}
|
|
1539
1550
|
}
|
|
1540
1551
|
|
|
1541
1552
|
val finalBundleDir = File(bundleStoreDir, bundleId)
|
|
1542
|
-
withContext(Dispatchers.IO) {
|
|
1553
|
+
return withContext(Dispatchers.IO) {
|
|
1543
1554
|
updateBundleFromManifest(
|
|
1544
1555
|
bundleId = bundleId,
|
|
1545
1556
|
manifestUrl = manifestUrl,
|
|
@@ -1553,6 +1564,60 @@ class BundleFileStorageService(
|
|
|
1553
1564
|
}
|
|
1554
1565
|
}
|
|
1555
1566
|
|
|
1567
|
+
/**
|
|
1568
|
+
* Runs one step of the update pipeline and gives its failure a
|
|
1569
|
+
* classification for JS: [resource] is what the step fetches or applies,
|
|
1570
|
+
* and an error nothing anticipated is reported as unknown in [stage].
|
|
1571
|
+
*/
|
|
1572
|
+
private inline fun <T> updateStep(
|
|
1573
|
+
stage: UpdateFailureStage,
|
|
1574
|
+
resource: UpdateFailureResource?,
|
|
1575
|
+
block: () -> T,
|
|
1576
|
+
): T =
|
|
1577
|
+
try {
|
|
1578
|
+
block()
|
|
1579
|
+
} catch (e: Exception) {
|
|
1580
|
+
throw classifyUpdateFailure(e, stage, resource)
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* Gives a failure of the update pipeline its classification for JS.
|
|
1585
|
+
* Failures that are not update failures pass through unchanged, and an
|
|
1586
|
+
* already classified failure only gains a [resource] it did not name.
|
|
1587
|
+
*/
|
|
1588
|
+
private fun classifyUpdateFailure(
|
|
1589
|
+
error: Exception,
|
|
1590
|
+
stage: UpdateFailureStage,
|
|
1591
|
+
resource: UpdateFailureResource?,
|
|
1592
|
+
): Exception =
|
|
1593
|
+
when (error) {
|
|
1594
|
+
is StaleReleaseSelectionException, is CancellationException -> {
|
|
1595
|
+
error
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
is HotUpdaterException -> {
|
|
1599
|
+
error.withResource(resource)
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
else -> {
|
|
1603
|
+
HotUpdaterException.unexpected(
|
|
1604
|
+
error,
|
|
1605
|
+
UpdateFailure.ofUnexpectedError(error, stage).copy(resource = resource),
|
|
1606
|
+
)
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/** The rejection for a download that returned [error]. */
|
|
1611
|
+
private fun downloadError(error: Exception): HotUpdaterException =
|
|
1612
|
+
if (error is IncompleteDownloadException) {
|
|
1613
|
+
HotUpdaterException.incompleteDownload(error.expectedSize, error.actualSize)
|
|
1614
|
+
} else {
|
|
1615
|
+
HotUpdaterException.downloadFailed(
|
|
1616
|
+
error,
|
|
1617
|
+
UpdateFailure.ofDownloadError(error) { NetworkState.hasNoActiveNetwork(context) },
|
|
1618
|
+
)
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1556
1621
|
private suspend fun downloadManifestAsset(
|
|
1557
1622
|
assetPath: String,
|
|
1558
1623
|
expectedAsset: ParsedManifestAsset,
|
|
@@ -1563,10 +1628,10 @@ class BundleFileStorageService(
|
|
|
1563
1628
|
tempDir: File,
|
|
1564
1629
|
progressFile: DiffProgressFileSnapshot,
|
|
1565
1630
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
1566
|
-
) {
|
|
1631
|
+
): FileDelivery {
|
|
1567
1632
|
val expectedHash = expectedAsset.fileHash
|
|
1568
1633
|
val diffFiles = mutableListOf(progressFile)
|
|
1569
|
-
val
|
|
1634
|
+
val patchOutcome =
|
|
1570
1635
|
applyPatchAssetIfPossible(
|
|
1571
1636
|
assetPath = assetPath,
|
|
1572
1637
|
changedAsset = changedAsset,
|
|
@@ -1578,8 +1643,12 @@ class BundleFileStorageService(
|
|
|
1578
1643
|
diffFiles = diffFiles,
|
|
1579
1644
|
progressCallback = progressCallback,
|
|
1580
1645
|
)
|
|
1581
|
-
if (
|
|
1582
|
-
verifyManifestAssetFileOrThrow(
|
|
1646
|
+
if (patchOutcome == PatchOutcome.APPLIED) {
|
|
1647
|
+
verifyManifestAssetFileOrThrow(
|
|
1648
|
+
targetFile,
|
|
1649
|
+
expectedAsset,
|
|
1650
|
+
hashMismatch = PATCH_FAILURE,
|
|
1651
|
+
)
|
|
1583
1652
|
updateDiffProgressFile(
|
|
1584
1653
|
files = diffFiles,
|
|
1585
1654
|
assetPath = assetPath,
|
|
@@ -1596,7 +1665,7 @@ class BundleFileStorageService(
|
|
|
1596
1665
|
},
|
|
1597
1666
|
files = diffFiles,
|
|
1598
1667
|
)
|
|
1599
|
-
return
|
|
1668
|
+
return FileDelivery.PATCHED
|
|
1600
1669
|
}
|
|
1601
1670
|
|
|
1602
1671
|
val changedAssetFileUrl =
|
|
@@ -1615,6 +1684,7 @@ class BundleFileStorageService(
|
|
|
1615
1684
|
)
|
|
1616
1685
|
throw HotUpdaterException.downloadFailed(
|
|
1617
1686
|
IllegalStateException("Changed asset fileUrl missing and patch could not be applied: $assetPath"),
|
|
1687
|
+
PATCH_FAILURE,
|
|
1618
1688
|
)
|
|
1619
1689
|
}
|
|
1620
1690
|
|
|
@@ -1663,15 +1733,7 @@ class BundleFileStorageService(
|
|
|
1663
1733
|
phase = "downloading",
|
|
1664
1734
|
files = diffFiles,
|
|
1665
1735
|
)
|
|
1666
|
-
|
|
1667
|
-
val incompleteEx =
|
|
1668
|
-
assetDownloadResult.exception as IncompleteDownloadException
|
|
1669
|
-
throw HotUpdaterException.incompleteDownload(
|
|
1670
|
-
incompleteEx.expectedSize,
|
|
1671
|
-
incompleteEx.actualSize,
|
|
1672
|
-
)
|
|
1673
|
-
}
|
|
1674
|
-
throw HotUpdaterException.downloadFailed(assetDownloadResult.exception)
|
|
1736
|
+
throw downloadError(assetDownloadResult.exception)
|
|
1675
1737
|
}
|
|
1676
1738
|
|
|
1677
1739
|
is DownloadResult.Success -> {
|
|
@@ -1701,7 +1763,11 @@ class BundleFileStorageService(
|
|
|
1701
1763
|
if (e is HotUpdaterException) {
|
|
1702
1764
|
throw e
|
|
1703
1765
|
}
|
|
1704
|
-
|
|
1766
|
+
// The bytes arrived; what failed is reading them back.
|
|
1767
|
+
throw HotUpdaterException.downloadFailed(
|
|
1768
|
+
e,
|
|
1769
|
+
UpdateFailure.ofUnexpectedError(e, UpdateFailureStage.DOWNLOAD),
|
|
1770
|
+
)
|
|
1705
1771
|
} finally {
|
|
1706
1772
|
if (changedAsset.fileCompression == "br") {
|
|
1707
1773
|
assetDownloadResult.file.delete()
|
|
@@ -1720,6 +1786,11 @@ class BundleFileStorageService(
|
|
|
1720
1786
|
)
|
|
1721
1787
|
}
|
|
1722
1788
|
}
|
|
1789
|
+
return if (patchOutcome == PatchOutcome.FAILED) {
|
|
1790
|
+
FileDelivery.DOWNLOADED_AFTER_PATCH_FAILURE
|
|
1791
|
+
} else {
|
|
1792
|
+
FileDelivery.DOWNLOADED
|
|
1793
|
+
}
|
|
1723
1794
|
}
|
|
1724
1795
|
|
|
1725
1796
|
private fun pruneStagingDirectory(
|
|
@@ -1730,11 +1801,15 @@ class BundleFileStorageService(
|
|
|
1730
1801
|
directory.listFiles()?.forEach { file ->
|
|
1731
1802
|
val relativePath = prefix + file.name
|
|
1732
1803
|
if (file.canonicalFile != File(directory.canonicalFile, file.name)) {
|
|
1733
|
-
|
|
1804
|
+
if (!file.delete()) {
|
|
1805
|
+
throw HotUpdaterException.storageFailed("Cannot remove staging link: $relativePath")
|
|
1806
|
+
}
|
|
1734
1807
|
} else if (file.isDirectory && assets.any { it.startsWith("$relativePath/") }) {
|
|
1735
1808
|
pruneStagingDirectory(file, assets, "$relativePath/")
|
|
1736
1809
|
} else if (!file.isFile || relativePath !in assets) {
|
|
1737
|
-
|
|
1810
|
+
if (!file.deleteRecursively()) {
|
|
1811
|
+
throw HotUpdaterException.storageFailed("Cannot remove stale staging file: $relativePath")
|
|
1812
|
+
}
|
|
1738
1813
|
}
|
|
1739
1814
|
}
|
|
1740
1815
|
}
|
|
@@ -1846,7 +1921,11 @@ class BundleFileStorageService(
|
|
|
1846
1921
|
val extractedFile =
|
|
1847
1922
|
RelativePathResolver.resolveInside(extractedDir, assetPath)
|
|
1848
1923
|
?: throw IllegalStateException("Archive output path is invalid")
|
|
1849
|
-
verifyManifestAssetFileOrThrow(
|
|
1924
|
+
verifyManifestAssetFileOrThrow(
|
|
1925
|
+
extractedFile,
|
|
1926
|
+
expectedAsset,
|
|
1927
|
+
hashMismatch = UpdateFailure.install(UpdateFailureReason.EXTRACT),
|
|
1928
|
+
)
|
|
1850
1929
|
}
|
|
1851
1930
|
|
|
1852
1931
|
if (!tmpDir.renameTo(localBackupDir)) {
|
|
@@ -1874,7 +1953,8 @@ class BundleFileStorageService(
|
|
|
1874
1953
|
tmpDir.exists() ||
|
|
1875
1954
|
(localBackupDir.exists() && localBackupDir.renameTo(tmpDir))
|
|
1876
1955
|
if (!canUseIndividualPlan) {
|
|
1877
|
-
|
|
1956
|
+
// Only the staging directory swap can leave tmpDir missing.
|
|
1957
|
+
throw HotUpdaterException.unexpected(error, UpdateFailure.install(UpdateFailureReason.STORAGE))
|
|
1878
1958
|
}
|
|
1879
1959
|
extractedDir.deleteRecursively()
|
|
1880
1960
|
null
|
|
@@ -1894,7 +1974,7 @@ class BundleFileStorageService(
|
|
|
1894
1974
|
bundleStoreDir: File,
|
|
1895
1975
|
finalBundleDir: File,
|
|
1896
1976
|
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
1897
|
-
) {
|
|
1977
|
+
): UpdateBundleResult {
|
|
1898
1978
|
val activeBundleDir = getActiveBundleDir()
|
|
1899
1979
|
val currentBundleId = getBundleId()
|
|
1900
1980
|
val baseDir =
|
|
@@ -1911,257 +1991,280 @@ class BundleFileStorageService(
|
|
|
1911
1991
|
|
|
1912
1992
|
try {
|
|
1913
1993
|
val manifestFile = File(tempDir, "manifest.json")
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
manifestDownloadResult.exception
|
|
1938
|
-
|
|
1939
|
-
incompleteEx.expectedSize,
|
|
1940
|
-
incompleteEx.actualSize,
|
|
1941
|
-
)
|
|
1942
|
-
}
|
|
1943
|
-
throw HotUpdaterException.downloadFailed(manifestDownloadResult.exception)
|
|
1944
|
-
}
|
|
1994
|
+
val targetManifest =
|
|
1995
|
+
updateStep(UpdateFailureStage.DOWNLOAD, UpdateFailureResource.MANIFEST) {
|
|
1996
|
+
emitDiffProgress(
|
|
1997
|
+
progressCallback = progressCallback,
|
|
1998
|
+
phase = "manifest",
|
|
1999
|
+
files = diffFiles,
|
|
2000
|
+
manifestProgress = 0.0,
|
|
2001
|
+
)
|
|
2002
|
+
when (
|
|
2003
|
+
val manifestDownloadResult =
|
|
2004
|
+
downloadService.downloadFile(
|
|
2005
|
+
URL(manifestUrl),
|
|
2006
|
+
manifestFile,
|
|
2007
|
+
) { downloadProgress ->
|
|
2008
|
+
emitDiffProgress(
|
|
2009
|
+
progressCallback = progressCallback,
|
|
2010
|
+
phase = "manifest",
|
|
2011
|
+
files = diffFiles,
|
|
2012
|
+
manifestProgress = downloadProgress.progress,
|
|
2013
|
+
)
|
|
2014
|
+
}
|
|
2015
|
+
) {
|
|
2016
|
+
is DownloadResult.Error -> {
|
|
2017
|
+
throw downloadError(manifestDownloadResult.exception)
|
|
2018
|
+
}
|
|
1945
2019
|
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2020
|
+
is DownloadResult.Success -> {
|
|
2021
|
+
Unit
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1950
2024
|
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2025
|
+
try {
|
|
2026
|
+
SignatureVerifier.verifyBundle(context, manifestFile, manifestFileHash)
|
|
2027
|
+
} catch (e: SignatureVerificationException) {
|
|
2028
|
+
throw HotUpdaterException.signatureVerificationFailed(e)
|
|
2029
|
+
}
|
|
1956
2030
|
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2031
|
+
val manifest = parseBundleManifestFromFile(manifestFile) ?: throw HotUpdaterException.invalidBundle()
|
|
2032
|
+
if (manifest.bundleId != bundleId) {
|
|
2033
|
+
throw HotUpdaterException.invalidBundle()
|
|
2034
|
+
}
|
|
2035
|
+
// Reuse never bypasses the complete artifact contract.
|
|
2036
|
+
if (manifest.assets.keys != changedAssets.keys) throw HotUpdaterException.invalidBundle()
|
|
2037
|
+
manifest.assets.forEach { (path, asset) ->
|
|
2038
|
+
val descriptor = changedAssets.getValue(path)
|
|
2039
|
+
if (RelativePathResolver.normalizeRelativePath(path) != path ||
|
|
2040
|
+
path == "manifest.json" ||
|
|
2041
|
+
descriptor.fileUrl.isNullOrBlank() ||
|
|
2042
|
+
!descriptor.fileHash.equals(asset.fileHash, ignoreCase = true) ||
|
|
2043
|
+
RelativePathResolver.resolveInside(tmpDir, path) == null
|
|
2044
|
+
) {
|
|
2045
|
+
throw HotUpdaterException.invalidBundle()
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
manifest
|
|
1972
2049
|
}
|
|
2050
|
+
updateStep(UpdateFailureStage.INSTALL, resource = null) {
|
|
2051
|
+
if (!tmpDir.isDirectory && !tmpDir.mkdirs()) throw HotUpdaterException.directoryCreationFailed()
|
|
2052
|
+
pruneStagingDirectory(tmpDir, targetManifest.assets.keys)
|
|
1973
2053
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2054
|
+
val downloads =
|
|
2055
|
+
updateStep(UpdateFailureStage.INSTALL, UpdateFailureResource.FILE) {
|
|
2056
|
+
val targetEntries = targetManifest.assets.entries.sortedBy { it.key }
|
|
2057
|
+
val localDirectories = listOfNotNull(activeBundleDir, finalBundleDir).distinct().filter { it.isDirectory }
|
|
2058
|
+
val downloads = mutableMapOf<String, ChangedAssetDescriptor>()
|
|
2059
|
+
targetEntries.forEachIndexed { index, (assetPath, expectedAsset) ->
|
|
2060
|
+
progressCallback(
|
|
2061
|
+
UpdateProgressPayload(
|
|
2062
|
+
progress = 0.15 + 0.05 * index / targetEntries.size.coerceAtLeast(1),
|
|
2063
|
+
artifactType = "diff",
|
|
2064
|
+
details = DiffProgressDetails(totalFilesCount = 0, completedFilesCount = 0),
|
|
2065
|
+
),
|
|
2066
|
+
)
|
|
2067
|
+
val targetFile = RelativePathResolver.resolveInside(tmpDir, assetPath) ?: throw HotUpdaterException.invalidBundle()
|
|
2068
|
+
targetFile.parentFile?.mkdirs()
|
|
2069
|
+
if (targetFile.isFile && runCatching { verifyManifestAssetFileOrThrow(targetFile, expectedAsset) }.isSuccess) {
|
|
2070
|
+
return@forEachIndexed
|
|
2071
|
+
}
|
|
2072
|
+
if (targetFile.exists()) targetFile.delete()
|
|
2073
|
+
var reused = false
|
|
2074
|
+
for (directory in localDirectories) {
|
|
2075
|
+
val sourceFile = RelativePathResolver.resolveInside(directory, assetPath) ?: continue
|
|
2076
|
+
val sourceMatches =
|
|
2077
|
+
sourceFile.isFile && runCatching { verifyManifestAssetFileOrThrow(sourceFile, expectedAsset) }.isSuccess
|
|
2078
|
+
if (!sourceMatches) continue
|
|
2079
|
+
if (runCatching {
|
|
2080
|
+
copyBundleFile(sourceFile, targetFile)
|
|
2081
|
+
verifyManifestAssetFileOrThrow(targetFile, expectedAsset)
|
|
2082
|
+
}.isSuccess
|
|
2083
|
+
) {
|
|
2084
|
+
reused = true
|
|
2085
|
+
break
|
|
2086
|
+
}
|
|
2087
|
+
targetFile.delete()
|
|
2088
|
+
}
|
|
2089
|
+
if (reused) return@forEachIndexed
|
|
2090
|
+
if (builtInAssetResolver.copyIfMatches(assetPath, expectedAsset.fileHash, targetFile)) {
|
|
1999
2091
|
verifyManifestAssetFileOrThrow(targetFile, expectedAsset)
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
break
|
|
2092
|
+
return@forEachIndexed
|
|
2093
|
+
}
|
|
2094
|
+
downloads[assetPath] = changedAssets.getValue(assetPath)
|
|
2004
2095
|
}
|
|
2005
|
-
|
|
2096
|
+
downloads
|
|
2006
2097
|
}
|
|
2007
|
-
if (reused) return@forEachIndexed
|
|
2008
|
-
if (builtInAssetResolver.copyIfMatches(assetPath, expectedAsset.fileHash, targetFile)) {
|
|
2009
|
-
verifyManifestAssetFileOrThrow(targetFile, expectedAsset)
|
|
2010
|
-
return@forEachIndexed
|
|
2011
|
-
}
|
|
2012
|
-
downloads[assetPath] = changedAssets.getValue(assetPath)
|
|
2013
|
-
}
|
|
2014
2098
|
val selectedArchive = selectArchive(targetManifest, downloads, archiveUrl)
|
|
2015
2099
|
val archiveProgress =
|
|
2016
2100
|
if (selectedArchive != null) {
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2101
|
+
updateStep(UpdateFailureStage.DOWNLOAD, UpdateFailureResource.ARCHIVE) {
|
|
2102
|
+
tryInstallArchive(
|
|
2103
|
+
archiveUrl = archiveUrl!!,
|
|
2104
|
+
archive = selectedArchive,
|
|
2105
|
+
manifest = targetManifest,
|
|
2106
|
+
tempDir = tempDir,
|
|
2107
|
+
bundleStoreDir = bundleStoreDir,
|
|
2108
|
+
tmpDir = tmpDir,
|
|
2109
|
+
bundleId = bundleId,
|
|
2110
|
+
progressCallback = progressCallback,
|
|
2111
|
+
)
|
|
2112
|
+
}
|
|
2027
2113
|
} else {
|
|
2028
2114
|
null
|
|
2029
2115
|
}
|
|
2030
2116
|
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
val
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2117
|
+
val fileDeliveries =
|
|
2118
|
+
if (archiveProgress != null) {
|
|
2119
|
+
diffFiles = archiveProgress
|
|
2120
|
+
emptyList()
|
|
2121
|
+
} else {
|
|
2122
|
+
diffFiles = createDiffProgressFiles(downloads)
|
|
2123
|
+
emitDiffProgress(progressCallback, "downloading", diffFiles)
|
|
2124
|
+
val progressLock = Any()
|
|
2125
|
+
coroutineScope {
|
|
2126
|
+
val permits = Semaphore(4)
|
|
2127
|
+
diffFiles
|
|
2128
|
+
.toList()
|
|
2129
|
+
.map { progressFile ->
|
|
2130
|
+
async {
|
|
2131
|
+
permits.withPermit {
|
|
2132
|
+
updateStep(UpdateFailureStage.DOWNLOAD, UpdateFailureResource.FILE) {
|
|
2133
|
+
val path = progressFile.path
|
|
2134
|
+
val workerTemp =
|
|
2135
|
+
File(tempDir, progressFile.order.toString()).apply { mkdirs() }
|
|
2136
|
+
downloadManifestAsset(
|
|
2137
|
+
assetPath = path,
|
|
2138
|
+
expectedAsset = targetManifest.assets.getValue(path),
|
|
2139
|
+
changedAsset = downloads.getValue(path),
|
|
2140
|
+
currentBundleId = currentBundleId,
|
|
2141
|
+
activeBundleDir = activeBundleDir,
|
|
2142
|
+
targetFile =
|
|
2143
|
+
RelativePathResolver.resolveInside(tmpDir, path)
|
|
2144
|
+
?: throw HotUpdaterException.invalidBundle(),
|
|
2145
|
+
tempDir = workerTemp,
|
|
2146
|
+
progressFile = progressFile,
|
|
2147
|
+
progressCallback = { payload ->
|
|
2148
|
+
synchronized(progressLock) {
|
|
2149
|
+
payload.details?.files?.firstOrNull()?.let { snapshot ->
|
|
2150
|
+
val index = diffFiles.indexOfFirst { it.path == path }
|
|
2151
|
+
diffFiles[index] = snapshot
|
|
2152
|
+
}
|
|
2153
|
+
emitDiffProgress(progressCallback, "downloading", diffFiles)
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
)
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2068
2159
|
}
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2160
|
+
}.awaitAll()
|
|
2161
|
+
}
|
|
2071
2162
|
}
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
emitDiffProgress(
|
|
2075
|
-
progressCallback = progressCallback,
|
|
2076
|
-
phase = "finalizing",
|
|
2077
|
-
files = diffFiles,
|
|
2078
|
-
)
|
|
2079
2163
|
|
|
2080
|
-
|
|
2164
|
+
updateStep(UpdateFailureStage.INSTALL, resource = null) {
|
|
2165
|
+
emitDiffProgress(
|
|
2166
|
+
progressCallback = progressCallback,
|
|
2167
|
+
phase = "finalizing",
|
|
2168
|
+
files = diffFiles,
|
|
2169
|
+
)
|
|
2081
2170
|
|
|
2082
|
-
|
|
2083
|
-
if (extractedIndex == null) {
|
|
2084
|
-
throw HotUpdaterException.invalidBundle()
|
|
2085
|
-
}
|
|
2171
|
+
manifestFile.copyTo(File(tmpDir, "manifest.json"), overwrite = true)
|
|
2086
2172
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
}
|
|
2173
|
+
val extractedIndex = tmpDir.walk().find { it.name == "index.android.bundle" }
|
|
2174
|
+
if (extractedIndex == null) {
|
|
2175
|
+
// The manifest lists no platform bundle.
|
|
2176
|
+
throw HotUpdaterException.invalidBundle().withResource(UpdateFailureResource.MANIFEST)
|
|
2177
|
+
}
|
|
2093
2178
|
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
if (hadExistingFinal && !directoryRenamer(finalBundleDir, backupDir)) {
|
|
2179
|
+
val backupDir = installBackupDir(bundleId)
|
|
2180
|
+
val updatedMetadata =
|
|
2181
|
+
synchronized(releaseStateLock) {
|
|
2182
|
+
if (backupDir.exists()) {
|
|
2099
2183
|
throw HotUpdaterException.moveOperationFailed()
|
|
2100
2184
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2185
|
+
|
|
2186
|
+
val hadExistingFinal = finalBundleDir.exists()
|
|
2187
|
+
var promoted = false
|
|
2188
|
+
val previousBundleUrl = preferences.getItem("HotUpdaterBundleURL")
|
|
2189
|
+
try {
|
|
2190
|
+
if (hadExistingFinal && !directoryRenamer(finalBundleDir, backupDir)) {
|
|
2191
|
+
throw HotUpdaterException.moveOperationFailed()
|
|
2104
2192
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2193
|
+
if (!directoryRenamer(tmpDir, finalBundleDir)) {
|
|
2194
|
+
if (hadExistingFinal) {
|
|
2195
|
+
directoryRenamer(backupDir, finalBundleDir)
|
|
2196
|
+
}
|
|
2197
|
+
throw HotUpdaterException.moveOperationFailed()
|
|
2198
|
+
}
|
|
2199
|
+
promoted = true
|
|
2108
2200
|
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2201
|
+
val finalIndexFile =
|
|
2202
|
+
resolveBundleFile(finalBundleDir, bundleId)
|
|
2203
|
+
?: throw HotUpdaterException.invalidBundle()
|
|
2204
|
+
if (!isCompleteBundleDirectory(finalBundleDir, bundleId)) {
|
|
2205
|
+
throw HotUpdaterException.invalidBundle()
|
|
2206
|
+
}
|
|
2207
|
+
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
2116
2208
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2209
|
+
val currentMetadata = loadMetadataOrNull() ?: createInitialMetadata()
|
|
2210
|
+
val nextMetadata = prepareMetadataForNewStagingBundle(currentMetadata, bundleId)
|
|
2211
|
+
if (!setBundleURL(finalIndexFile.absolutePath)) {
|
|
2212
|
+
throw HotUpdaterException.storageFailed("Failed to persist bundle URL")
|
|
2213
|
+
}
|
|
2214
|
+
if (!saveMetadata(nextMetadata)) {
|
|
2215
|
+
throw HotUpdaterException.storageFailed("Failed to persist bundle metadata")
|
|
2216
|
+
}
|
|
2125
2217
|
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2218
|
+
if (backupDir.exists() && !backupDir.deleteRecursively()) {
|
|
2219
|
+
Log.w(TAG, "Failed to remove committed install backup: ${backupDir.absolutePath}")
|
|
2220
|
+
}
|
|
2221
|
+
nextMetadata
|
|
2222
|
+
} catch (e: Exception) {
|
|
2223
|
+
runCatching { preferences.setItem("HotUpdaterBundleURL", previousBundleUrl) }
|
|
2224
|
+
clearActiveBundleMetadataSnapshot()
|
|
2225
|
+
if (promoted && finalBundleDir.exists() && !finalBundleDir.deleteRecursively()) {
|
|
2226
|
+
Log.e(TAG, "Failed to remove uncommitted bundle: ${finalBundleDir.absolutePath}")
|
|
2227
|
+
}
|
|
2228
|
+
if (hadExistingFinal &&
|
|
2229
|
+
!finalBundleDir.exists() &&
|
|
2230
|
+
backupDir.exists() &&
|
|
2231
|
+
!directoryRenamer(backupDir, finalBundleDir)
|
|
2232
|
+
) {
|
|
2233
|
+
Log.e(TAG, "Failed to restore install backup: ${backupDir.absolutePath}")
|
|
2234
|
+
}
|
|
2235
|
+
throw e
|
|
2142
2236
|
}
|
|
2143
|
-
throw e
|
|
2144
2237
|
}
|
|
2145
|
-
}
|
|
2146
2238
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2239
|
+
tempDir.deleteRecursively()
|
|
2240
|
+
cleanupOldBundles(bundleStoreDir, updatedMetadata.stableBundleId, bundleId)
|
|
2241
|
+
progressCallback(
|
|
2242
|
+
UpdateProgressPayload(
|
|
2243
|
+
progress = 1.0,
|
|
2244
|
+
artifactType = "diff",
|
|
2245
|
+
details =
|
|
2246
|
+
DiffProgressDetails(
|
|
2247
|
+
totalFilesCount = diffFiles.size,
|
|
2248
|
+
completedFilesCount = diffFiles.count { it.status == "downloaded" },
|
|
2249
|
+
files = diffFiles.toList(),
|
|
2250
|
+
),
|
|
2251
|
+
),
|
|
2252
|
+
)
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
return UpdateBundleResult(
|
|
2256
|
+
delivery =
|
|
2257
|
+
when {
|
|
2258
|
+
archiveProgress != null -> BundleDelivery.ARCHIVE
|
|
2259
|
+
FileDelivery.PATCHED in fileDeliveries -> BundleDelivery.PATCH
|
|
2260
|
+
else -> BundleDelivery.MANIFEST
|
|
2261
|
+
},
|
|
2262
|
+
patchFallback = FileDelivery.DOWNLOADED_AFTER_PATCH_FAILURE in fileDeliveries,
|
|
2160
2263
|
)
|
|
2161
2264
|
} catch (e: Exception) {
|
|
2162
2265
|
tempDir.deleteRecursively()
|
|
2163
2266
|
// A retry rehashes completed files and rejects partial bytes.
|
|
2164
|
-
throw e
|
|
2267
|
+
throw classifyUpdateFailure(e, UpdateFailureStage.DOWNLOAD, resource = null)
|
|
2165
2268
|
}
|
|
2166
2269
|
}
|
|
2167
2270
|
|