@capgo/capacitor-updater 8.51.0 → 8.51.2
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 +50 -47
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +7 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +1 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +251 -85
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +289 -60
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +1 -1
- package/dist/docs.json +26 -2
- package/dist/esm/definitions.d.ts +23 -7
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +5 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleStatus.swift +3 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +609 -69
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +187 -34
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +8 -0
- package/package.json +1 -1
|
@@ -50,6 +50,7 @@ import com.google.android.play.core.install.model.InstallStatus;
|
|
|
50
50
|
import com.google.android.play.core.install.model.UpdateAvailability;
|
|
51
51
|
import io.github.g00fy2.versioncompare.Version;
|
|
52
52
|
import java.io.ByteArrayOutputStream;
|
|
53
|
+
import java.io.File;
|
|
53
54
|
import java.io.IOException;
|
|
54
55
|
import java.io.InputStream;
|
|
55
56
|
import java.net.HttpURLConnection;
|
|
@@ -68,6 +69,7 @@ import java.util.Set;
|
|
|
68
69
|
import java.util.TimeZone;
|
|
69
70
|
import java.util.Timer;
|
|
70
71
|
import java.util.TimerTask;
|
|
72
|
+
import java.util.concurrent.CountDownLatch;
|
|
71
73
|
import java.util.concurrent.Phaser;
|
|
72
74
|
import java.util.concurrent.Semaphore;
|
|
73
75
|
import java.util.concurrent.TimeUnit;
|
|
@@ -103,6 +105,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
103
105
|
private static final String CHANNEL_URL_PREF_KEY = "CapacitorUpdater.channelUrl";
|
|
104
106
|
private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
|
|
105
107
|
private static final String PREVIEW_SESSION_PREF_KEY = "CapacitorUpdater.previewSession";
|
|
108
|
+
private static final String DEFAULT_CHANNEL_INSTALL_MARKER_PREF_KEY = "CapacitorUpdater.defaultChannelInstallMarkerCreated";
|
|
109
|
+
private static final String DEFAULT_CHANNEL_INSTALL_MARKER_FILE = "CapacitorUpdater.defaultChannelInstallMarker";
|
|
106
110
|
private static final String PREVIEW_PREVIOUS_SHAKE_MENU_PREF_KEY = "CapacitorUpdater.previewPreviousShakeMenu";
|
|
107
111
|
private static final String PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY = "CapacitorUpdater.previewPreviousShakeChannelSelector";
|
|
108
112
|
private static final String PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY = "CapacitorUpdater.previewPreviousNextBundle";
|
|
@@ -142,7 +146,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
142
146
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
143
147
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
144
148
|
|
|
145
|
-
private final String pluginVersion = "8.51.
|
|
149
|
+
private final String pluginVersion = "8.51.2";
|
|
146
150
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
147
151
|
|
|
148
152
|
private SharedPreferences.Editor editor;
|
|
@@ -151,6 +155,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
151
155
|
protected CapgoUpdater implementation;
|
|
152
156
|
private Boolean persistCustomId = false;
|
|
153
157
|
private Boolean persistModifyUrl = false;
|
|
158
|
+
private Boolean persistDefaultChannelOnReinstall = true;
|
|
154
159
|
|
|
155
160
|
private Integer appReadyTimeout = 10000;
|
|
156
161
|
private Integer periodCheckDelay = 0;
|
|
@@ -215,6 +220,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
215
220
|
|
|
216
221
|
private volatile Thread backgroundDownloadTask;
|
|
217
222
|
private volatile Thread appReadyCheck;
|
|
223
|
+
// When true, sendReadyToJs should wait for notifyAppReady before hiding splash.
|
|
224
|
+
private volatile boolean pendingNotifyAppReadyWait = false;
|
|
225
|
+
private volatile int pendingNotifyAppReadyPhase = -1;
|
|
218
226
|
private volatile long downloadStartTimeMs = 0;
|
|
219
227
|
private static final long DOWNLOAD_TIMEOUT_MS = 600000; // 10 minute timeout
|
|
220
228
|
|
|
@@ -227,8 +235,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
227
235
|
|
|
228
236
|
// Lock to ensure cleanup completes before downloads start
|
|
229
237
|
private final Object cleanupLock = new Object();
|
|
238
|
+
private volatile CountDownLatch cleanupLatch = new CountDownLatch(0);
|
|
230
239
|
private volatile boolean cleanupComplete = false;
|
|
231
240
|
private volatile Thread cleanupThread = null;
|
|
241
|
+
private volatile boolean defaultChannelCleanupMustRetry = false;
|
|
232
242
|
|
|
233
243
|
private int lastNotifiedStatPercent = 0;
|
|
234
244
|
|
|
@@ -751,6 +761,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
751
761
|
|
|
752
762
|
this.persistCustomId = this.getConfig().getBoolean("persistCustomId", false);
|
|
753
763
|
this.persistModifyUrl = this.getConfig().getBoolean("persistModifyUrl", false);
|
|
764
|
+
this.persistDefaultChannelOnReinstall = this.getConfig().getBoolean("persistDefaultChannelOnReinstall", true);
|
|
754
765
|
this.allowSetDefaultChannel = this.getConfig().getBoolean("allowSetDefaultChannel", true);
|
|
755
766
|
this.implementation.setPublicKey(this.getConfig().getString("publicKey", ""));
|
|
756
767
|
// Log public key prefix if encryption is enabled
|
|
@@ -777,6 +788,35 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
777
788
|
}
|
|
778
789
|
}
|
|
779
790
|
|
|
791
|
+
final boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
|
|
792
|
+
final boolean nativeBuildVersionChanged = this.hasNativeBuildVersionChanged();
|
|
793
|
+
final boolean defaultChannelPersistenceDisabled = !Boolean.TRUE.equals(this.persistDefaultChannelOnReinstall);
|
|
794
|
+
final boolean restoredReinstall = defaultChannelPersistenceDisabled && this.isRestoredReinstall();
|
|
795
|
+
boolean installMarkerCanBePrepared = true;
|
|
796
|
+
if (
|
|
797
|
+
shouldClearPersistedDefaultChannel(
|
|
798
|
+
Boolean.TRUE.equals(this.persistDefaultChannelOnReinstall),
|
|
799
|
+
resetWhenUpdate,
|
|
800
|
+
nativeBuildVersionChanged,
|
|
801
|
+
restoredReinstall
|
|
802
|
+
)
|
|
803
|
+
) {
|
|
804
|
+
installMarkerCanBePrepared = clearPersistedDefaultChannel(this.editor);
|
|
805
|
+
if (installMarkerCanBePrepared) {
|
|
806
|
+
logger.info("Cleared persisted defaultChannel because reinstall persistence is disabled");
|
|
807
|
+
} else {
|
|
808
|
+
logger.warn("Cannot durably clear persisted defaultChannel");
|
|
809
|
+
this.defaultChannelCleanupMustRetry = true;
|
|
810
|
+
if (!invalidateDefaultChannelInstallMarker(this.defaultChannelInstallMarker())) {
|
|
811
|
+
logger.warn("Cannot invalidate default channel install marker for cleanup retry");
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (defaultChannelPersistenceDisabled && installMarkerCanBePrepared) {
|
|
816
|
+
this.prepareDefaultChannelInstallMarker();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
final String configDefaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
780
820
|
// Load defaultChannel: first try from persistent storage (set via setChannel), then fall back to config
|
|
781
821
|
if (this.prefs.contains(DEFAULT_CHANNEL_PREF_KEY)) {
|
|
782
822
|
final String storedDefaultChannel = this.prefs.getString(DEFAULT_CHANNEL_PREF_KEY, "");
|
|
@@ -784,10 +824,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
784
824
|
this.implementation.defaultChannel = storedDefaultChannel;
|
|
785
825
|
logger.info("Loaded persisted defaultChannel from setChannel()");
|
|
786
826
|
} else {
|
|
787
|
-
this.implementation.defaultChannel =
|
|
827
|
+
this.implementation.defaultChannel = configDefaultChannel;
|
|
788
828
|
}
|
|
789
829
|
} else {
|
|
790
|
-
this.implementation.defaultChannel =
|
|
830
|
+
this.implementation.defaultChannel = configDefaultChannel;
|
|
791
831
|
}
|
|
792
832
|
|
|
793
833
|
this.periodCheckDelay = normalizedPeriodCheckDelayMs(this.getConfig().getInt("periodCheckDelay", 0));
|
|
@@ -856,11 +896,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
856
896
|
? this.prefs.getBoolean(PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY, false)
|
|
857
897
|
: this.shakeChannelSelectorEnabled;
|
|
858
898
|
}
|
|
859
|
-
boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
|
|
860
899
|
|
|
861
900
|
// Check if app was recently installed/updated BEFORE cleanupObsoleteVersions updates LatestVersionNative
|
|
862
901
|
this.wasRecentlyInstalledOrUpdated = this.checkIfRecentlyInstalledOrUpdated();
|
|
863
|
-
final boolean nativeBuildVersionChanged = this.hasNativeBuildVersionChanged();
|
|
864
902
|
|
|
865
903
|
this.implementation.autoReset(this.currentBuildVersion, resetWhenUpdate);
|
|
866
904
|
if (nativeBuildVersionChanged) {
|
|
@@ -871,11 +909,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
871
909
|
this.reportPreviousAppExitReasons();
|
|
872
910
|
this.reportPreviousWebViewRenderProcessGone();
|
|
873
911
|
this.installWebViewStatsReporter();
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
912
|
+
// Downloads (including shake-menu / CapgoUpdater entry points) wait on this gate.
|
|
913
|
+
this.implementation.downloadGate = this::waitForCleanupIfNeeded;
|
|
914
|
+
// Always run async cleanup: delete obsolete bundles on native update (when enabled)
|
|
915
|
+
// and sweep orphan folders every launch. Must not block app startup.
|
|
916
|
+
if (!resetWhenUpdate) {
|
|
877
917
|
this.persistCurrentNativeBuildVersion();
|
|
878
918
|
}
|
|
919
|
+
this.cleanupObsoleteVersions(resetWhenUpdate);
|
|
879
920
|
|
|
880
921
|
// Check for 'kill' delay condition on app launch
|
|
881
922
|
// This handles cases where the app was killed by the system (onDestroy is not reliable)
|
|
@@ -909,6 +950,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
909
950
|
} else {
|
|
910
951
|
logger.info("Using activity lifecycle callbacks for foreground/background detection (Android <14)");
|
|
911
952
|
}
|
|
953
|
+
|
|
954
|
+
// Expect notifyAppReady before the first appReady/splash hide (same idea as iOS).
|
|
955
|
+
this.armPendingNotifyAppReadyWait();
|
|
912
956
|
}
|
|
913
957
|
|
|
914
958
|
private boolean semaphoreWait(final int phase, Number waitTime) {
|
|
@@ -951,6 +995,32 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
951
995
|
semaphoreReady.arriveAndDeregister();
|
|
952
996
|
}
|
|
953
997
|
|
|
998
|
+
private void armPendingNotifyAppReadyWait() {
|
|
999
|
+
this.clearPendingNotifyAppReadyWait();
|
|
1000
|
+
this.pendingNotifyAppReadyPhase = this.semaphoreUp();
|
|
1001
|
+
this.pendingNotifyAppReadyWait = true;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private void clearPendingNotifyAppReadyWait() {
|
|
1005
|
+
if (!this.pendingNotifyAppReadyWait) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
final int phase = this.pendingNotifyAppReadyPhase;
|
|
1009
|
+
this.pendingNotifyAppReadyWait = false;
|
|
1010
|
+
this.pendingNotifyAppReadyPhase = -1;
|
|
1011
|
+
this.cleanupTimedOutSemaphoreWait(phase);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private boolean consumePendingNotifyAppReadyWait(final int[] phaseOut) {
|
|
1015
|
+
if (!this.pendingNotifyAppReadyWait) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
phaseOut[0] = this.pendingNotifyAppReadyPhase;
|
|
1019
|
+
this.pendingNotifyAppReadyWait = false;
|
|
1020
|
+
this.pendingNotifyAppReadyPhase = -1;
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
954
1024
|
protected long getMinimumPendingBundleAppReadyTimeoutMs() {
|
|
955
1025
|
return PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS;
|
|
956
1026
|
}
|
|
@@ -989,19 +1059,40 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
989
1059
|
|
|
990
1060
|
private void sendReadyToJs(final BundleInfo current, final String msg, final boolean isDirectUpdate) {
|
|
991
1061
|
logger.info("sendReadyToJs: " + msg);
|
|
992
|
-
final
|
|
993
|
-
|
|
994
|
-
ret.put("status", msg);
|
|
1062
|
+
final int[] pendingPhase = new int[] { -1 };
|
|
1063
|
+
final boolean shouldWait = this.consumePendingNotifyAppReadyWait(pendingPhase);
|
|
995
1064
|
|
|
996
|
-
|
|
997
|
-
|
|
1065
|
+
final Runnable emitReady = () -> {
|
|
1066
|
+
final JSObject ret = new JSObject();
|
|
1067
|
+
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
1068
|
+
ret.put("status", msg);
|
|
998
1069
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1070
|
+
this.notifyListeners("appReady", ret, true);
|
|
1071
|
+
|
|
1072
|
+
// Auto hide splashscreen if enabled
|
|
1073
|
+
// We show it on background when conditions are met, so we should hide it on foreground regardless of update outcome
|
|
1074
|
+
if (this.autoSplashscreen) {
|
|
1075
|
+
this.hideSplashscreen();
|
|
1076
|
+
}
|
|
1077
|
+
this.hidePreviewTransitionLoader("app-ready");
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
if (!shouldWait) {
|
|
1081
|
+
emitReady.run();
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// Never block the UI thread waiting for notifyAppReady (JS needs it).
|
|
1086
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
1087
|
+
startNewThread(() -> {
|
|
1088
|
+
this.semaphoreWait(pendingPhase[0], this.appReadyTimeout);
|
|
1089
|
+
emitReady.run();
|
|
1090
|
+
});
|
|
1091
|
+
return;
|
|
1003
1092
|
}
|
|
1004
|
-
|
|
1093
|
+
|
|
1094
|
+
this.semaphoreWait(pendingPhase[0], this.appReadyTimeout);
|
|
1095
|
+
emitReady.run();
|
|
1005
1096
|
}
|
|
1006
1097
|
|
|
1007
1098
|
private void hideSplashscreen() {
|
|
@@ -1305,6 +1396,74 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1305
1396
|
return false;
|
|
1306
1397
|
}
|
|
1307
1398
|
|
|
1399
|
+
static boolean shouldClearPersistedDefaultChannel(
|
|
1400
|
+
final boolean persistDefaultChannelOnReinstall,
|
|
1401
|
+
final boolean resetWhenUpdate,
|
|
1402
|
+
final boolean nativeBuildVersionChanged,
|
|
1403
|
+
final boolean restoredReinstall
|
|
1404
|
+
) {
|
|
1405
|
+
return !persistDefaultChannelOnReinstall && (restoredReinstall || (resetWhenUpdate && nativeBuildVersionChanged));
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
static boolean clearPersistedDefaultChannel(final SharedPreferences.Editor editor) {
|
|
1409
|
+
editor.remove(DEFAULT_CHANNEL_PREF_KEY);
|
|
1410
|
+
editor.remove(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_PREF_KEY);
|
|
1411
|
+
editor.remove(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY);
|
|
1412
|
+
return editor.commit();
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
private File defaultChannelInstallMarker() {
|
|
1416
|
+
return new File(this.getContext().getNoBackupFilesDir(), DEFAULT_CHANNEL_INSTALL_MARKER_FILE);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
static boolean invalidateDefaultChannelInstallMarker(final File marker) {
|
|
1420
|
+
return !marker.exists() || marker.delete();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
private boolean isRestoredReinstall() {
|
|
1424
|
+
return isRestoredReinstall(
|
|
1425
|
+
this.defaultChannelInstallMarker(),
|
|
1426
|
+
this.prefs.getBoolean(DEFAULT_CHANNEL_INSTALL_MARKER_PREF_KEY, false)
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
static boolean isRestoredReinstall(final File marker, final boolean markerWasCreated) {
|
|
1431
|
+
return markerWasCreated && !marker.exists();
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
private void prepareDefaultChannelInstallMarker() {
|
|
1435
|
+
prepareDefaultChannelInstallMarker(
|
|
1436
|
+
this.defaultChannelInstallMarker(),
|
|
1437
|
+
this.prefs.getBoolean(DEFAULT_CHANNEL_INSTALL_MARKER_PREF_KEY, false),
|
|
1438
|
+
this.editor,
|
|
1439
|
+
this.logger
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
static void prepareDefaultChannelInstallMarker(
|
|
1444
|
+
final File marker,
|
|
1445
|
+
final boolean markerWasCreated,
|
|
1446
|
+
final SharedPreferences.Editor editor,
|
|
1447
|
+
final Logger logger
|
|
1448
|
+
) {
|
|
1449
|
+
if (!marker.exists()) {
|
|
1450
|
+
try {
|
|
1451
|
+
if (!marker.createNewFile() && !marker.exists()) {
|
|
1452
|
+
throw new IOException("Marker file was not created");
|
|
1453
|
+
}
|
|
1454
|
+
} catch (final IOException e) {
|
|
1455
|
+
logger.warn("Cannot create default channel install marker: " + e.getMessage());
|
|
1456
|
+
editor.remove(DEFAULT_CHANNEL_INSTALL_MARKER_PREF_KEY);
|
|
1457
|
+
editor.commit();
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
if (!markerWasCreated) {
|
|
1462
|
+
editor.putBoolean(DEFAULT_CHANNEL_INSTALL_MARKER_PREF_KEY, true);
|
|
1463
|
+
editor.apply();
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1308
1467
|
private boolean hasNativeBuildVersionChanged() {
|
|
1309
1468
|
final String lastKnownVersion = this.getStoredNativeBuildVersion();
|
|
1310
1469
|
return !lastKnownVersion.isEmpty() && !lastKnownVersion.equals(this.currentBuildVersion);
|
|
@@ -2222,67 +2381,57 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2222
2381
|
return false;
|
|
2223
2382
|
}
|
|
2224
2383
|
|
|
2225
|
-
private void cleanupObsoleteVersions() {
|
|
2384
|
+
private void cleanupObsoleteVersions(final boolean resetWhenUpdate) {
|
|
2385
|
+
// Latch created before start so waiters never race past an unstarted cleanup thread.
|
|
2386
|
+
final CountDownLatch latch = new CountDownLatch(1);
|
|
2387
|
+
this.cleanupComplete = false;
|
|
2388
|
+
this.cleanupLatch = latch;
|
|
2226
2389
|
cleanupThread = startNewThread(() -> {
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
for (final BundleInfo info : storedBundles) {
|
|
2250
|
-
if (info != null && info.getId() != null && !info.getId().isEmpty()) {
|
|
2251
|
-
allowedIds.add(info.getId());
|
|
2390
|
+
try {
|
|
2391
|
+
synchronized (cleanupLock) {
|
|
2392
|
+
try {
|
|
2393
|
+
final String previous = this.getStoredNativeBuildVersion();
|
|
2394
|
+
final boolean nativeVersionChanged = !"".equals(previous) && !Objects.equals(this.currentBuildVersion, previous);
|
|
2395
|
+
if (resetWhenUpdate && nativeVersionChanged) {
|
|
2396
|
+
logger.info("New native build version detected: " + this.currentBuildVersion);
|
|
2397
|
+
this.implementation.reset(true);
|
|
2398
|
+
final List<BundleInfo> installed = this.implementation.list(false);
|
|
2399
|
+
for (final BundleInfo bundle : installed) {
|
|
2400
|
+
try {
|
|
2401
|
+
logger.info("Deleting obsolete bundle: " + bundle.getId());
|
|
2402
|
+
this.implementation.delete(bundle.getId());
|
|
2403
|
+
} catch (final Exception e) {
|
|
2404
|
+
logger.error("Failed to delete: " + bundle.getId() + " " + e.getMessage());
|
|
2405
|
+
}
|
|
2406
|
+
try {
|
|
2407
|
+
Thread.sleep(75L);
|
|
2408
|
+
} catch (final InterruptedException ie) {
|
|
2409
|
+
Thread.currentThread().interrupt();
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2252
2412
|
}
|
|
2413
|
+
this.implementation.cleanupDeltaCache();
|
|
2253
2414
|
}
|
|
2254
|
-
this.implementation.cleanupDownloadDirectories(allowedIds, Thread.currentThread());
|
|
2255
|
-
this.implementation.cleanupOrphanedTempFolders(Thread.currentThread());
|
|
2256
2415
|
|
|
2257
|
-
//
|
|
2258
|
-
|
|
2259
|
-
logger.warn("Cleanup was cancelled before delta cache cleanup");
|
|
2260
|
-
return;
|
|
2261
|
-
}
|
|
2262
|
-
this.implementation.cleanupDeltaCache(Thread.currentThread());
|
|
2263
|
-
}
|
|
2264
|
-
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
2265
|
-
this.editor.apply();
|
|
2266
|
-
} catch (Exception e) {
|
|
2267
|
-
logger.error("Error during cleanupObsoleteVersions: " + e.getMessage());
|
|
2268
|
-
} finally {
|
|
2269
|
-
cleanupComplete = true;
|
|
2270
|
-
logger.info("Cleanup complete");
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
});
|
|
2416
|
+
// Resume any DELETING leftovers from prior kills, one-by-one.
|
|
2417
|
+
this.implementation.drainPendingDeletes();
|
|
2274
2418
|
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2419
|
+
// Always sweep orphan folders so incomplete prior cleanups (or failed deletes)
|
|
2420
|
+
// cannot leave hundreds of MB behind across launches.
|
|
2421
|
+
final Set<String> allowedIds = this.implementation.allowedBundleIdsForCleanup();
|
|
2422
|
+
this.implementation.cleanupDownloadDirectories(allowedIds);
|
|
2423
|
+
this.implementation.cleanupOrphanedTempFolders(null);
|
|
2424
|
+
|
|
2425
|
+
this.persistCurrentNativeBuildVersion();
|
|
2426
|
+
} catch (Exception e) {
|
|
2427
|
+
logger.error("Error during cleanupObsoleteVersions: " + e.getMessage());
|
|
2428
|
+
} finally {
|
|
2429
|
+
cleanupComplete = true;
|
|
2430
|
+
logger.info("Cleanup complete");
|
|
2431
|
+
}
|
|
2283
2432
|
}
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2433
|
+
} finally {
|
|
2434
|
+
latch.countDown();
|
|
2286
2435
|
}
|
|
2287
2436
|
});
|
|
2288
2437
|
}
|
|
@@ -2296,6 +2445,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2296
2445
|
}
|
|
2297
2446
|
|
|
2298
2447
|
void persistCurrentNativeBuildVersion() {
|
|
2448
|
+
if (this.defaultChannelCleanupMustRetry) {
|
|
2449
|
+
logger.warn("Keeping the previous native build version so default channel cleanup retries");
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2299
2452
|
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
2300
2453
|
this.editor.apply();
|
|
2301
2454
|
}
|
|
@@ -2306,11 +2459,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2306
2459
|
}
|
|
2307
2460
|
|
|
2308
2461
|
logger.info("Waiting for cleanup to complete before starting download...");
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2462
|
+
try {
|
|
2463
|
+
this.cleanupLatch.await();
|
|
2464
|
+
} catch (final InterruptedException e) {
|
|
2465
|
+
Thread.currentThread().interrupt();
|
|
2466
|
+
logger.warn("Interrupted while waiting for cleanup");
|
|
2467
|
+
throw new IllegalStateException("Interrupted while waiting for cleanup");
|
|
2313
2468
|
}
|
|
2469
|
+
logger.info("Cleanup finished, proceeding with download");
|
|
2314
2470
|
}
|
|
2315
2471
|
|
|
2316
2472
|
public void notifyDownload(final String id, final int percent) {
|
|
@@ -2648,6 +2804,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2648
2804
|
final String checksum,
|
|
2649
2805
|
final JSONArray manifest
|
|
2650
2806
|
) throws IOException {
|
|
2807
|
+
// Manual/preview downloads must wait too — launch orphan sweep can delete their temps.
|
|
2808
|
+
waitForCleanupIfNeeded();
|
|
2651
2809
|
if (manifest != null) {
|
|
2652
2810
|
return this.implementation.downloadManifest(url, version, sessionKey, checksum, manifest);
|
|
2653
2811
|
}
|
|
@@ -2821,6 +2979,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2821
2979
|
}
|
|
2822
2980
|
|
|
2823
2981
|
protected boolean _reload() {
|
|
2982
|
+
// Drop any launch pending wait; this reload owns notifyAppReady synchronization.
|
|
2983
|
+
this.clearPendingNotifyAppReadyWait();
|
|
2824
2984
|
final int phase = this.semaphoreUp();
|
|
2825
2985
|
this.applyCurrentBundleToBridge();
|
|
2826
2986
|
|
|
@@ -4961,15 +5121,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4961
5121
|
this.implementation.setError(current);
|
|
4962
5122
|
this.performReset(true, false, true);
|
|
4963
5123
|
if (CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()) {
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
5124
|
+
final String failedId = current.getId();
|
|
5125
|
+
final String failedVersion = current.getVersionName();
|
|
5126
|
+
logger.info("Deleting failing bundle: " + failedVersion);
|
|
5127
|
+
// Mark before async work so kill/OOM still resumes via drainPendingDeletes.
|
|
5128
|
+
CapacitorUpdaterPlugin.this.implementation.saveBundleInfo(failedId, current.setStatus(BundleStatus.DELETING));
|
|
5129
|
+
startNewThread(() -> {
|
|
5130
|
+
try {
|
|
5131
|
+
final Boolean res = CapacitorUpdaterPlugin.this.implementation.delete(failedId, false, false);
|
|
5132
|
+
if (Boolean.TRUE.equals(res)) {
|
|
5133
|
+
logger.info("Failed bundle deleted: " + failedVersion);
|
|
5134
|
+
}
|
|
5135
|
+
} catch (final IOException e) {
|
|
5136
|
+
logger.error("Failed to delete failed bundle: " + failedVersion + " " + e.getMessage());
|
|
4969
5137
|
}
|
|
4970
|
-
}
|
|
4971
|
-
logger.error("Failed to delete failed bundle: " + current.getVersionName() + " " + e.getMessage());
|
|
4972
|
-
}
|
|
5138
|
+
});
|
|
4973
5139
|
}
|
|
4974
5140
|
} else {
|
|
4975
5141
|
logger.info("notifyAppReady was called. This is fine: " + current.getId());
|