@capgo/capacitor-updater 8.51.14 → 8.51.16
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/CapgoCapacitorUpdater.podspec +0 -2
- package/Package.swift +4 -5
- package/README.md +1 -1
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +55 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +59 -29
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +23 -7
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +11 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +57 -31
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +103 -3
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +4 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +4 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +114 -30
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoRawRsa.swift +36 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +56 -20
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +73 -251
- package/package.json +2 -2
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +0 -39
|
@@ -16,7 +16,5 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
s.dependency 'ZIPFoundation', '~> 0.9'
|
|
17
17
|
s.dependency 'Alamofire', '5.10.2'
|
|
18
18
|
s.dependency 'Version', '0.8.0'
|
|
19
|
-
# We cannot update to latest because it not published anymore in cocoa https://github.com/attaswift/BigInt/issues/94
|
|
20
|
-
s.dependency 'BigInt', '5.2.0'
|
|
21
19
|
s.swift_version = '5.1'
|
|
22
20
|
end
|
package/Package.swift
CHANGED
|
@@ -13,8 +13,7 @@ let package = Package(
|
|
|
13
13
|
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
|
|
14
14
|
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.12.0")),
|
|
15
15
|
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.20"),
|
|
16
|
-
.package(url: "https://github.com/mrackwitz/Version.git", exact: "0.8.0")
|
|
17
|
-
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.7.0")
|
|
16
|
+
.package(url: "https://github.com/mrackwitz/Version.git", exact: "0.8.0")
|
|
18
17
|
],
|
|
19
18
|
targets: [
|
|
20
19
|
.target(
|
|
@@ -24,15 +23,15 @@ let package = Package(
|
|
|
24
23
|
.product(name: "Cordova", package: "capacitor-swift-pm"),
|
|
25
24
|
.product(name: "ZIPFoundation", package: "ZIPFoundation"),
|
|
26
25
|
.product(name: "Alamofire", package: "Alamofire"),
|
|
27
|
-
.product(name: "Version", package: "Version")
|
|
28
|
-
.product(name: "BigInt", package: "BigInt")
|
|
26
|
+
.product(name: "Version", package: "Version")
|
|
29
27
|
],
|
|
30
28
|
path: "ios/Sources/CapacitorUpdaterPlugin"),
|
|
31
29
|
.testTarget(
|
|
32
30
|
name: "CapacitorUpdaterPluginTests",
|
|
33
31
|
dependencies: [
|
|
34
32
|
"CapacitorUpdaterPlugin",
|
|
35
|
-
.product(name: "Version", package: "Version")
|
|
33
|
+
.product(name: "Version", package: "Version"),
|
|
34
|
+
.product(name: "ZIPFoundation", package: "ZIPFoundation")
|
|
36
35
|
],
|
|
37
36
|
path: "ios/Tests/CapacitorUpdaterPluginTests")
|
|
38
37
|
],
|
package/README.md
CHANGED
|
@@ -303,7 +303,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
303
303
|
| Prop | Type | Description | Default | Since |
|
|
304
304
|
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------- |
|
|
305
305
|
| **`appReadyTimeout`** | <code>number</code> | Configure the number of milliseconds the native plugin should wait before considering an update 'failed'. Only available for Android and iOS. | <code>10000 // (10 seconds)</code> | |
|
|
306
|
-
| **`responseTimeout`** | <code>number</code> | Configure the number of seconds the native plugin should wait before considering
|
|
306
|
+
| **`responseTimeout`** | <code>number</code> | Configure the number of seconds the native plugin should wait before considering an HTTP timeout. Applies to update checks and file downloads. On Android these are idle connect/read/write timeouts and do not cap total download time; on iOS the request timeout also bounds the total download duration. Only available for Android and iOS. | <code>20 // (20 second)</code> | |
|
|
307
307
|
| **`autoDeleteFailed`** | <code>boolean</code> | Configure whether the plugin should use automatically delete failed bundles. Only available for Android and iOS. | <code>true</code> | |
|
|
308
308
|
| **`autoDeletePrevious`** | <code>boolean</code> | Configure whether the plugin should use automatically delete previous bundles after a successful update. Only available for Android and iOS. | <code>true</code> | |
|
|
309
309
|
| **`autoUpdate`** | <code>boolean \| 'always' \| 'off' \| 'atBackground' \| 'atInstall' \| 'onLaunch' \| 'onlyDownload'</code> | Configure how the plugin checks for, downloads, and applies live updates. The plugin checks for updates when the app moves to the foreground. When {@link periodCheckDelay} is greater than 0, it also checks on a repeating timer while the app stays open. Boolean values keep their existing behavior: - `true`: Same as `"atBackground"`. - `false`: Same as `"off"`. String values merge the previous Auto Update and Direct Update configuration: - `"off"`: Disable automatic update checks. - `"atBackground"`: Check and download automatically on each foreground check, then apply the update the next time the app moves to background. - `"atInstall"`: Apply immediately only after a fresh install or native app store update; otherwise use `"atBackground"` behavior. - `"onLaunch"`: Apply immediately only when the app is brought to the foreground from a killed state (cold start). After that first check, fall back to `"atBackground"` behavior. - `"always"`: Check on every foreground transition and apply immediately whenever an update is available. - `"onlyDownload"`: Check and download automatically, emit `updateAvailable`, and never set the next bundle or apply an update automatically. Only available for Android and iOS. | <code>true</code> | |
|
package/android/build.gradle
CHANGED
|
@@ -3,6 +3,7 @@ ext {
|
|
|
3
3
|
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
4
|
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
5
|
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
okhttpVersion = project.hasProperty('okhttpVersion') ? rootProject.ext.okhttpVersion : '4.12.0'
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
buildscript {
|
|
@@ -54,7 +55,6 @@ dependencies {
|
|
|
54
55
|
implementation "androidx.work:work-runtime:$work_version"
|
|
55
56
|
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
|
56
57
|
implementation "com.google.android.gms:play-services-tasks:18.4.1"
|
|
57
|
-
implementation "com.google.guava:guava:33.6.0-android"
|
|
58
58
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
59
59
|
implementation project(':capacitor-android')
|
|
60
60
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
@@ -65,11 +65,11 @@ dependencies {
|
|
|
65
65
|
testImplementation "junit:junit:$junitVersion"
|
|
66
66
|
testImplementation 'org.mockito:mockito-core:5.23.0'
|
|
67
67
|
testImplementation 'org.json:json:20250517'
|
|
68
|
-
testImplementation 'org.robolectric:robolectric:4.
|
|
68
|
+
testImplementation 'org.robolectric:robolectric:4.17'
|
|
69
69
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
70
70
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
71
71
|
implementation 'org.brotli:dec:0.1.2'
|
|
72
|
-
implementation
|
|
72
|
+
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
tasks.withType(Test) {
|
|
@@ -146,7 +146,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
146
146
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
147
147
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
148
148
|
|
|
149
|
-
private final String pluginVersion = "8.51.
|
|
149
|
+
private final String pluginVersion = "8.51.16";
|
|
150
150
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
151
151
|
|
|
152
152
|
private SharedPreferences.Editor editor;
|
|
@@ -881,7 +881,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
881
881
|
this.autoSplashscreenLoader = this.getConfig().getBoolean("autoSplashscreenLoader", false);
|
|
882
882
|
int splashscreenTimeoutValue = this.getConfig().getInt("autoSplashscreenTimeout", 10000);
|
|
883
883
|
this.autoSplashscreenTimeout = Math.max(0, splashscreenTimeoutValue);
|
|
884
|
-
|
|
884
|
+
int responseTimeoutSeconds = this.getConfig().getInt("responseTimeout", 20);
|
|
885
|
+
long responseTimeoutMillis = responseTimeoutSeconds > 0 ? (long) responseTimeoutSeconds * 1000L : 20_000L;
|
|
886
|
+
this.implementation.timeout = (int) Math.min(Integer.MAX_VALUE, responseTimeoutMillis);
|
|
887
|
+
DownloadService.applyHttpTimeouts(this.implementation.timeout);
|
|
885
888
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
886
889
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
887
890
|
this.shakeMenuGesture = normalizedShakeMenuGesture(this.getConfig().getString("shakeMenuGesture", SHAKE_MENU_GESTURE_SHAKE));
|
|
@@ -2529,11 +2532,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2529
2532
|
call.reject("setUpdateUrl called without url");
|
|
2530
2533
|
return;
|
|
2531
2534
|
}
|
|
2532
|
-
this.updateUrl = url;
|
|
2533
2535
|
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
2534
2536
|
this.editor.putString(UPDATE_URL_PREF_KEY, url);
|
|
2535
|
-
this.editor.
|
|
2537
|
+
if (!this.editor.commit()) {
|
|
2538
|
+
logger.error("Failed to persist updateUrl");
|
|
2539
|
+
call.reject("Failed to persist updateUrl");
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2536
2542
|
}
|
|
2543
|
+
this.updateUrl = url;
|
|
2537
2544
|
call.resolve();
|
|
2538
2545
|
}
|
|
2539
2546
|
|
|
@@ -2550,11 +2557,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2550
2557
|
call.reject("setStatsUrl called without url");
|
|
2551
2558
|
return;
|
|
2552
2559
|
}
|
|
2553
|
-
this.implementation.statsUrl = url;
|
|
2554
2560
|
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
2555
2561
|
this.editor.putString(STATS_URL_PREF_KEY, url);
|
|
2556
|
-
this.editor.
|
|
2562
|
+
if (!this.editor.commit()) {
|
|
2563
|
+
logger.error("Failed to persist statsUrl");
|
|
2564
|
+
call.reject("Failed to persist statsUrl");
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2557
2567
|
}
|
|
2568
|
+
this.implementation.statsUrl = url;
|
|
2558
2569
|
call.resolve();
|
|
2559
2570
|
}
|
|
2560
2571
|
|
|
@@ -2571,11 +2582,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2571
2582
|
call.reject("setChannelUrl called without url");
|
|
2572
2583
|
return;
|
|
2573
2584
|
}
|
|
2574
|
-
this.implementation.channelUrl = url;
|
|
2575
2585
|
if (Boolean.TRUE.equals(this.persistModifyUrl)) {
|
|
2576
2586
|
this.editor.putString(CHANNEL_URL_PREF_KEY, url);
|
|
2577
|
-
this.editor.
|
|
2587
|
+
if (!this.editor.commit()) {
|
|
2588
|
+
logger.error("Failed to persist channelUrl");
|
|
2589
|
+
call.reject("Failed to persist channelUrl");
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2578
2592
|
}
|
|
2593
|
+
this.implementation.channelUrl = url;
|
|
2579
2594
|
call.resolve();
|
|
2580
2595
|
}
|
|
2581
2596
|
|
|
@@ -4230,12 +4245,18 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4230
4245
|
if (this.shouldBlockAutoUpdateForPreviewSession()) {
|
|
4231
4246
|
return "preview_session";
|
|
4232
4247
|
}
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4248
|
+
synchronized (this) {
|
|
4249
|
+
final Thread previousTask = this.backgroundDownloadTask;
|
|
4250
|
+
final Thread task = this.backgroundDownload();
|
|
4251
|
+
if (task == null) {
|
|
4252
|
+
return "unavailable";
|
|
4253
|
+
}
|
|
4254
|
+
if (previousTask != null && previousTask == task) {
|
|
4255
|
+
logger.info("Download already in progress, skipping duplicate download request");
|
|
4256
|
+
return "already_running";
|
|
4257
|
+
}
|
|
4258
|
+
return "queued";
|
|
4236
4259
|
}
|
|
4237
|
-
this.backgroundDownload();
|
|
4238
|
-
return "queued";
|
|
4239
4260
|
}
|
|
4240
4261
|
|
|
4241
4262
|
@PluginMethod
|
|
@@ -4748,10 +4769,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4748
4769
|
return true;
|
|
4749
4770
|
}
|
|
4750
4771
|
|
|
4751
|
-
private Thread backgroundDownload() {
|
|
4772
|
+
private synchronized Thread backgroundDownload() {
|
|
4752
4773
|
if (this.shouldBlockAutoUpdateForPreviewSession()) {
|
|
4753
4774
|
return null;
|
|
4754
4775
|
}
|
|
4776
|
+
if (this.isDownloadStuckOrTimedOut()) {
|
|
4777
|
+
logger.info("Download already in progress, skipping duplicate download request");
|
|
4778
|
+
return this.backgroundDownloadTask;
|
|
4779
|
+
}
|
|
4755
4780
|
final boolean plannedDirectUpdate = this.shouldUseDirectUpdate();
|
|
4756
4781
|
final boolean initialDirectUpdateAllowed = this.isDirectUpdateCurrentlyAllowed(plannedDirectUpdate);
|
|
4757
4782
|
final String messageUpdate = initialDirectUpdateAllowed
|
|
@@ -4760,8 +4785,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4760
4785
|
? "Update will occur next time app moves to background."
|
|
4761
4786
|
: "Update will be downloaded and made available.";
|
|
4762
4787
|
Thread newTask = startNewThread(() -> {
|
|
4763
|
-
// Wait for cleanup to complete before starting download
|
|
4764
|
-
waitForCleanupIfNeeded();
|
|
4765
4788
|
if (CapacitorUpdaterPlugin.this.shouldBlockAutoUpdateForPreviewSession()) {
|
|
4766
4789
|
CapacitorUpdaterPlugin.this.clearBackgroundDownloadState();
|
|
4767
4790
|
return;
|
|
@@ -4774,7 +4797,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4774
4797
|
return;
|
|
4775
4798
|
}
|
|
4776
4799
|
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
4777
|
-
final BundleInfo
|
|
4800
|
+
final BundleInfo currentBeforeCleanup = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
4778
4801
|
|
|
4779
4802
|
// Handle network errors and other failures first
|
|
4780
4803
|
if (jsRes.has("error") || jsRes.has("kind")) {
|
|
@@ -4782,8 +4805,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4782
4805
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
|
|
4783
4806
|
int statusCode = jsRes.has("statusCode") ? jsRes.optInt("statusCode", 0) : 0;
|
|
4784
4807
|
String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
|
|
4785
|
-
String latestVersion = jsRes.has("version") ? jsRes.getString("version") :
|
|
4786
|
-
CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(
|
|
4808
|
+
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : currentBeforeCleanup.getVersionName();
|
|
4809
|
+
CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(
|
|
4810
|
+
kind,
|
|
4811
|
+
error,
|
|
4812
|
+
errorMessage,
|
|
4813
|
+
statusCode,
|
|
4814
|
+
latestVersion,
|
|
4815
|
+
currentBeforeCleanup
|
|
4816
|
+
);
|
|
4787
4817
|
CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(
|
|
4788
4818
|
jsRes,
|
|
4789
4819
|
jsRes.has("version") ? jsRes.getString("version") : ""
|
|
@@ -4803,7 +4833,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4803
4833
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
4804
4834
|
errorMessage,
|
|
4805
4835
|
latestVersion,
|
|
4806
|
-
|
|
4836
|
+
currentBeforeCleanup,
|
|
4807
4837
|
isFailure,
|
|
4808
4838
|
plannedDirectUpdate,
|
|
4809
4839
|
"download_fail",
|
|
@@ -4813,6 +4843,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4813
4843
|
return;
|
|
4814
4844
|
}
|
|
4815
4845
|
try {
|
|
4846
|
+
// File mutations wait here. getLatest already ran in parallel with cleanup.
|
|
4847
|
+
waitForCleanupIfNeeded();
|
|
4848
|
+
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
4816
4849
|
final String latestVersionName = jsRes.getString("version");
|
|
4817
4850
|
|
|
4818
4851
|
if ("builtin".equals(latestVersionName)) {
|
|
@@ -5061,8 +5094,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
5061
5094
|
logger.error("error in update check " + e.getMessage());
|
|
5062
5095
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
5063
5096
|
"Error in update check",
|
|
5064
|
-
|
|
5065
|
-
|
|
5097
|
+
currentBeforeCleanup.getVersionName(),
|
|
5098
|
+
currentBeforeCleanup,
|
|
5066
5099
|
true,
|
|
5067
5100
|
plannedDirectUpdate
|
|
5068
5101
|
);
|
|
@@ -23,8 +23,6 @@ import androidx.work.OneTimeWorkRequest;
|
|
|
23
23
|
import androidx.work.PeriodicWorkRequest;
|
|
24
24
|
import androidx.work.WorkInfo;
|
|
25
25
|
import androidx.work.WorkManager;
|
|
26
|
-
import com.google.common.util.concurrent.Futures;
|
|
27
|
-
import com.google.common.util.concurrent.ListenableFuture;
|
|
28
26
|
import java.io.BufferedInputStream;
|
|
29
27
|
import java.io.BufferedReader;
|
|
30
28
|
import java.io.File;
|
|
@@ -315,14 +313,17 @@ public class CapgoUpdater {
|
|
|
315
313
|
return this.cachedKeyId;
|
|
316
314
|
}
|
|
317
315
|
|
|
318
|
-
|
|
316
|
+
File unzip(final String id, final File zipFile, final String dest) throws IOException {
|
|
317
|
+
return unzip(id, zipFile, dest, CryptoCipher.ioBufferBytes());
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
File unzip(final String id, final File zipFile, final String dest, final int bufferSize) throws IOException {
|
|
319
321
|
final File targetDirectory = new File(this.documentsDir, dest);
|
|
320
322
|
try (
|
|
321
323
|
final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFile));
|
|
322
324
|
final ZipInputStream zis = new ZipInputStream(bis)
|
|
323
325
|
) {
|
|
324
326
|
int count;
|
|
325
|
-
final int bufferSize = 8192;
|
|
326
327
|
final byte[] buffer = new byte[bufferSize];
|
|
327
328
|
final long lengthTotal = zipFile.length();
|
|
328
329
|
long lengthRead = bufferSize;
|
|
@@ -760,6 +761,13 @@ public class CapgoUpdater {
|
|
|
760
761
|
}
|
|
761
762
|
});
|
|
762
763
|
break;
|
|
764
|
+
case CANCELLED:
|
|
765
|
+
DataManager.getInstance().clearManifest(id);
|
|
766
|
+
CompletableFuture<BundleInfo> cancelledFuture = downloadFutures.remove(id);
|
|
767
|
+
if (cancelledFuture != null) {
|
|
768
|
+
cancelledFuture.cancel(true);
|
|
769
|
+
}
|
|
770
|
+
break;
|
|
763
771
|
}
|
|
764
772
|
});
|
|
765
773
|
});
|
|
@@ -781,6 +789,10 @@ public class CapgoUpdater {
|
|
|
781
789
|
}
|
|
782
790
|
observeWorkProgress(this.activity, id, setNext);
|
|
783
791
|
|
|
792
|
+
if (manifest != null) {
|
|
793
|
+
DataManager.getInstance().setManifest(id, manifest);
|
|
794
|
+
}
|
|
795
|
+
|
|
784
796
|
DownloadWorkerManager.enqueueDownload(
|
|
785
797
|
this.activity,
|
|
786
798
|
url,
|
|
@@ -805,10 +817,6 @@ public class CapgoUpdater {
|
|
|
805
817
|
this.customId,
|
|
806
818
|
this.defaultChannel
|
|
807
819
|
);
|
|
808
|
-
|
|
809
|
-
if (manifest != null) {
|
|
810
|
-
DataManager.getInstance().setManifest(manifest);
|
|
811
|
-
}
|
|
812
820
|
}
|
|
813
821
|
|
|
814
822
|
public Boolean finishDownload(
|
|
@@ -1398,7 +1406,10 @@ public class CapgoUpdater {
|
|
|
1398
1406
|
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
1399
1407
|
if (existingBundle != null && existingBundle.isErrorStatus()) {
|
|
1400
1408
|
// Cancel the failed download and allow retry
|
|
1401
|
-
DownloadWorkerManager.
|
|
1409
|
+
if (!DownloadWorkerManager.cancelVersionDownloadAndAwait(this.activity, version)) {
|
|
1410
|
+
logger.error("Failed to cancel previous download before retry");
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1402
1413
|
logger.info("Retrying failed download for version: " + version);
|
|
1403
1414
|
} else {
|
|
1404
1415
|
logger.info("Version already downloading: " + version);
|
|
@@ -1419,7 +1430,9 @@ public class CapgoUpdater {
|
|
|
1419
1430
|
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
1420
1431
|
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted() || existingBundle.isDeleting())) {
|
|
1421
1432
|
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
1422
|
-
this.delete(existingBundle.getId(), true)
|
|
1433
|
+
if (!Boolean.TRUE.equals(this.delete(existingBundle.getId(), true))) {
|
|
1434
|
+
throw new IOException("Failed to delete existing bundle before retry");
|
|
1435
|
+
}
|
|
1423
1436
|
}
|
|
1424
1437
|
|
|
1425
1438
|
final String id = this.randomString();
|
|
@@ -1472,7 +1485,9 @@ public class CapgoUpdater {
|
|
|
1472
1485
|
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
1473
1486
|
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted() || existingBundle.isDeleting())) {
|
|
1474
1487
|
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
1475
|
-
this.delete(existingBundle.getId(), true)
|
|
1488
|
+
if (!Boolean.TRUE.equals(this.delete(existingBundle.getId(), true))) {
|
|
1489
|
+
throw new IOException("Failed to delete existing bundle before retry");
|
|
1490
|
+
}
|
|
1476
1491
|
}
|
|
1477
1492
|
|
|
1478
1493
|
final String id = this.randomString();
|
|
@@ -1587,7 +1602,10 @@ public class CapgoUpdater {
|
|
|
1587
1602
|
|
|
1588
1603
|
// Cancel download for this version if active
|
|
1589
1604
|
if (cancelActiveDownload && this.activity != null) {
|
|
1590
|
-
DownloadWorkerManager.
|
|
1605
|
+
if (!DownloadWorkerManager.cancelVersionDownloadAndAwait(this.activity, deleted.getVersionName())) {
|
|
1606
|
+
logger.error("Failed to cancel active download before delete");
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1591
1609
|
}
|
|
1592
1610
|
|
|
1593
1611
|
if (bundle.exists()) {
|
|
@@ -1922,32 +1940,35 @@ public class CapgoUpdater {
|
|
|
1922
1940
|
!fallbackIsPreviewFallback &&
|
|
1923
1941
|
!previousIsNext;
|
|
1924
1942
|
if (shouldDeletePrevious) {
|
|
1925
|
-
// Cancel any in-flight download for the old version before the async boundary
|
|
1926
|
-
// so a later download of the same version name is not cancelled mid-flight.
|
|
1927
|
-
if (this.activity != null) {
|
|
1928
|
-
DownloadWorkerManager.cancelVersionDownload(this.activity, previousFallbackVersion);
|
|
1929
|
-
}
|
|
1930
|
-
// Mark durable intent before fallback switch so a kill mid-flight still retries.
|
|
1931
1943
|
if (!this.saveBundleInfo(previousFallbackId, fallback.setStatus(BundleStatus.DELETING))) {
|
|
1932
1944
|
logger.error("Failed to persist DELETING for previous bundle; queueing durable retry");
|
|
1933
1945
|
logger.debug("Bundle ID: " + previousFallbackId);
|
|
1934
1946
|
this.enqueuePendingDelete(previousFallbackId);
|
|
1935
1947
|
}
|
|
1936
1948
|
}
|
|
1949
|
+
boolean deletePreviousAsync = shouldDeletePrevious;
|
|
1937
1950
|
this.setFallbackBundle(bundle);
|
|
1938
|
-
if (
|
|
1939
|
-
|
|
1951
|
+
if (deletePreviousAsync) {
|
|
1952
|
+
final String asyncPreviousFallbackId = previousFallbackId;
|
|
1953
|
+
final String asyncPreviousFallbackVersion = previousFallbackVersion;
|
|
1954
|
+
io.execute(() -> {
|
|
1955
|
+
if (this.activity != null) {
|
|
1956
|
+
if (!DownloadWorkerManager.cancelVersionDownloadAndAwait(this.activity, asyncPreviousFallbackVersion)) {
|
|
1957
|
+
logger.error("Failed to cancel previous version download before delete");
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1940
1961
|
try {
|
|
1941
|
-
final Boolean res = this.delete(
|
|
1962
|
+
final Boolean res = this.delete(asyncPreviousFallbackId, true, false);
|
|
1942
1963
|
if (Boolean.TRUE.equals(res)) {
|
|
1943
|
-
logger.info("Deleted previous bundle: " +
|
|
1964
|
+
logger.info("Deleted previous bundle: " + asyncPreviousFallbackVersion);
|
|
1944
1965
|
} else {
|
|
1945
|
-
logger.debug("Previous bundle delete incomplete, will retry: " +
|
|
1966
|
+
logger.debug("Previous bundle delete incomplete, will retry: " + asyncPreviousFallbackId);
|
|
1946
1967
|
}
|
|
1947
1968
|
} catch (final IOException e) {
|
|
1948
|
-
logger.error("Failed to delete previous bundle: " +
|
|
1969
|
+
logger.error("Failed to delete previous bundle: " + asyncPreviousFallbackId + " " + e.getMessage());
|
|
1949
1970
|
}
|
|
1950
|
-
}
|
|
1971
|
+
});
|
|
1951
1972
|
}
|
|
1952
1973
|
}
|
|
1953
1974
|
|
|
@@ -2878,6 +2899,11 @@ public class CapgoUpdater {
|
|
|
2878
2899
|
statsQueue.add(new QueuedStatsEvent(arr.getJSONObject(i), null));
|
|
2879
2900
|
}
|
|
2880
2901
|
}
|
|
2902
|
+
if (backup.exists() && !backup.delete()) {
|
|
2903
|
+
if (logger != null) {
|
|
2904
|
+
logger.error("Failed to delete stats backup");
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2881
2907
|
if (!statsQueue.isEmpty()) {
|
|
2882
2908
|
if (logger != null) {
|
|
2883
2909
|
logger.info("Restored " + statsQueue.size() + " pending stats events");
|
|
@@ -2933,9 +2959,11 @@ public class CapgoUpdater {
|
|
|
2933
2959
|
}
|
|
2934
2960
|
try {
|
|
2935
2961
|
if (arr.length() == 0) {
|
|
2936
|
-
|
|
2962
|
+
writeFileAtomically(file, "[]".getBytes(StandardCharsets.UTF_8));
|
|
2963
|
+
File backup = new File(file.getAbsolutePath() + ".bak");
|
|
2964
|
+
if (backup.exists() && !backup.delete()) {
|
|
2937
2965
|
if (logger != null) {
|
|
2938
|
-
logger.error("Failed to delete empty stats
|
|
2966
|
+
logger.error("Failed to delete empty stats backup");
|
|
2939
2967
|
}
|
|
2940
2968
|
}
|
|
2941
2969
|
return;
|
|
@@ -2966,7 +2994,7 @@ public class CapgoUpdater {
|
|
|
2966
2994
|
}
|
|
2967
2995
|
}
|
|
2968
2996
|
|
|
2969
|
-
private
|
|
2997
|
+
private void writeFileAtomically(final File file, final byte[] bytes) throws IOException {
|
|
2970
2998
|
final File tmp = new File(file.getAbsolutePath() + ".tmp");
|
|
2971
2999
|
File backup = null;
|
|
2972
3000
|
try {
|
|
@@ -2988,7 +3016,9 @@ public class CapgoUpdater {
|
|
|
2988
3016
|
}
|
|
2989
3017
|
if (tmp.renameTo(file)) {
|
|
2990
3018
|
if (backup != null && backup.exists() && !backup.delete()) {
|
|
2991
|
-
|
|
3019
|
+
if (logger != null) {
|
|
3020
|
+
logger.error("Failed to delete stats backup");
|
|
3021
|
+
}
|
|
2992
3022
|
}
|
|
2993
3023
|
backup = null;
|
|
2994
3024
|
return;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
package ee.forgr.capacitor_updater;
|
|
2
2
|
|
|
3
|
+
import java.util.HashMap;
|
|
4
|
+
import java.util.Map;
|
|
3
5
|
import org.json.JSONArray;
|
|
4
6
|
|
|
5
7
|
public class DataManager {
|
|
6
8
|
|
|
7
9
|
private static DataManager instance;
|
|
8
|
-
private JSONArray
|
|
10
|
+
private final Map<String, JSONArray> manifestsById = new HashMap<>();
|
|
9
11
|
|
|
10
12
|
private DataManager() {}
|
|
11
13
|
|
|
@@ -16,13 +18,27 @@ public class DataManager {
|
|
|
16
18
|
return instance;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
public void setManifest(JSONArray manifest) {
|
|
20
|
-
|
|
21
|
+
public synchronized void setManifest(String downloadId, JSONArray manifest) {
|
|
22
|
+
if (downloadId == null || manifest == null) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.manifestsById.put(downloadId, manifest);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public synchronized JSONArray getAndClearManifest(String downloadId) {
|
|
29
|
+
if (downloadId == null) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return this.manifestsById.remove(downloadId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public synchronized void clearManifest(String downloadId) {
|
|
36
|
+
if (downloadId != null) {
|
|
37
|
+
this.manifestsById.remove(downloadId);
|
|
38
|
+
}
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
public
|
|
24
|
-
|
|
25
|
-
this.currentManifest = null;
|
|
26
|
-
return manifest;
|
|
41
|
+
public synchronized void clearAllManifests() {
|
|
42
|
+
this.manifestsById.clear();
|
|
27
43
|
}
|
|
28
44
|
}
|
|
@@ -75,6 +75,17 @@ public class DelayUpdateUtils {
|
|
|
75
75
|
", longValue: " +
|
|
76
76
|
longValue
|
|
77
77
|
);
|
|
78
|
+
} else {
|
|
79
|
+
delayConditionListToKeep.add(condition);
|
|
80
|
+
logger.info(
|
|
81
|
+
"Background delay (value: " +
|
|
82
|
+
value +
|
|
83
|
+
") condition kept at index " +
|
|
84
|
+
index +
|
|
85
|
+
" (source: " +
|
|
86
|
+
source.toString() +
|
|
87
|
+
")"
|
|
88
|
+
);
|
|
78
89
|
}
|
|
79
90
|
} else {
|
|
80
91
|
delayConditionListToKeep.add(condition);
|