@otakit/capacitor-updater 2.3.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -5
- package/android/build.gradle +16 -0
- package/android/src/main/java/com/otakit/updater/BundleCrypto.java +38 -17
- package/android/src/main/java/com/otakit/updater/BundleInfo.java +10 -0
- package/android/src/main/java/com/otakit/updater/BundleStore.java +134 -86
- package/android/src/main/java/com/otakit/updater/CheckFailure.java +53 -0
- package/android/src/main/java/com/otakit/updater/DeltaAssembler.java +46 -41
- package/android/src/main/java/com/otakit/updater/DeviceEventClient.java +125 -26
- package/android/src/main/java/com/otakit/updater/DocumentReadyBridge.java +71 -0
- package/android/src/main/java/com/otakit/updater/DownloadRetry.java +144 -0
- package/android/src/main/java/com/otakit/updater/EventOutbox.java +174 -0
- package/android/src/main/java/com/otakit/updater/FileDownloader.java +80 -0
- package/android/src/main/java/com/otakit/updater/ForegroundDeadline.java +84 -0
- package/android/src/main/java/com/otakit/updater/HashUtils.java +26 -0
- package/android/src/main/java/com/otakit/updater/ManifestClient.java +12 -9
- package/android/src/main/java/com/otakit/updater/ManifestKeyConfig.java +47 -0
- package/android/src/main/java/com/otakit/updater/ManifestVerifier.java +22 -4
- package/android/src/main/java/com/otakit/updater/SDKVersion.java +9 -0
- package/android/src/main/java/com/otakit/updater/UpdateOwner.java +27 -0
- package/android/src/main/java/com/otakit/updater/UpdaterCoordinator.java +154 -59
- package/android/src/main/java/com/otakit/updater/UpdaterPlugin.java +262 -173
- package/dist/esm/definitions.d.ts +3 -3
- package/dist/esm/definitions.d.ts.map +1 -1
- package/ios/Sources/UpdaterPlugin/BundleCrypto.swift +35 -3
- package/ios/Sources/UpdaterPlugin/BundleInfo.swift +13 -0
- package/ios/Sources/UpdaterPlugin/BundleStore.swift +103 -77
- package/ios/Sources/UpdaterPlugin/CheckFailure.swift +41 -0
- package/ios/Sources/UpdaterPlugin/DeltaAssembler.swift +29 -17
- package/ios/Sources/UpdaterPlugin/DeviceEventClient.swift +12 -10
- package/ios/Sources/UpdaterPlugin/DocumentReadyBridge.swift +43 -0
- package/ios/Sources/UpdaterPlugin/DownloadRetry.swift +51 -0
- package/ios/Sources/UpdaterPlugin/Downloader.swift +97 -39
- package/ios/Sources/UpdaterPlugin/EventOutbox.swift +183 -0
- package/ios/Sources/UpdaterPlugin/ForegroundDeadline.swift +94 -0
- package/ios/Sources/UpdaterPlugin/HashUtils.swift +16 -0
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +2 -4
- package/ios/Sources/UpdaterPlugin/ManifestKeyConfig.swift +20 -0
- package/ios/Sources/UpdaterPlugin/ManifestVerifier.swift +7 -1
- package/ios/Sources/UpdaterPlugin/SDKVersion.swift +4 -0
- package/ios/Sources/UpdaterPlugin/UpdateOwner.swift +12 -0
- package/ios/Sources/UpdaterPlugin/UpdaterCoordinator.swift +146 -110
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +206 -202
- package/ios/Tests/UpdaterPluginTests/BundleCryptoTests.swift +80 -0
- package/ios/Tests/UpdaterPluginTests/BundlePersistenceTests.swift +102 -0
- package/ios/Tests/UpdaterPluginTests/CheckFailureTests.swift +55 -0
- package/ios/Tests/UpdaterPluginTests/DeltaCacheIntegrityTests.swift +69 -0
- package/ios/Tests/UpdaterPluginTests/DocumentReadyBridgeTests.swift +102 -0
- package/ios/Tests/UpdaterPluginTests/DownloadIntegrityTests.swift +40 -0
- package/ios/Tests/UpdaterPluginTests/DownloadRetryTests.swift +189 -0
- package/ios/Tests/UpdaterPluginTests/EventDeliveryTests.swift +56 -0
- package/ios/Tests/UpdaterPluginTests/EventOutboxTests.swift +86 -0
- package/ios/Tests/UpdaterPluginTests/ForegroundDeadlineTests.swift +144 -0
- package/ios/Tests/UpdaterPluginTests/ManifestKeyConfigTests.swift +48 -0
- package/ios/Tests/UpdaterPluginTests/UpdateOwnerTests.swift +34 -0
- package/ios/Tests/UpdaterPluginTests/UpdaterCoordinatorTests.swift +394 -0
- package/package.json +7 -3
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
final class UpdaterCoordinator {
|
|
4
|
+
struct Trial: Equatable {
|
|
5
|
+
let bundleId: String
|
|
6
|
+
let activationId = UUID().uuidString
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
struct StateSnapshot {
|
|
5
10
|
let current: BundleInfo
|
|
6
11
|
let fallback: BundleInfo
|
|
@@ -15,6 +20,8 @@ final class UpdaterCoordinator {
|
|
|
15
20
|
let channel: String?
|
|
16
21
|
let releaseId: String?
|
|
17
22
|
let detail: String?
|
|
23
|
+
var attemptId: String? = nil
|
|
24
|
+
var phase: String? = nil
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
enum LatestManifestClassification {
|
|
@@ -25,14 +32,14 @@ final class UpdaterCoordinator {
|
|
|
25
32
|
|
|
26
33
|
struct StartupPreparation {
|
|
27
34
|
let activationPath: String?
|
|
28
|
-
let
|
|
35
|
+
let trial: Trial?
|
|
29
36
|
let cleanupBundleIds: [String]
|
|
30
37
|
let eventPayload: DeviceEventPayload?
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
struct ApplyPreparation {
|
|
34
41
|
let activationPath: String?
|
|
35
|
-
let
|
|
42
|
+
let trial: Trial?
|
|
36
43
|
let cleanupBundleIds: [String]
|
|
37
44
|
|
|
38
45
|
var didApply: Bool {
|
|
@@ -56,6 +63,7 @@ final class UpdaterCoordinator {
|
|
|
56
63
|
private let operationLock = NSLock()
|
|
57
64
|
private let stateLock = NSLock()
|
|
58
65
|
private var operationInProgress = false
|
|
66
|
+
private var activeTrial: Trial?
|
|
59
67
|
|
|
60
68
|
init(store: BundleStore) {
|
|
61
69
|
self.store = store
|
|
@@ -110,18 +118,19 @@ final class UpdaterCoordinator {
|
|
|
110
118
|
|
|
111
119
|
func pruneIncompatibleBundles(
|
|
112
120
|
isCompatibleRuntime: @escaping (BundleInfo) -> Bool
|
|
113
|
-
) -> [String] {
|
|
114
|
-
withStateLock {
|
|
121
|
+
) throws -> [String] {
|
|
122
|
+
try withStateLock {
|
|
123
|
+
_ = try store.protectedBundleIds()
|
|
115
124
|
var cleanupBundleIds = Set<String>()
|
|
116
125
|
|
|
117
126
|
for bundle in store.listDownloadedBundles() where !isCompatibleRuntime(bundle) {
|
|
118
|
-
detachBundleReferencesLocked(bundleId: bundle.id)
|
|
127
|
+
try detachBundleReferencesLocked(bundleId: bundle.id)
|
|
119
128
|
cleanupBundleIds.insert(bundle.id)
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
if let failed = store.getLastFailedBundle(),
|
|
123
132
|
!isCompatibleRuntime(failed) {
|
|
124
|
-
store.setLastFailedBundle(nil)
|
|
133
|
+
try store.setLastFailedBundle(nil)
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
return Array(cleanupBundleIds)
|
|
@@ -130,15 +139,17 @@ final class UpdaterCoordinator {
|
|
|
130
139
|
|
|
131
140
|
func normalizeStartupState(
|
|
132
141
|
isBundleUsable: @escaping (BundleInfo) -> Bool
|
|
133
|
-
) -> StartupPreparation {
|
|
134
|
-
withStateLock {
|
|
142
|
+
) throws -> StartupPreparation {
|
|
143
|
+
try withStateLock {
|
|
144
|
+
_ = try store.protectedBundleIds()
|
|
145
|
+
activeTrial = nil
|
|
135
146
|
var cleanupBundleIds = Set<String>()
|
|
136
|
-
clearStaleBundlePointersLocked()
|
|
137
|
-
normalizeStagedPointerLocked(
|
|
147
|
+
try clearStaleBundlePointersLocked()
|
|
148
|
+
try normalizeStagedPointerLocked(
|
|
138
149
|
isBundleUsable: isBundleUsable,
|
|
139
150
|
cleanupBundleIds: &cleanupBundleIds
|
|
140
151
|
)
|
|
141
|
-
normalizeFallbackPointerLocked(
|
|
152
|
+
try normalizeFallbackPointerLocked(
|
|
142
153
|
isBundleUsable: isBundleUsable,
|
|
143
154
|
cleanupBundleIds: &cleanupBundleIds
|
|
144
155
|
)
|
|
@@ -147,7 +158,7 @@ final class UpdaterCoordinator {
|
|
|
147
158
|
let current = store.getCurrentBundle()
|
|
148
159
|
if !current.isBuiltin,
|
|
149
160
|
current.status == .trial {
|
|
150
|
-
let rollback = rollbackLocked(
|
|
161
|
+
let rollback = try rollbackLocked(
|
|
151
162
|
reason: "app_restarted_before_notify",
|
|
152
163
|
isBundleUsable: isBundleUsable,
|
|
153
164
|
cleanupBundleIds: &cleanupBundleIds
|
|
@@ -157,24 +168,25 @@ final class UpdaterCoordinator {
|
|
|
157
168
|
|
|
158
169
|
var normalizedCurrent = store.getCurrentBundle()
|
|
159
170
|
if !normalizedCurrent.isBuiltin,
|
|
160
|
-
!isBundleUsable(normalizedCurrent) {
|
|
171
|
+
normalizedCurrent.status == .error || !isBundleUsable(normalizedCurrent) {
|
|
161
172
|
cleanupBundleIds.insert(normalizedCurrent.id)
|
|
162
|
-
normalizedCurrent = restoreFallbackOrBuiltinLocked(
|
|
173
|
+
normalizedCurrent = try restoreFallbackOrBuiltinLocked(
|
|
163
174
|
isBundleUsable: isBundleUsable,
|
|
164
|
-
cleanupBundleIds: &cleanupBundleIds
|
|
175
|
+
cleanupBundleIds: &cleanupBundleIds,
|
|
176
|
+
excluding: normalizedCurrent.id
|
|
165
177
|
)
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
var
|
|
180
|
+
var trial: Trial?
|
|
169
181
|
if !normalizedCurrent.isBuiltin,
|
|
170
182
|
normalizedCurrent.status == .pending {
|
|
171
|
-
normalizedCurrent = updateStatusLocked(normalizedCurrent, status: .trial)
|
|
172
|
-
|
|
183
|
+
normalizedCurrent = try updateStatusLocked(normalizedCurrent, status: .trial)
|
|
184
|
+
trial = beginTrialLocked(bundleId: normalizedCurrent.id)
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
return StartupPreparation(
|
|
176
188
|
activationPath: normalizedCurrent.isBuiltin ? nil : normalizedCurrent.path,
|
|
177
|
-
|
|
189
|
+
trial: trial,
|
|
178
190
|
cleanupBundleIds: Array(cleanupBundleIds),
|
|
179
191
|
eventPayload: eventPayload
|
|
180
192
|
)
|
|
@@ -225,10 +237,15 @@ final class UpdaterCoordinator {
|
|
|
225
237
|
|
|
226
238
|
func stageDownloadedBundle(_ bundle: BundleInfo) throws -> [String] {
|
|
227
239
|
try withStateLock {
|
|
240
|
+
guard !bundle.isBuiltin, !(try store.protectedBundleIds()).contains(bundle.id) else {
|
|
241
|
+
throw NSError(domain: "OtaKit", code: 1, userInfo: [
|
|
242
|
+
NSLocalizedDescriptionKey: "Cannot overwrite a referenced bundle installation"
|
|
243
|
+
])
|
|
244
|
+
}
|
|
228
245
|
var cleanupBundleIds = Set<String>()
|
|
229
246
|
let previousStagedId = store.getStagedBundleId()
|
|
230
247
|
try store.saveBundle(bundle)
|
|
231
|
-
store.setStagedBundleId(bundle.id)
|
|
248
|
+
try store.setStagedBundleId(bundle.id)
|
|
232
249
|
|
|
233
250
|
if let previousStagedId,
|
|
234
251
|
previousStagedId != bundle.id,
|
|
@@ -245,10 +262,15 @@ final class UpdaterCoordinator {
|
|
|
245
262
|
func prepareApplyStaged(
|
|
246
263
|
isCompatibleRuntime: @escaping (BundleInfo) -> Bool,
|
|
247
264
|
isBundleUsable: @escaping (BundleInfo) -> Bool
|
|
248
|
-
) -> ApplyPreparation {
|
|
249
|
-
withStateLock {
|
|
265
|
+
) throws -> ApplyPreparation {
|
|
266
|
+
try withStateLock {
|
|
250
267
|
var cleanupBundleIds = Set<String>()
|
|
251
|
-
|
|
268
|
+
let previousCurrent = store.getCurrentBundle()
|
|
269
|
+
// Keep the staged update until the running trial has a definite outcome.
|
|
270
|
+
guard previousCurrent.status != .trial else {
|
|
271
|
+
return ApplyPreparation(activationPath: nil, trial: nil, cleanupBundleIds: [])
|
|
272
|
+
}
|
|
273
|
+
guard var staged = try stagedBundleLocked(
|
|
252
274
|
cleanInvalid: true,
|
|
253
275
|
isCompatibleRuntime: isCompatibleRuntime,
|
|
254
276
|
isUsable: isBundleUsable,
|
|
@@ -256,48 +278,48 @@ final class UpdaterCoordinator {
|
|
|
256
278
|
) else {
|
|
257
279
|
return ApplyPreparation(
|
|
258
280
|
activationPath: nil,
|
|
259
|
-
|
|
281
|
+
trial: nil,
|
|
260
282
|
cleanupBundleIds: Array(cleanupBundleIds)
|
|
261
283
|
)
|
|
262
284
|
}
|
|
263
285
|
|
|
264
|
-
let
|
|
265
|
-
|
|
266
|
-
store.setFallbackBundleId(nil)
|
|
267
|
-
} else {
|
|
268
|
-
store.setFallbackBundleId(previousCurrent.id)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
store.setCurrentBundleId(staged.id)
|
|
272
|
-
store.setStagedBundleId(nil)
|
|
273
|
-
|
|
274
|
-
var trialBundleId: String?
|
|
286
|
+
let fallbackId = previousCurrent.isBuiltin ? nil :
|
|
287
|
+
(previousCurrent.status == .success ? previousCurrent.id : store.getFallbackBundleId())
|
|
275
288
|
if staged.status == .pending {
|
|
276
|
-
staged = updateStatusLocked(staged, status: .trial)
|
|
277
|
-
trialBundleId = staged.id
|
|
278
|
-
} else if staged.status == .trial {
|
|
279
|
-
trialBundleId = staged.id
|
|
289
|
+
staged = try updateStatusLocked(staged, status: .trial)
|
|
280
290
|
}
|
|
291
|
+
// Persist trial metadata before publishing the pointer; keep all pointers in one write.
|
|
292
|
+
try store.setCoreState(
|
|
293
|
+
currentId: staged.id, fallbackId: fallbackId, stagedId: nil,
|
|
294
|
+
lastFailed: store.getLastFailedBundle()
|
|
295
|
+
)
|
|
296
|
+
let trial = staged.status == .trial ? beginTrialLocked(bundleId: staged.id) : nil
|
|
281
297
|
|
|
282
298
|
return ApplyPreparation(
|
|
283
299
|
activationPath: staged.path,
|
|
284
|
-
|
|
300
|
+
trial: trial,
|
|
285
301
|
cleanupBundleIds: Array(cleanupBundleIds)
|
|
286
302
|
)
|
|
287
303
|
}
|
|
288
304
|
}
|
|
289
305
|
|
|
290
|
-
func prepareNotifyAppReady() -> NotifyReadyPreparation {
|
|
291
|
-
withStateLock {
|
|
306
|
+
func prepareNotifyAppReady(activationId: String?) throws -> NotifyReadyPreparation {
|
|
307
|
+
try withStateLock {
|
|
292
308
|
let current = store.getCurrentBundle()
|
|
293
309
|
guard !current.isBuiltin,
|
|
294
|
-
|
|
310
|
+
activeTrial?.bundleId == current.id,
|
|
311
|
+
let activationId, activeTrial?.activationId == activationId,
|
|
312
|
+
current.status == .trial ||
|
|
313
|
+
(current.status == .success && store.getFallbackBundleId() != current.id) else {
|
|
295
314
|
return NotifyReadyPreparation(eventPayload: nil, cleanupBundleIds: [])
|
|
296
315
|
}
|
|
297
316
|
|
|
298
317
|
let oldFallbackId = store.getFallbackBundleId()
|
|
299
|
-
|
|
300
|
-
|
|
318
|
+
if current.status == .trial {
|
|
319
|
+
_ = try updateStatusLocked(current, status: .success)
|
|
320
|
+
}
|
|
321
|
+
try store.setFallbackBundleId(current.id)
|
|
322
|
+
activeTrial = nil
|
|
301
323
|
|
|
302
324
|
var cleanupBundleIds = Set<String>()
|
|
303
325
|
if let oldFallbackId,
|
|
@@ -312,7 +334,9 @@ final class UpdaterCoordinator {
|
|
|
312
334
|
runtimeVersion: current.runtimeVersion,
|
|
313
335
|
channel: current.channel,
|
|
314
336
|
releaseId: current.releaseId,
|
|
315
|
-
detail: nil
|
|
337
|
+
detail: nil,
|
|
338
|
+
attemptId: current.attemptId,
|
|
339
|
+
phase: "readiness"
|
|
316
340
|
),
|
|
317
341
|
cleanupBundleIds: Array(cleanupBundleIds)
|
|
318
342
|
)
|
|
@@ -320,12 +344,21 @@ final class UpdaterCoordinator {
|
|
|
320
344
|
}
|
|
321
345
|
|
|
322
346
|
func prepareRollback(
|
|
347
|
+
expectedTrial: Trial,
|
|
323
348
|
reason: String,
|
|
324
349
|
isBundleUsable: @escaping (BundleInfo) -> Bool
|
|
325
|
-
) -> RollbackPreparation {
|
|
326
|
-
withStateLock {
|
|
350
|
+
) throws -> RollbackPreparation {
|
|
351
|
+
try withStateLock {
|
|
352
|
+
let current = store.getCurrentBundle()
|
|
353
|
+
guard activeTrial == expectedTrial,
|
|
354
|
+
current.id == expectedTrial.bundleId,
|
|
355
|
+
current.status == .trial else {
|
|
356
|
+
return RollbackPreparation(
|
|
357
|
+
didRollback: false, activationPath: nil, eventPayload: nil, cleanupBundleIds: []
|
|
358
|
+
)
|
|
359
|
+
}
|
|
327
360
|
var cleanupBundleIds = Set<String>()
|
|
328
|
-
let rollback = rollbackLocked(
|
|
361
|
+
let rollback = try rollbackLocked(
|
|
329
362
|
reason: reason,
|
|
330
363
|
isBundleUsable: isBundleUsable,
|
|
331
364
|
cleanupBundleIds: &cleanupBundleIds
|
|
@@ -339,17 +372,22 @@ final class UpdaterCoordinator {
|
|
|
339
372
|
}
|
|
340
373
|
}
|
|
341
374
|
|
|
342
|
-
func
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
375
|
+
private func beginTrialLocked(bundleId: String) -> Trial {
|
|
376
|
+
let trial = Trial(bundleId: bundleId)
|
|
377
|
+
activeTrial = trial
|
|
378
|
+
return trial
|
|
347
379
|
}
|
|
348
380
|
|
|
349
381
|
func cleanupBundles(_ bundleIds: [String]) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
try?
|
|
382
|
+
withStateLock {
|
|
383
|
+
// An unreadable state is not evidence that these files are unreferenced.
|
|
384
|
+
guard let protectedIds = try? store.protectedBundleIds() else { return }
|
|
385
|
+
let uniqueIds = Set(bundleIds).filter { !$0.isEmpty && $0 != "builtin" }
|
|
386
|
+
for bundleId in uniqueIds {
|
|
387
|
+
// A later transition may have made an earlier cleanup candidate live again.
|
|
388
|
+
guard !protectedIds.contains(bundleId) else { continue }
|
|
389
|
+
try? FileManager.default.removeItem(at: store.bundleDirectory(for: bundleId))
|
|
390
|
+
}
|
|
353
391
|
}
|
|
354
392
|
}
|
|
355
393
|
|
|
@@ -365,27 +403,27 @@ final class UpdaterCoordinator {
|
|
|
365
403
|
return try work()
|
|
366
404
|
}
|
|
367
405
|
|
|
368
|
-
private func clearStaleBundlePointersLocked() {
|
|
406
|
+
private func clearStaleBundlePointersLocked() throws {
|
|
369
407
|
if let currentId = store.getCurrentBundleId(),
|
|
370
408
|
store.getBundle(id: currentId) == nil {
|
|
371
|
-
store.setCurrentBundleId(nil)
|
|
409
|
+
try store.setCurrentBundleId(nil)
|
|
372
410
|
}
|
|
373
411
|
|
|
374
412
|
if let fallbackId = store.getFallbackBundleId(),
|
|
375
413
|
store.getBundle(id: fallbackId) == nil {
|
|
376
|
-
store.setFallbackBundleId(nil)
|
|
414
|
+
try store.setFallbackBundleId(nil)
|
|
377
415
|
}
|
|
378
416
|
|
|
379
417
|
if let stagedId = store.getStagedBundleId(),
|
|
380
418
|
store.getBundle(id: stagedId) == nil {
|
|
381
|
-
store.setStagedBundleId(nil)
|
|
419
|
+
try store.setStagedBundleId(nil)
|
|
382
420
|
}
|
|
383
421
|
}
|
|
384
422
|
|
|
385
423
|
private func normalizeFallbackPointerLocked(
|
|
386
424
|
isBundleUsable: (BundleInfo) -> Bool,
|
|
387
425
|
cleanupBundleIds: inout Set<String>
|
|
388
|
-
) {
|
|
426
|
+
) throws {
|
|
389
427
|
guard let fallbackId = store.getFallbackBundleId(),
|
|
390
428
|
let fallback = store.getBundle(id: fallbackId) else {
|
|
391
429
|
return
|
|
@@ -395,8 +433,8 @@ final class UpdaterCoordinator {
|
|
|
395
433
|
return
|
|
396
434
|
}
|
|
397
435
|
|
|
398
|
-
if !isBundleUsable(fallback) {
|
|
399
|
-
store.setFallbackBundleId(nil)
|
|
436
|
+
if fallback.status != .success || !isBundleUsable(fallback) {
|
|
437
|
+
try store.setFallbackBundleId(nil)
|
|
400
438
|
cleanupBundleIds.insert(fallback.id)
|
|
401
439
|
}
|
|
402
440
|
}
|
|
@@ -404,18 +442,18 @@ final class UpdaterCoordinator {
|
|
|
404
442
|
private func normalizeStagedPointerLocked(
|
|
405
443
|
isBundleUsable: (BundleInfo) -> Bool,
|
|
406
444
|
cleanupBundleIds: inout Set<String>
|
|
407
|
-
) {
|
|
445
|
+
) throws {
|
|
408
446
|
guard let stagedId = store.getStagedBundleId() else {
|
|
409
447
|
return
|
|
410
448
|
}
|
|
411
449
|
|
|
412
450
|
guard let staged = store.getBundle(id: stagedId) else {
|
|
413
|
-
store.setStagedBundleId(nil)
|
|
451
|
+
try store.setStagedBundleId(nil)
|
|
414
452
|
return
|
|
415
453
|
}
|
|
416
454
|
|
|
417
455
|
if !isBundleUsable(staged) {
|
|
418
|
-
store.setStagedBundleId(nil)
|
|
456
|
+
try store.setStagedBundleId(nil)
|
|
419
457
|
cleanupBundleIds.insert(staged.id)
|
|
420
458
|
}
|
|
421
459
|
}
|
|
@@ -425,7 +463,7 @@ final class UpdaterCoordinator {
|
|
|
425
463
|
isUsable: (BundleInfo) -> Bool
|
|
426
464
|
) -> BundleInfo? {
|
|
427
465
|
var ignored = Set<String>()
|
|
428
|
-
return stagedBundleLocked(
|
|
466
|
+
return try? stagedBundleLocked(
|
|
429
467
|
cleanInvalid: false,
|
|
430
468
|
isCompatibleRuntime: isCompatibleRuntime,
|
|
431
469
|
isUsable: isUsable,
|
|
@@ -438,14 +476,14 @@ final class UpdaterCoordinator {
|
|
|
438
476
|
isCompatibleRuntime: ((BundleInfo) -> Bool)?,
|
|
439
477
|
isUsable: (BundleInfo) -> Bool,
|
|
440
478
|
cleanupBundleIds: inout Set<String>
|
|
441
|
-
) -> BundleInfo? {
|
|
479
|
+
) throws -> BundleInfo? {
|
|
442
480
|
guard let stagedId = store.getStagedBundleId() else {
|
|
443
481
|
return nil
|
|
444
482
|
}
|
|
445
483
|
|
|
446
484
|
guard let staged = store.getBundle(id: stagedId) else {
|
|
447
485
|
if cleanInvalid {
|
|
448
|
-
store.setStagedBundleId(nil)
|
|
486
|
+
try store.setStagedBundleId(nil)
|
|
449
487
|
}
|
|
450
488
|
return nil
|
|
451
489
|
}
|
|
@@ -453,7 +491,7 @@ final class UpdaterCoordinator {
|
|
|
453
491
|
if let isCompatibleRuntime,
|
|
454
492
|
!isCompatibleRuntime(staged) {
|
|
455
493
|
if cleanInvalid {
|
|
456
|
-
store.setStagedBundleId(nil)
|
|
494
|
+
try store.setStagedBundleId(nil)
|
|
457
495
|
cleanupBundleIds.insert(staged.id)
|
|
458
496
|
}
|
|
459
497
|
return nil
|
|
@@ -461,7 +499,7 @@ final class UpdaterCoordinator {
|
|
|
461
499
|
|
|
462
500
|
if !isUsable(staged) {
|
|
463
501
|
if cleanInvalid {
|
|
464
|
-
store.setStagedBundleId(nil)
|
|
502
|
+
try store.setStagedBundleId(nil)
|
|
465
503
|
cleanupBundleIds.insert(staged.id)
|
|
466
504
|
}
|
|
467
505
|
return nil
|
|
@@ -470,15 +508,15 @@ final class UpdaterCoordinator {
|
|
|
470
508
|
return staged
|
|
471
509
|
}
|
|
472
510
|
|
|
473
|
-
private func detachBundleReferencesLocked(bundleId: String) {
|
|
511
|
+
private func detachBundleReferencesLocked(bundleId: String) throws {
|
|
474
512
|
if store.getCurrentBundleId() == bundleId {
|
|
475
|
-
store.setCurrentBundleId(nil)
|
|
513
|
+
try store.setCurrentBundleId(nil)
|
|
476
514
|
}
|
|
477
515
|
if store.getFallbackBundleId() == bundleId {
|
|
478
|
-
store.setFallbackBundleId(nil)
|
|
516
|
+
try store.setFallbackBundleId(nil)
|
|
479
517
|
}
|
|
480
518
|
if store.getStagedBundleId() == bundleId {
|
|
481
|
-
store.setStagedBundleId(nil)
|
|
519
|
+
try store.setStagedBundleId(nil)
|
|
482
520
|
}
|
|
483
521
|
}
|
|
484
522
|
|
|
@@ -486,7 +524,7 @@ final class UpdaterCoordinator {
|
|
|
486
524
|
reason: String,
|
|
487
525
|
isBundleUsable: (BundleInfo) -> Bool,
|
|
488
526
|
cleanupBundleIds: inout Set<String>
|
|
489
|
-
) -> LockedRollbackResult {
|
|
527
|
+
) throws -> LockedRollbackResult {
|
|
490
528
|
let current = store.getCurrentBundle()
|
|
491
529
|
guard !current.isBuiltin else {
|
|
492
530
|
return LockedRollbackResult(
|
|
@@ -496,14 +534,14 @@ final class UpdaterCoordinator {
|
|
|
496
534
|
)
|
|
497
535
|
}
|
|
498
536
|
|
|
499
|
-
let failed =
|
|
500
|
-
|
|
501
|
-
store.setStagedBundleId(nil)
|
|
502
|
-
|
|
503
|
-
let fallback = restoreFallbackOrBuiltinLocked(
|
|
537
|
+
let failed = current.withStatus(.error)
|
|
538
|
+
let fallback = try restoreFallbackOrBuiltinLocked(
|
|
504
539
|
isBundleUsable: isBundleUsable,
|
|
505
|
-
cleanupBundleIds: &cleanupBundleIds
|
|
540
|
+
cleanupBundleIds: &cleanupBundleIds,
|
|
541
|
+
excluding: current.id,
|
|
542
|
+
failedBundle: failed
|
|
506
543
|
)
|
|
544
|
+
activeTrial = nil
|
|
507
545
|
cleanupBundleIds.insert(current.id)
|
|
508
546
|
|
|
509
547
|
return LockedRollbackResult(
|
|
@@ -515,50 +553,48 @@ final class UpdaterCoordinator {
|
|
|
515
553
|
runtimeVersion: current.runtimeVersion,
|
|
516
554
|
channel: current.channel,
|
|
517
555
|
releaseId: current.releaseId,
|
|
518
|
-
detail: reason
|
|
556
|
+
detail: reason,
|
|
557
|
+
attemptId: current.attemptId,
|
|
558
|
+
phase: "rollback"
|
|
519
559
|
)
|
|
520
560
|
)
|
|
521
561
|
}
|
|
522
562
|
|
|
523
563
|
private func restoreFallbackOrBuiltinLocked(
|
|
524
564
|
isBundleUsable: (BundleInfo) -> Bool,
|
|
525
|
-
cleanupBundleIds: inout Set<String
|
|
526
|
-
|
|
565
|
+
cleanupBundleIds: inout Set<String>,
|
|
566
|
+
excluding excludedBundleId: String,
|
|
567
|
+
failedBundle: BundleInfo? = nil
|
|
568
|
+
) throws -> BundleInfo {
|
|
569
|
+
var restored = store.builtinBundle()
|
|
527
570
|
if let fallbackId = store.getFallbackBundleId(),
|
|
528
571
|
let fallback = store.getBundle(id: fallbackId),
|
|
529
572
|
!fallback.isBuiltin,
|
|
573
|
+
fallback.id != excludedBundleId,
|
|
574
|
+
fallback.status == .success,
|
|
530
575
|
isBundleUsable(fallback) {
|
|
531
|
-
|
|
532
|
-
return fallback
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if let fallbackId = store.getFallbackBundleId() {
|
|
536
|
-
store.setFallbackBundleId(nil)
|
|
537
|
-
if fallbackId != "builtin" {
|
|
538
|
-
cleanupBundleIds.insert(fallbackId)
|
|
539
|
-
}
|
|
576
|
+
restored = fallback
|
|
540
577
|
}
|
|
541
578
|
|
|
542
|
-
store.
|
|
543
|
-
|
|
579
|
+
if let fallbackId = store.getFallbackBundleId(),
|
|
580
|
+
fallbackId != "builtin", fallbackId != restored.id {
|
|
581
|
+
cleanupBundleIds.insert(fallbackId)
|
|
582
|
+
}
|
|
583
|
+
let restoredId = restored.isBuiltin ? nil : restored.id
|
|
584
|
+
try store.setCoreState(
|
|
585
|
+
currentId: restoredId, fallbackId: restoredId,
|
|
586
|
+
stagedId: failedBundle == nil ? store.getStagedBundleId() : nil,
|
|
587
|
+
lastFailed: failedBundle ?? store.getLastFailedBundle()
|
|
588
|
+
)
|
|
589
|
+
return restored
|
|
544
590
|
}
|
|
545
591
|
|
|
546
592
|
private func updateStatusLocked(
|
|
547
593
|
_ bundle: BundleInfo,
|
|
548
594
|
status: BundleStatus
|
|
549
|
-
) -> BundleInfo {
|
|
550
|
-
let updated =
|
|
551
|
-
|
|
552
|
-
version: bundle.version,
|
|
553
|
-
runtimeVersion: bundle.runtimeVersion,
|
|
554
|
-
status: status,
|
|
555
|
-
downloadedAt: bundle.downloadedAt,
|
|
556
|
-
sha256: bundle.sha256,
|
|
557
|
-
path: bundle.path,
|
|
558
|
-
channel: bundle.channel,
|
|
559
|
-
releaseId: bundle.releaseId
|
|
560
|
-
)
|
|
561
|
-
try? store.saveBundle(updated)
|
|
595
|
+
) throws -> BundleInfo {
|
|
596
|
+
let updated = bundle.withStatus(status)
|
|
597
|
+
try store.saveBundle(updated)
|
|
562
598
|
return updated
|
|
563
599
|
}
|
|
564
600
|
|