@capgo/capacitor-updater 8.42.2 → 8.42.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +80 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +7 -2
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +58 -4
- package/package.json +1 -1
|
@@ -84,7 +84,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
84
84
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
85
85
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
86
86
|
|
|
87
|
-
private final String pluginVersion = "8.42.
|
|
87
|
+
private final String pluginVersion = "8.42.4";
|
|
88
88
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
89
89
|
|
|
90
90
|
private SharedPreferences.Editor editor;
|
|
@@ -261,13 +261,90 @@ public class CapgoUpdater {
|
|
|
261
261
|
}
|
|
262
262
|
if (entries.length == 1 && !"index.html".equals(entries[0])) {
|
|
263
263
|
final File child = new File(sourceFile, entries[0]);
|
|
264
|
-
child.renameTo(destinationFile)
|
|
264
|
+
if (!child.renameTo(destinationFile)) {
|
|
265
|
+
throw new IOException("Failed to move bundle contents: " + child.getPath() + " -> " + destinationFile.getPath());
|
|
266
|
+
}
|
|
265
267
|
} else {
|
|
266
|
-
sourceFile.renameTo(destinationFile)
|
|
268
|
+
if (!sourceFile.renameTo(destinationFile)) {
|
|
269
|
+
throw new IOException("Failed to move bundle contents: " + sourceFile.getPath() + " -> " + destinationFile.getPath());
|
|
270
|
+
}
|
|
267
271
|
}
|
|
268
272
|
sourceFile.delete();
|
|
269
273
|
}
|
|
270
274
|
|
|
275
|
+
private void cacheBundleFilesAsync(final String id) {
|
|
276
|
+
io.execute(() -> cacheBundleFiles(id));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private void cacheBundleFiles(final String id) {
|
|
280
|
+
if (this.activity == null) {
|
|
281
|
+
logger.debug("Skip delta cache population: activity is null");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
final File bundleDir = this.getBundleDirectory(id);
|
|
286
|
+
if (!bundleDir.exists()) {
|
|
287
|
+
logger.debug("Skip delta cache population: bundle dir missing");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
final File cacheDir = new File(this.activity.getCacheDir(), "capgo_downloads");
|
|
292
|
+
if (cacheDir.exists() && !cacheDir.isDirectory()) {
|
|
293
|
+
logger.debug("Skip delta cache population: cache dir is not a directory");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
|
|
297
|
+
logger.debug("Skip delta cache population: failed to create cache dir");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
final List<File> files = new ArrayList<>();
|
|
302
|
+
collectFiles(bundleDir, files);
|
|
303
|
+
for (File file : files) {
|
|
304
|
+
final String checksum = CryptoCipher.calcChecksum(file);
|
|
305
|
+
if (checksum.isEmpty()) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
final String cacheName = checksum + "_" + file.getName();
|
|
309
|
+
final File cacheFile = new File(cacheDir, cacheName);
|
|
310
|
+
if (cacheFile.exists()) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
copyFile(file, cacheFile);
|
|
315
|
+
} catch (IOException e) {
|
|
316
|
+
logger.debug("Delta cache copy failed: " + file.getPath());
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private void collectFiles(final File dir, final List<File> files) {
|
|
322
|
+
final File[] entries = dir.listFiles();
|
|
323
|
+
if (entries == null) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
for (File entry : entries) {
|
|
327
|
+
if (!this.filter.accept(dir, entry.getName())) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (entry.isDirectory()) {
|
|
331
|
+
collectFiles(entry, files);
|
|
332
|
+
} else if (entry.isFile()) {
|
|
333
|
+
files.add(entry);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private void copyFile(final File source, final File dest) throws IOException {
|
|
339
|
+
try (final FileInputStream input = new FileInputStream(source); final FileOutputStream output = new FileOutputStream(dest)) {
|
|
340
|
+
final byte[] buffer = new byte[1024 * 1024];
|
|
341
|
+
int length;
|
|
342
|
+
while ((length = input.read(buffer)) != -1) {
|
|
343
|
+
output.write(buffer, 0, length);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
271
348
|
private void observeWorkProgress(Context context, String id) {
|
|
272
349
|
if (!(context instanceof LifecycleOwner)) {
|
|
273
350
|
logger.error("Context is not a LifecycleOwner, cannot observe work progress");
|
|
@@ -482,6 +559,7 @@ public class CapgoUpdater {
|
|
|
482
559
|
this.notifyDownload(id, 91);
|
|
483
560
|
final String idName = bundleDirectory + "/" + id;
|
|
484
561
|
this.flattenAssets(extractedDir, idName);
|
|
562
|
+
this.cacheBundleFilesAsync(id);
|
|
485
563
|
} else {
|
|
486
564
|
this.notifyDownload(id, 91);
|
|
487
565
|
final String idName = bundleDirectory + "/" + id;
|
|
@@ -308,7 +308,9 @@ public class DownloadService extends Worker {
|
|
|
308
308
|
String targetFileName = isBrotli ? fileName.substring(0, fileName.length() - 3) : fileName;
|
|
309
309
|
|
|
310
310
|
File targetFile = new File(destFolder, targetFileName);
|
|
311
|
-
|
|
311
|
+
String cacheBaseName = new File(isBrotli ? targetFileName : fileName).getName();
|
|
312
|
+
File cacheFile = new File(cacheFolder, finalFileHash + "_" + cacheBaseName);
|
|
313
|
+
final File legacyCacheFile = isBrotli ? new File(cacheFolder, finalFileHash + "_" + new File(fileName).getName()) : null;
|
|
312
314
|
File builtinFile = new File(builtinFolder, fileName);
|
|
313
315
|
|
|
314
316
|
// Ensure parent directories of the target file exist
|
|
@@ -324,7 +326,10 @@ public class DownloadService extends Worker {
|
|
|
324
326
|
if (builtinFile.exists() && verifyChecksum(builtinFile, finalFileHash)) {
|
|
325
327
|
copyFile(builtinFile, targetFile);
|
|
326
328
|
logger.debug("using builtin file " + fileName);
|
|
327
|
-
} else if (
|
|
329
|
+
} else if (
|
|
330
|
+
tryCopyFromCache(cacheFile, targetFile, finalFileHash) ||
|
|
331
|
+
(legacyCacheFile != null && tryCopyFromCache(legacyCacheFile, targetFile, finalFileHash))
|
|
332
|
+
) {
|
|
328
333
|
logger.debug("already cached " + fileName);
|
|
329
334
|
} else {
|
|
330
335
|
downloadAndVerify(downloadUrl, targetFile, cacheFile, finalFileHash, sessionKey, publicKey, finalIsBrotli);
|
|
@@ -71,7 +71,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
71
71
|
CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
|
|
72
72
|
]
|
|
73
73
|
public var implementation = CapgoUpdater()
|
|
74
|
-
private let pluginVersion: String = "8.42.
|
|
74
|
+
private let pluginVersion: String = "8.42.4"
|
|
75
75
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
76
76
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
77
77
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -383,6 +383,56 @@ import UIKit
|
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
private func populateDeltaCacheAsync(for id: String) {
|
|
387
|
+
DispatchQueue.global(qos: .utility).async { [weak self] in
|
|
388
|
+
self?.populateDeltaCache(for: id)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private func populateDeltaCache(for id: String) {
|
|
393
|
+
let bundleDir = self.getBundleDirectory(id: id)
|
|
394
|
+
let fileManager = FileManager.default
|
|
395
|
+
|
|
396
|
+
guard fileManager.fileExists(atPath: bundleDir.path) else {
|
|
397
|
+
logger.debug("Skip delta cache population: bundle dir missing")
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
do {
|
|
402
|
+
try fileManager.createDirectory(at: cacheFolder, withIntermediateDirectories: true, attributes: nil)
|
|
403
|
+
} catch {
|
|
404
|
+
logger.debug("Skip delta cache population: failed to create cache dir")
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
guard let enumerator = fileManager.enumerator(at: bundleDir, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles]) else {
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for case let fileURL as URL in enumerator {
|
|
413
|
+
let resourceValues = try? fileURL.resourceValues(forKeys: [.isDirectoryKey])
|
|
414
|
+
if resourceValues?.isDirectory == true {
|
|
415
|
+
continue
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let checksum = CryptoCipher.calcChecksum(filePath: fileURL)
|
|
419
|
+
if checksum.isEmpty {
|
|
420
|
+
continue
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let cacheFile = cacheFolder.appendingPathComponent("\(checksum)_\(fileURL.lastPathComponent)")
|
|
424
|
+
if fileManager.fileExists(atPath: cacheFile.path) {
|
|
425
|
+
continue
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
do {
|
|
429
|
+
try fileManager.copyItem(at: fileURL, to: cacheFile)
|
|
430
|
+
} catch {
|
|
431
|
+
logger.debug("Delta cache copy failed: \(fileURL.path)")
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
386
436
|
private func createInfoObject() -> InfoObject {
|
|
387
437
|
return InfoObject(
|
|
388
438
|
platform: "ios",
|
|
@@ -549,10 +599,11 @@ import UIKit
|
|
|
549
599
|
|
|
550
600
|
let finalFileHash = fileHash
|
|
551
601
|
let fileNameWithoutPath = (fileName as NSString).lastPathComponent
|
|
552
|
-
let cacheFileName = "\(finalFileHash)_\(fileNameWithoutPath)"
|
|
553
|
-
let cacheFilePath = cacheFolder.appendingPathComponent(cacheFileName)
|
|
554
|
-
|
|
555
602
|
let isBrotli = fileName.hasSuffix(".br")
|
|
603
|
+
let cacheBaseName = isBrotli ? String(fileNameWithoutPath.dropLast(3)) : fileNameWithoutPath
|
|
604
|
+
let cacheFilePath = cacheFolder.appendingPathComponent("\(finalFileHash)_\(cacheBaseName)")
|
|
605
|
+
let legacyCacheFilePath: URL? = isBrotli ? cacheFolder.appendingPathComponent("\(finalFileHash)_\(fileNameWithoutPath)") : nil
|
|
606
|
+
|
|
556
607
|
let destFileName = isBrotli ? String(fileName.dropLast(3)) : fileName
|
|
557
608
|
let destFilePath = destFolder.appendingPathComponent(destFileName)
|
|
558
609
|
let builtinFilePath = builtinFolder.appendingPathComponent(fileName)
|
|
@@ -571,7 +622,9 @@ import UIKit
|
|
|
571
622
|
self.logger.info("downloadManifest \(fileName) using builtin file \(id)")
|
|
572
623
|
}
|
|
573
624
|
// Try cache
|
|
574
|
-
else if
|
|
625
|
+
else if
|
|
626
|
+
self.tryCopyFromCache(from: cacheFilePath, to: destFilePath, expectedHash: finalFileHash) ||
|
|
627
|
+
(legacyCacheFilePath != nil && self.tryCopyFromCache(from: legacyCacheFilePath!, to: destFilePath, expectedHash: finalFileHash)) {
|
|
575
628
|
self.logger.info("downloadManifest \(fileName) copy from cache \(id)")
|
|
576
629
|
}
|
|
577
630
|
// Download
|
|
@@ -985,6 +1038,7 @@ import UIKit
|
|
|
985
1038
|
CryptoCipher.logChecksumInfo(label: "Calculated bundle checksum", hexChecksum: checksum)
|
|
986
1039
|
logger.info("Downloading: 80% (unzipping)")
|
|
987
1040
|
try self.saveDownloaded(sourceZip: finalPath, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
|
|
1041
|
+
self.populateDeltaCacheAsync(for: id)
|
|
988
1042
|
|
|
989
1043
|
} catch {
|
|
990
1044
|
logger.error("Failed to unzip file")
|