@capgo/capacitor-updater 8.51.0 → 8.51.1
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 +1 -1
- 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 +143 -81
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +289 -59
- 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 +8 -0
- package/dist/esm/definitions.d.ts +1 -1
- 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 +105 -49
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +186 -32
- package/package.json +1 -1
|
@@ -39,7 +39,9 @@ import java.security.SecureRandom;
|
|
|
39
39
|
import java.util.ArrayList;
|
|
40
40
|
import java.util.Date;
|
|
41
41
|
import java.util.HashMap;
|
|
42
|
+
import java.util.HashSet;
|
|
42
43
|
import java.util.Iterator;
|
|
44
|
+
import java.util.LinkedHashSet;
|
|
43
45
|
import java.util.List;
|
|
44
46
|
import java.util.Map;
|
|
45
47
|
import java.util.Objects;
|
|
@@ -72,14 +74,20 @@ public class CapgoUpdater {
|
|
|
72
74
|
private static final String FALLBACK_VERSION = "pastVersion";
|
|
73
75
|
private static final String NEXT_VERSION = "nextVersion";
|
|
74
76
|
private static final String PREVIEW_FALLBACK_VERSION = "previewFallbackVersion";
|
|
77
|
+
private static final String PENDING_DELETE_IDS = "pendingDeleteIds";
|
|
75
78
|
private static final String bundleDirectory = "versions";
|
|
76
79
|
private static final String TEMP_UNZIP_PREFIX = "capgo_unzip_";
|
|
80
|
+
private static final long DELETE_PACE_MS = 75L;
|
|
81
|
+
private final Object deleteLock = new Object();
|
|
77
82
|
private static final String CAPACITOR_CONFIG_ASSET = "capacitor.config.json";
|
|
78
83
|
private static final String BACKGROUND_RUNNER_CONFIG_KEY = "BackgroundRunner";
|
|
79
84
|
private static final String BACKGROUND_RUNNER_WORKER_CLASS = "io.ionic.backgroundrunner.plugin.RunnerWorker";
|
|
80
85
|
|
|
81
86
|
public static final String TAG = "Capacitor-updater";
|
|
82
87
|
public SharedPreferences.Editor editor;
|
|
88
|
+
|
|
89
|
+
/** Optional gate run before any download touches disk (e.g. wait for launch cleanup). */
|
|
90
|
+
public Runnable downloadGate = null;
|
|
83
91
|
public SharedPreferences prefs;
|
|
84
92
|
|
|
85
93
|
public File documentsDir;
|
|
@@ -136,7 +144,7 @@ public class CapgoUpdater {
|
|
|
136
144
|
|
|
137
145
|
private final FilenameFilter filter = (f, name) -> {
|
|
138
146
|
// ignore directories generated by mac os x
|
|
139
|
-
return
|
|
147
|
+
return !name.startsWith("__MACOSX") && !name.startsWith(".") && !name.startsWith(".DS_Store");
|
|
140
148
|
};
|
|
141
149
|
|
|
142
150
|
private boolean isProd() {
|
|
@@ -927,6 +935,11 @@ public class CapgoUpdater {
|
|
|
927
935
|
|
|
928
936
|
try {
|
|
929
937
|
this.deleteDirectory(entry, threadToCheck);
|
|
938
|
+
if (entry.exists()) {
|
|
939
|
+
logger.error("Orphan bundle directory still present after delete");
|
|
940
|
+
logger.debug("Bundle ID: " + id);
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
930
943
|
this.removeBundleInfo(id);
|
|
931
944
|
logger.info("Deleted orphan bundle directory");
|
|
932
945
|
logger.debug("Bundle ID: " + id);
|
|
@@ -938,6 +951,43 @@ public class CapgoUpdater {
|
|
|
938
951
|
}
|
|
939
952
|
}
|
|
940
953
|
|
|
954
|
+
public Set<String> allowedBundleIdsForCleanup() {
|
|
955
|
+
final Set<String> allowedIds = new HashSet<>();
|
|
956
|
+
for (final BundleInfo info : this.list(true)) {
|
|
957
|
+
if (info == null || info.getId() == null || info.getId().isEmpty()) {
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
// DELETED tombstones must not protect leftover folders.
|
|
961
|
+
// DELETING stays protected so drainPendingDeletes owns the removal.
|
|
962
|
+
if (info.isDeleted()) {
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
allowedIds.add(info.getId());
|
|
966
|
+
}
|
|
967
|
+
final String currentId = this.getCurrentBundleId();
|
|
968
|
+
if (currentId != null && !currentId.isEmpty()) {
|
|
969
|
+
allowedIds.add(currentId);
|
|
970
|
+
}
|
|
971
|
+
final BundleInfo fallback = this.getFallbackBundle();
|
|
972
|
+
if (fallback != null && fallback.getId() != null && !fallback.getId().isEmpty() && !fallback.isDeleting()) {
|
|
973
|
+
allowedIds.add(fallback.getId());
|
|
974
|
+
}
|
|
975
|
+
final BundleInfo next = this.getNextBundle();
|
|
976
|
+
if (next != null && next.getId() != null && !next.getId().isEmpty() && !next.isDeleting()) {
|
|
977
|
+
allowedIds.add(next.getId());
|
|
978
|
+
}
|
|
979
|
+
final BundleInfo previewFallback = this.getPreviewFallbackBundle();
|
|
980
|
+
if (
|
|
981
|
+
previewFallback != null &&
|
|
982
|
+
previewFallback.getId() != null &&
|
|
983
|
+
!previewFallback.getId().isEmpty() &&
|
|
984
|
+
!previewFallback.isDeleting()
|
|
985
|
+
) {
|
|
986
|
+
allowedIds.add(previewFallback.getId());
|
|
987
|
+
}
|
|
988
|
+
return allowedIds;
|
|
989
|
+
}
|
|
990
|
+
|
|
941
991
|
public void cleanupOrphanedTempFolders(final Thread threadToCheck) {
|
|
942
992
|
if (this.documentsDir == null) {
|
|
943
993
|
logger.warn("Documents directory is null, skipping temp folder cleanup");
|
|
@@ -1221,6 +1271,27 @@ public class CapgoUpdater {
|
|
|
1221
1271
|
);
|
|
1222
1272
|
}
|
|
1223
1273
|
|
|
1274
|
+
private void runDownloadGate() throws IOException {
|
|
1275
|
+
if (this.downloadGate == null) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
try {
|
|
1279
|
+
this.downloadGate.run();
|
|
1280
|
+
} catch (final RuntimeException e) {
|
|
1281
|
+
throw new IOException(e.getMessage() == null ? "Download gate failed" : e.getMessage(), e);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
private boolean runDownloadGateQuiet() {
|
|
1286
|
+
try {
|
|
1287
|
+
this.runDownloadGate();
|
|
1288
|
+
return true;
|
|
1289
|
+
} catch (final IOException e) {
|
|
1290
|
+
logger.error("Download blocked by cleanup gate: " + e.getMessage());
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1224
1295
|
public void downloadBackground(
|
|
1225
1296
|
final String url,
|
|
1226
1297
|
final String version,
|
|
@@ -1239,6 +1310,9 @@ public class CapgoUpdater {
|
|
|
1239
1310
|
final JSONArray manifest,
|
|
1240
1311
|
final boolean setNext
|
|
1241
1312
|
) {
|
|
1313
|
+
if (!this.runDownloadGateQuiet()) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1242
1316
|
final String id = this.randomString();
|
|
1243
1317
|
|
|
1244
1318
|
// Check if version is already downloading, but allow retry if previous download failed
|
|
@@ -1263,9 +1337,10 @@ public class CapgoUpdater {
|
|
|
1263
1337
|
}
|
|
1264
1338
|
|
|
1265
1339
|
public BundleInfo download(final String url, final String version, final String sessionKey, final String checksum) throws IOException {
|
|
1340
|
+
this.runDownloadGate();
|
|
1266
1341
|
// Check for existing bundle with same version and clean up if in error state
|
|
1267
1342
|
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
1268
|
-
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted())) {
|
|
1343
|
+
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted() || existingBundle.isDeleting())) {
|
|
1269
1344
|
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
1270
1345
|
this.delete(existingBundle.getId(), true);
|
|
1271
1346
|
}
|
|
@@ -1311,13 +1386,14 @@ public class CapgoUpdater {
|
|
|
1311
1386
|
final String checksum,
|
|
1312
1387
|
final JSONArray manifest
|
|
1313
1388
|
) throws IOException {
|
|
1389
|
+
this.runDownloadGate();
|
|
1314
1390
|
if (manifest == null) {
|
|
1315
1391
|
return download(url, version, sessionKey, checksum);
|
|
1316
1392
|
}
|
|
1317
1393
|
|
|
1318
1394
|
// Check for existing bundle with same version and clean up if in error state
|
|
1319
1395
|
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
1320
|
-
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted())) {
|
|
1396
|
+
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted() || existingBundle.isDeleting())) {
|
|
1321
1397
|
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
1322
1398
|
this.delete(existingBundle.getId(), true);
|
|
1323
1399
|
}
|
|
@@ -1384,51 +1460,93 @@ public class CapgoUpdater {
|
|
|
1384
1460
|
}
|
|
1385
1461
|
|
|
1386
1462
|
public Boolean delete(final String id, final Boolean removeInfo) throws IOException {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
previewFallback.
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1463
|
+
return this.delete(id, removeInfo, true);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
public Boolean delete(final String id, final Boolean removeInfo, final boolean cancelActiveDownload) throws IOException {
|
|
1467
|
+
synchronized (this.deleteLock) {
|
|
1468
|
+
final BundleInfo deleted = this.getBundleInfo(id);
|
|
1469
|
+
if (deleted.isBuiltin() || this.getCurrentBundleId().equals(id)) {
|
|
1470
|
+
logger.error("Cannot delete current or builtin bundle");
|
|
1471
|
+
logger.debug("Bundle ID: " + id);
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1474
|
+
final BundleInfo previewFallback = this.getPreviewFallbackBundle();
|
|
1475
|
+
if (
|
|
1476
|
+
previewFallback != null &&
|
|
1477
|
+
!previewFallback.isDeleted() &&
|
|
1478
|
+
!previewFallback.isErrorStatus() &&
|
|
1479
|
+
!previewFallback.isDeleting() &&
|
|
1480
|
+
previewFallback.getId().equals(id)
|
|
1481
|
+
) {
|
|
1482
|
+
logger.error("Cannot delete the preview fallback bundle");
|
|
1483
|
+
logger.debug("Bundle ID: " + id);
|
|
1484
|
+
return false;
|
|
1485
|
+
}
|
|
1486
|
+
final BundleInfo next = this.getNextBundle();
|
|
1487
|
+
if (next != null && !next.isDeleted() && !next.isErrorStatus() && !next.isDeleting() && next.getId().equals(id)) {
|
|
1488
|
+
logger.error("Cannot delete the next bundle");
|
|
1489
|
+
logger.debug("Bundle ID: " + id);
|
|
1490
|
+
return false;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
final File bundle = this.getBundleDirectory(id);
|
|
1494
|
+
final boolean hadRegistry = this.hasStoredBundleInfo(id);
|
|
1495
|
+
final boolean hadFolder = bundle.exists();
|
|
1496
|
+
if (!hadRegistry && !hadFolder) {
|
|
1497
|
+
logger.error("Cannot delete unknown bundle");
|
|
1498
|
+
logger.debug("Bundle ID: " + id);
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// Persist DELETING before touching disk so kill/OOM can resume on next launch.
|
|
1503
|
+
if (!deleted.isDeleting()) {
|
|
1504
|
+
if (!this.saveBundleInfo(id, deleted.setStatus(BundleStatus.DELETING))) {
|
|
1505
|
+
logger.error("Failed to persist DELETING marker, aborting disk delete");
|
|
1506
|
+
logger.debug("Bundle ID: " + id);
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// Cancel download for this version if active
|
|
1512
|
+
if (cancelActiveDownload && this.activity != null) {
|
|
1513
|
+
DownloadWorkerManager.cancelVersionDownload(this.activity, deleted.getVersionName());
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
if (bundle.exists()) {
|
|
1517
|
+
try {
|
|
1518
|
+
this.deleteDirectory(bundle);
|
|
1519
|
+
} catch (final IOException e) {
|
|
1520
|
+
logger.error("Failed to delete bundle folder, will retry later");
|
|
1521
|
+
logger.debug("Bundle ID: " + id + ", Error: " + e.getMessage());
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Only drop registry after the folder is confirmed gone.
|
|
1527
|
+
if (bundle.exists()) {
|
|
1528
|
+
logger.error("Bundle folder still present after delete, will retry later");
|
|
1529
|
+
logger.debug("Bundle ID: " + id);
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
final boolean finalized;
|
|
1534
|
+
if (Boolean.FALSE.equals(removeInfo)) {
|
|
1535
|
+
finalized = this.saveBundleInfo(id, deleted.setStatus(BundleStatus.DELETED));
|
|
1419
1536
|
} else {
|
|
1420
|
-
this.
|
|
1537
|
+
finalized = this.saveBundleInfo(id, null);
|
|
1538
|
+
}
|
|
1539
|
+
if (!finalized) {
|
|
1540
|
+
logger.error("Failed to finalize delete registry update, will retry later");
|
|
1541
|
+
logger.debug("Bundle ID: " + id);
|
|
1542
|
+
return false;
|
|
1421
1543
|
}
|
|
1544
|
+
this.sendStats("delete", deleted.getVersionName());
|
|
1545
|
+
this.dequeuePendingDelete(id);
|
|
1546
|
+
logger.info("Bundle deleted and confirmed gone");
|
|
1547
|
+
logger.debug("Bundle ID: " + id);
|
|
1422
1548
|
return true;
|
|
1423
1549
|
}
|
|
1424
|
-
logger.info("Bundle not found on disk");
|
|
1425
|
-
logger.debug("Version: " + deleted.getVersionName());
|
|
1426
|
-
// perhaps we did not find the bundle in the files, but if the user requested a delete, we delete
|
|
1427
|
-
if (removeInfo) {
|
|
1428
|
-
this.removeBundleInfo(id);
|
|
1429
|
-
}
|
|
1430
|
-
this.sendStats("delete", deleted.getVersionName());
|
|
1431
|
-
return false;
|
|
1432
1550
|
}
|
|
1433
1551
|
|
|
1434
1552
|
public Boolean delete(final String id) {
|
|
@@ -1441,6 +1559,82 @@ public class CapgoUpdater {
|
|
|
1441
1559
|
}
|
|
1442
1560
|
}
|
|
1443
1561
|
|
|
1562
|
+
/**
|
|
1563
|
+
* Resume incomplete deletes one-by-one. Safe across app kill / OOM because
|
|
1564
|
+
* delete() marks DELETING before disk work and only clears registry after confirm.
|
|
1565
|
+
*/
|
|
1566
|
+
public void drainPendingDeletes() {
|
|
1567
|
+
final LinkedHashSet<String> pendingIds = new LinkedHashSet<>();
|
|
1568
|
+
for (final BundleInfo info : this.list(true)) {
|
|
1569
|
+
if (info != null && info.isDeleting() && info.getId() != null && !info.getId().isEmpty()) {
|
|
1570
|
+
pendingIds.add(info.getId());
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
pendingIds.addAll(this.getPendingDeleteIds());
|
|
1574
|
+
for (final String id : pendingIds) {
|
|
1575
|
+
try {
|
|
1576
|
+
logger.info("Resuming pending delete for bundle: " + id);
|
|
1577
|
+
if (Boolean.TRUE.equals(this.delete(id, true))) {
|
|
1578
|
+
this.dequeuePendingDelete(id);
|
|
1579
|
+
}
|
|
1580
|
+
} catch (final Exception e) {
|
|
1581
|
+
logger.error("Pending delete failed, will retry next launch");
|
|
1582
|
+
logger.debug("Bundle ID: " + id + ", Error: " + e.getMessage());
|
|
1583
|
+
}
|
|
1584
|
+
try {
|
|
1585
|
+
Thread.sleep(DELETE_PACE_MS);
|
|
1586
|
+
} catch (final InterruptedException ie) {
|
|
1587
|
+
Thread.currentThread().interrupt();
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
private Set<String> getPendingDeleteIds() {
|
|
1594
|
+
final Set<String> ids = new LinkedHashSet<>();
|
|
1595
|
+
if (this.prefs == null) {
|
|
1596
|
+
return ids;
|
|
1597
|
+
}
|
|
1598
|
+
final String raw = this.prefs.getString(PENDING_DELETE_IDS, "");
|
|
1599
|
+
if (raw == null || raw.isEmpty()) {
|
|
1600
|
+
return ids;
|
|
1601
|
+
}
|
|
1602
|
+
for (final String part : raw.split(",")) {
|
|
1603
|
+
if (part != null && !part.isEmpty()) {
|
|
1604
|
+
ids.add(part);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return ids;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
private void enqueuePendingDelete(final String id) {
|
|
1611
|
+
if (id == null || id.isEmpty() || this.editor == null || this.prefs == null) {
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
final Set<String> ids = this.getPendingDeleteIds();
|
|
1615
|
+
if (!ids.add(id)) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
this.editor.putString(PENDING_DELETE_IDS, String.join(",", ids));
|
|
1619
|
+
this.editor.commit();
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
private void dequeuePendingDelete(final String id) {
|
|
1623
|
+
if (id == null || id.isEmpty() || this.editor == null || this.prefs == null) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
final Set<String> ids = this.getPendingDeleteIds();
|
|
1627
|
+
if (!ids.remove(id)) {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
if (ids.isEmpty()) {
|
|
1631
|
+
this.editor.remove(PENDING_DELETE_IDS);
|
|
1632
|
+
} else {
|
|
1633
|
+
this.editor.putString(PENDING_DELETE_IDS, String.join(",", ids));
|
|
1634
|
+
}
|
|
1635
|
+
this.editor.commit();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1444
1638
|
private File getBundleDirectory(final String id) {
|
|
1445
1639
|
return new File(this.documentsDir, bundleDirectory + "/" + id);
|
|
1446
1640
|
}
|
|
@@ -1448,7 +1642,13 @@ public class CapgoUpdater {
|
|
|
1448
1642
|
private boolean bundleExists(final String id) {
|
|
1449
1643
|
final File bundle = this.getBundleDirectory(id);
|
|
1450
1644
|
final BundleInfo bundleInfo = this.getBundleInfo(id);
|
|
1451
|
-
return (
|
|
1645
|
+
return (
|
|
1646
|
+
bundle.isDirectory() &&
|
|
1647
|
+
bundle.exists() &&
|
|
1648
|
+
new File(bundle.getPath(), "/index.html").exists() &&
|
|
1649
|
+
!bundleInfo.isDeleted() &&
|
|
1650
|
+
!bundleInfo.isDeleting()
|
|
1651
|
+
);
|
|
1452
1652
|
}
|
|
1453
1653
|
|
|
1454
1654
|
static final class ResetState {
|
|
@@ -1627,21 +1827,51 @@ public class CapgoUpdater {
|
|
|
1627
1827
|
// Only attempt to delete when the fallback is a different bundle than the
|
|
1628
1828
|
// currently loaded one. Otherwise we spam logs with "Cannot delete <id>"
|
|
1629
1829
|
// because delete() protects the current bundle from removal.
|
|
1630
|
-
|
|
1631
|
-
|
|
1830
|
+
final String previousFallbackId = fallback.getId();
|
|
1831
|
+
final String previousFallbackVersion = fallback.getVersionName();
|
|
1832
|
+
final BundleInfo nextBundle = this.getNextBundle();
|
|
1833
|
+
final boolean previousIsNext =
|
|
1834
|
+
nextBundle != null &&
|
|
1835
|
+
previousFallbackId != null &&
|
|
1836
|
+
previousFallbackId.equals(nextBundle.getId()) &&
|
|
1837
|
+
!nextBundle.isDeleted() &&
|
|
1838
|
+
!nextBundle.isErrorStatus() &&
|
|
1839
|
+
!nextBundle.isDeleting();
|
|
1840
|
+
final boolean shouldDeletePrevious =
|
|
1841
|
+
Boolean.TRUE.equals(autoDeletePrevious) &&
|
|
1632
1842
|
!fallback.isBuiltin() &&
|
|
1633
|
-
|
|
1634
|
-
!
|
|
1635
|
-
!fallbackIsPreviewFallback
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1843
|
+
previousFallbackId != null &&
|
|
1844
|
+
!previousFallbackId.equals(bundle.getId()) &&
|
|
1845
|
+
!fallbackIsPreviewFallback &&
|
|
1846
|
+
!previousIsNext;
|
|
1847
|
+
if (shouldDeletePrevious) {
|
|
1848
|
+
// Cancel any in-flight download for the old version before the async boundary
|
|
1849
|
+
// so a later download of the same version name is not cancelled mid-flight.
|
|
1850
|
+
if (this.activity != null) {
|
|
1851
|
+
DownloadWorkerManager.cancelVersionDownload(this.activity, previousFallbackVersion);
|
|
1852
|
+
}
|
|
1853
|
+
// Mark durable intent before fallback switch so a kill mid-flight still retries.
|
|
1854
|
+
if (!this.saveBundleInfo(previousFallbackId, fallback.setStatus(BundleStatus.DELETING))) {
|
|
1855
|
+
logger.error("Failed to persist DELETING for previous bundle; queueing durable retry");
|
|
1856
|
+
logger.debug("Bundle ID: " + previousFallbackId);
|
|
1857
|
+
this.enqueuePendingDelete(previousFallbackId);
|
|
1642
1858
|
}
|
|
1643
1859
|
}
|
|
1644
1860
|
this.setFallbackBundle(bundle);
|
|
1861
|
+
if (shouldDeletePrevious) {
|
|
1862
|
+
new Thread(() -> {
|
|
1863
|
+
try {
|
|
1864
|
+
final Boolean res = this.delete(previousFallbackId, true, false);
|
|
1865
|
+
if (Boolean.TRUE.equals(res)) {
|
|
1866
|
+
logger.info("Deleted previous bundle: " + previousFallbackVersion);
|
|
1867
|
+
} else {
|
|
1868
|
+
logger.debug("Previous bundle delete incomplete, will retry: " + previousFallbackId);
|
|
1869
|
+
}
|
|
1870
|
+
} catch (final IOException e) {
|
|
1871
|
+
logger.error("Failed to delete previous bundle: " + previousFallbackId + " " + e.getMessage());
|
|
1872
|
+
}
|
|
1873
|
+
}, "CapgoUpdater-autoDeletePrevious").start();
|
|
1874
|
+
}
|
|
1645
1875
|
}
|
|
1646
1876
|
|
|
1647
1877
|
public void setError(final BundleInfo bundle) {
|
|
@@ -2484,10 +2714,10 @@ public class CapgoUpdater {
|
|
|
2484
2714
|
this.saveBundleInfo(id, null);
|
|
2485
2715
|
}
|
|
2486
2716
|
|
|
2487
|
-
public
|
|
2717
|
+
public boolean saveBundleInfo(final String id, final BundleInfo info) {
|
|
2488
2718
|
if (id == null || (info != null && (info.isBuiltin() || info.isUnknown()))) {
|
|
2489
2719
|
logger.debug("Not saving info for bundle: [" + id + "] " + info);
|
|
2490
|
-
return;
|
|
2720
|
+
return false;
|
|
2491
2721
|
}
|
|
2492
2722
|
|
|
2493
2723
|
if (info == null) {
|
|
@@ -2499,7 +2729,7 @@ public class CapgoUpdater {
|
|
|
2499
2729
|
logger.debug("Storing info for bundle [" + id + "] " + update.getClass().getName() + " -> " + jsonString);
|
|
2500
2730
|
this.editor.putString(id + INFO_SUFFIX, jsonString);
|
|
2501
2731
|
}
|
|
2502
|
-
this.editor.commit();
|
|
2732
|
+
return this.editor.commit();
|
|
2503
2733
|
}
|
|
2504
2734
|
|
|
2505
2735
|
private void setBundleStatus(final String id, final BundleStatus status) {
|
|
@@ -40,7 +40,7 @@ public class DelayCondition {
|
|
|
40
40
|
public boolean equals(Object o) {
|
|
41
41
|
if (this == o) return true;
|
|
42
42
|
if (!(o instanceof DelayCondition that)) return false;
|
|
43
|
-
return
|
|
43
|
+
return getKind() == that.getKind() && Objects.equals(getValue(), that.getValue());
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
@Override
|
|
@@ -51,6 +51,6 @@ public class DelayCondition {
|
|
|
51
51
|
@NonNull
|
|
52
52
|
@Override
|
|
53
53
|
public String toString() {
|
|
54
|
-
return
|
|
54
|
+
return "DelayCondition{" + "kind=" + kind + ", value='" + value + '\'' + '}';
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -22,7 +22,7 @@ public class InternalUtils {
|
|
|
22
22
|
public static String getPackageName(PackageManager pm, String packageName) {
|
|
23
23
|
try {
|
|
24
24
|
PackageInfo pInfo = getPackageInfoInternal(pm, packageName);
|
|
25
|
-
return
|
|
25
|
+
return pInfo != null ? pInfo.packageName : null;
|
|
26
26
|
} catch (PackageManager.NameNotFoundException e) {
|
|
27
27
|
// Exception is handled internally, and null is returned to indicate the package name could not be retrieved
|
|
28
28
|
return null;
|
|
@@ -273,7 +273,7 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
273
273
|
String name = preview.optString("name", "");
|
|
274
274
|
JSObject bundle = preview.getJSObject("bundle");
|
|
275
275
|
String version = bundle == null ? "" : bundle.optString("version", "");
|
|
276
|
-
String label = !name.isEmpty() ? name :
|
|
276
|
+
String label = !name.isEmpty() ? name : !version.isEmpty() ? version : preview.optString("id", "Preview");
|
|
277
277
|
if (preview.optBoolean("isActive", false)) {
|
|
278
278
|
label += " (current)";
|
|
279
279
|
}
|
package/dist/docs.json
CHANGED
|
@@ -1604,7 +1604,7 @@ export interface CapacitorUpdaterPlugin {
|
|
|
1604
1604
|
* success: The bundle has been downloaded and is ready to be **SET** as the next bundle.
|
|
1605
1605
|
* error: The bundle has failed to download.
|
|
1606
1606
|
*/
|
|
1607
|
-
export type BundleStatus = 'success' | 'error' | 'pending' | 'downloading';
|
|
1607
|
+
export type BundleStatus = 'success' | 'error' | 'pending' | 'downloading' | 'deleted' | 'deleting';
|
|
1608
1608
|
export type DelayUntilNext = 'background' | 'kill' | 'nativeVersion' | 'date';
|
|
1609
1609
|
/**
|
|
1610
1610
|
* Classification for update-check responses that do not provide a downloadable bundle.
|