@capgo/capacitor-updater 8.0.1 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +9 -7
- package/README.md +984 -215
- package/android/build.gradle +24 -12
- package/android/proguard-rules.pro +22 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +110 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1310 -488
- package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java} +640 -203
- package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java} +119 -33
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +0 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +497 -133
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +80 -25
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +873 -154
- package/dist/esm/definitions.d.ts +881 -114
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +12 -1
- package/dist/esm/web.js +29 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +311 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +311 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1605 -0
- package/ios/{Plugin/CapacitorUpdater.swift → Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift} +523 -230
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +267 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/InternalUtils.swift +53 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +21 -19
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -975
- package/ios/Plugin/CryptoCipherV2.swift +0 -310
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java}
RENAMED
|
@@ -10,14 +10,11 @@ import android.app.Activity;
|
|
|
10
10
|
import android.content.Context;
|
|
11
11
|
import android.content.SharedPreferences;
|
|
12
12
|
import android.os.Build;
|
|
13
|
-
import android.util.Log;
|
|
14
13
|
import androidx.annotation.NonNull;
|
|
15
14
|
import androidx.lifecycle.LifecycleOwner;
|
|
16
15
|
import androidx.work.Data;
|
|
17
16
|
import androidx.work.WorkInfo;
|
|
18
17
|
import androidx.work.WorkManager;
|
|
19
|
-
import com.getcapacitor.JSObject;
|
|
20
|
-
import com.getcapacitor.plugin.WebView;
|
|
21
18
|
import com.google.common.util.concurrent.Futures;
|
|
22
19
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
23
20
|
import java.io.BufferedInputStream;
|
|
@@ -30,17 +27,28 @@ import java.io.IOException;
|
|
|
30
27
|
import java.security.SecureRandom;
|
|
31
28
|
import java.util.ArrayList;
|
|
32
29
|
import java.util.Date;
|
|
30
|
+
import java.util.HashMap;
|
|
33
31
|
import java.util.Iterator;
|
|
34
32
|
import java.util.List;
|
|
33
|
+
import java.util.Map;
|
|
35
34
|
import java.util.Objects;
|
|
35
|
+
import java.util.Set;
|
|
36
|
+
import java.util.concurrent.CompletableFuture;
|
|
37
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
38
|
+
import java.util.concurrent.ExecutorService;
|
|
39
|
+
import java.util.concurrent.Executors;
|
|
40
|
+
import java.util.concurrent.TimeUnit;
|
|
36
41
|
import java.util.zip.ZipEntry;
|
|
37
42
|
import java.util.zip.ZipInputStream;
|
|
38
43
|
import okhttp3.*;
|
|
44
|
+
import okhttp3.HttpUrl;
|
|
39
45
|
import org.json.JSONArray;
|
|
40
46
|
import org.json.JSONException;
|
|
41
47
|
import org.json.JSONObject;
|
|
42
48
|
|
|
43
|
-
public class
|
|
49
|
+
public class CapgoUpdater {
|
|
50
|
+
|
|
51
|
+
private final Logger logger;
|
|
44
52
|
|
|
45
53
|
private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
46
54
|
private static final SecureRandom rnd = new SecureRandom();
|
|
@@ -55,15 +63,14 @@ public class CapacitorUpdater {
|
|
|
55
63
|
public SharedPreferences.Editor editor;
|
|
56
64
|
public SharedPreferences prefs;
|
|
57
65
|
|
|
58
|
-
public OkHttpClient client;
|
|
59
|
-
|
|
60
66
|
public File documentsDir;
|
|
61
67
|
public Boolean directUpdate = false;
|
|
62
68
|
public Activity activity;
|
|
63
|
-
public String
|
|
69
|
+
public String pluginVersion = "";
|
|
64
70
|
public String versionBuild = "";
|
|
65
71
|
public String versionCode = "";
|
|
66
72
|
public String versionOs = "";
|
|
73
|
+
public String CAP_SERVER_PATH = "";
|
|
67
74
|
|
|
68
75
|
public String customId = "";
|
|
69
76
|
public String statsUrl = "";
|
|
@@ -74,6 +81,19 @@ public class CapacitorUpdater {
|
|
|
74
81
|
public String deviceID = "";
|
|
75
82
|
public int timeout = 20000;
|
|
76
83
|
|
|
84
|
+
// Flag to track if we received a 429 response - stops requests until app restart
|
|
85
|
+
private static volatile boolean rateLimitExceeded = false;
|
|
86
|
+
|
|
87
|
+
// Flag to track if we've already sent the rate limit statistic - prevents infinite loop
|
|
88
|
+
private static volatile boolean rateLimitStatisticSent = false;
|
|
89
|
+
|
|
90
|
+
private final Map<String, CompletableFuture<BundleInfo>> downloadFutures = new ConcurrentHashMap<>();
|
|
91
|
+
private final ExecutorService io = Executors.newSingleThreadExecutor();
|
|
92
|
+
|
|
93
|
+
public CapgoUpdater(Logger logger) {
|
|
94
|
+
this.logger = logger;
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
private final FilenameFilter filter = (f, name) -> {
|
|
78
98
|
// ignore directories generated by mac os x
|
|
79
99
|
return (!name.startsWith("__MACOSX") && !name.startsWith(".") && !name.startsWith(".DS_Store"));
|
|
@@ -117,9 +137,9 @@ public class CapacitorUpdater {
|
|
|
117
137
|
|
|
118
138
|
void directUpdateFinish(final BundleInfo latest) {}
|
|
119
139
|
|
|
120
|
-
void notifyListeners(final String id, final
|
|
140
|
+
void notifyListeners(final String id, final Map<String, Object> res) {}
|
|
121
141
|
|
|
122
|
-
|
|
142
|
+
public String randomString() {
|
|
123
143
|
final StringBuilder sb = new StringBuilder(10);
|
|
124
144
|
for (int i = 0; i < 10; i++) sb.append(AB.charAt(rnd.nextInt(AB.length())));
|
|
125
145
|
return sb.toString();
|
|
@@ -142,7 +162,7 @@ public class CapacitorUpdater {
|
|
|
142
162
|
ZipEntry entry;
|
|
143
163
|
while ((entry = zis.getNextEntry()) != null) {
|
|
144
164
|
if (entry.getName().contains("\\")) {
|
|
145
|
-
|
|
165
|
+
logger.error("unzip: Windows path is not supported, please use unix path as require by zip RFC: " + entry.getName());
|
|
146
166
|
this.sendStats("windows_path_fail");
|
|
147
167
|
}
|
|
148
168
|
final File file = new File(targetDirectory, entry.getName());
|
|
@@ -207,14 +227,14 @@ public class CapacitorUpdater {
|
|
|
207
227
|
|
|
208
228
|
private void observeWorkProgress(Context context, String id) {
|
|
209
229
|
if (!(context instanceof LifecycleOwner)) {
|
|
210
|
-
|
|
230
|
+
logger.error("Context is not a LifecycleOwner, cannot observe work progress");
|
|
211
231
|
return;
|
|
212
232
|
}
|
|
213
233
|
|
|
214
234
|
activity.runOnUiThread(() -> {
|
|
215
235
|
WorkManager.getInstance(context)
|
|
216
236
|
.getWorkInfosByTagLiveData(id)
|
|
217
|
-
.observe((LifecycleOwner) context, workInfos -> {
|
|
237
|
+
.observe((LifecycleOwner) context, (workInfos) -> {
|
|
218
238
|
if (workInfos == null || workInfos.isEmpty()) return;
|
|
219
239
|
|
|
220
240
|
WorkInfo workInfo = workInfos.get(0);
|
|
@@ -226,6 +246,7 @@ public class CapacitorUpdater {
|
|
|
226
246
|
notifyDownload(id, percent);
|
|
227
247
|
break;
|
|
228
248
|
case SUCCEEDED:
|
|
249
|
+
logger.info("Download succeeded: " + workInfo.getState());
|
|
229
250
|
Data outputData = workInfo.getOutputData();
|
|
230
251
|
String dest = outputData.getString(DownloadService.FILEDEST);
|
|
231
252
|
String version = outputData.getString(DownloadService.VERSION);
|
|
@@ -233,35 +254,71 @@ public class CapacitorUpdater {
|
|
|
233
254
|
String checksum = outputData.getString(DownloadService.CHECKSUM);
|
|
234
255
|
boolean isManifest = outputData.getBoolean(DownloadService.IS_MANIFEST, false);
|
|
235
256
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
io.execute(() -> {
|
|
258
|
+
boolean success = finishDownload(id, dest, version, sessionKey, checksum, true, isManifest);
|
|
259
|
+
BundleInfo resultBundle;
|
|
260
|
+
if (!success) {
|
|
261
|
+
logger.error("Finish download failed: " + version);
|
|
262
|
+
resultBundle = new BundleInfo(
|
|
263
|
+
id,
|
|
264
|
+
version,
|
|
265
|
+
BundleStatus.ERROR,
|
|
266
|
+
new Date(System.currentTimeMillis()),
|
|
267
|
+
""
|
|
268
|
+
);
|
|
269
|
+
saveBundleInfo(id, resultBundle);
|
|
270
|
+
// Cleanup download tracking
|
|
271
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, version);
|
|
272
|
+
Map<String, Object> ret = new HashMap<>();
|
|
273
|
+
ret.put("version", version);
|
|
274
|
+
ret.put("error", "finish_download_fail");
|
|
275
|
+
sendStats("finish_download_fail", version);
|
|
276
|
+
notifyListeners("downloadFailed", ret);
|
|
277
|
+
} else {
|
|
278
|
+
// Successful download - cleanup tracking
|
|
279
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, version);
|
|
280
|
+
resultBundle = getBundleInfo(id);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Complete the future if it exists
|
|
284
|
+
CompletableFuture<BundleInfo> future = downloadFutures.remove(id);
|
|
285
|
+
if (future != null) {
|
|
286
|
+
future.complete(resultBundle);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
248
289
|
break;
|
|
249
290
|
case FAILED:
|
|
250
291
|
Data failedData = workInfo.getOutputData();
|
|
251
292
|
String error = failedData.getString(DownloadService.ERROR);
|
|
293
|
+
logger.error("Download failed: " + error + " " + workInfo.getState());
|
|
252
294
|
String failedVersion = failedData.getString(DownloadService.VERSION);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
295
|
+
|
|
296
|
+
io.execute(() -> {
|
|
297
|
+
BundleInfo failedBundle = new BundleInfo(
|
|
298
|
+
id,
|
|
299
|
+
failedVersion,
|
|
300
|
+
BundleStatus.ERROR,
|
|
301
|
+
new Date(System.currentTimeMillis()),
|
|
302
|
+
""
|
|
303
|
+
);
|
|
304
|
+
saveBundleInfo(id, failedBundle);
|
|
305
|
+
// Cleanup download tracking for failed downloads
|
|
306
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, failedVersion);
|
|
307
|
+
Map<String, Object> ret = new HashMap<>();
|
|
308
|
+
ret.put("version", failedVersion);
|
|
309
|
+
if ("low_mem_fail".equals(error)) {
|
|
310
|
+
sendStats("low_mem_fail", failedVersion);
|
|
311
|
+
}
|
|
312
|
+
ret.put("error", error != null ? error : "download_fail");
|
|
313
|
+
sendStats("download_fail", failedVersion);
|
|
314
|
+
notifyListeners("downloadFailed", ret);
|
|
315
|
+
|
|
316
|
+
// Complete the future with error status
|
|
317
|
+
CompletableFuture<BundleInfo> failedFuture = downloadFutures.remove(id);
|
|
318
|
+
if (failedFuture != null) {
|
|
319
|
+
failedFuture.complete(failedBundle);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
265
322
|
break;
|
|
266
323
|
}
|
|
267
324
|
});
|
|
@@ -278,7 +335,7 @@ public class CapacitorUpdater {
|
|
|
278
335
|
final JSONArray manifest
|
|
279
336
|
) {
|
|
280
337
|
if (this.activity == null) {
|
|
281
|
-
|
|
338
|
+
logger.error("Activity is null, cannot observe work progress");
|
|
282
339
|
return;
|
|
283
340
|
}
|
|
284
341
|
observeWorkProgress(this.activity, id);
|
|
@@ -293,7 +350,18 @@ public class CapacitorUpdater {
|
|
|
293
350
|
sessionKey,
|
|
294
351
|
checksum,
|
|
295
352
|
this.publicKey,
|
|
296
|
-
manifest != null
|
|
353
|
+
manifest != null,
|
|
354
|
+
this.isEmulator(),
|
|
355
|
+
this.appId,
|
|
356
|
+
this.pluginVersion,
|
|
357
|
+
this.isProd(),
|
|
358
|
+
this.statsUrl,
|
|
359
|
+
this.deviceID,
|
|
360
|
+
this.versionBuild,
|
|
361
|
+
this.versionCode,
|
|
362
|
+
this.versionOs,
|
|
363
|
+
this.customId,
|
|
364
|
+
this.defaultChannel
|
|
297
365
|
);
|
|
298
366
|
|
|
299
367
|
if (manifest != null) {
|
|
@@ -311,6 +379,7 @@ public class CapacitorUpdater {
|
|
|
311
379
|
Boolean isManifest
|
|
312
380
|
) {
|
|
313
381
|
File downloaded = null;
|
|
382
|
+
File extractedDir = null;
|
|
314
383
|
String checksum = "";
|
|
315
384
|
|
|
316
385
|
try {
|
|
@@ -319,68 +388,94 @@ public class CapacitorUpdater {
|
|
|
319
388
|
|
|
320
389
|
if (!isManifest) {
|
|
321
390
|
String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
|
|
391
|
+
|
|
392
|
+
// If public key is present but no checksum provided, refuse installation
|
|
393
|
+
if (!this.publicKey.isEmpty() && checksumDecrypted.isEmpty()) {
|
|
394
|
+
logger.error("Public key present but no checksum provided");
|
|
395
|
+
this.sendStats("checksum_required");
|
|
396
|
+
throw new IOException("Checksum required when public key is present: " + id);
|
|
397
|
+
}
|
|
398
|
+
|
|
322
399
|
if (!sessionKey.isEmpty()) {
|
|
323
|
-
|
|
324
|
-
checksumDecrypted =
|
|
325
|
-
checksum =
|
|
400
|
+
CryptoCipher.decryptFile(downloaded, publicKey, sessionKey);
|
|
401
|
+
checksumDecrypted = CryptoCipher.decryptChecksum(checksumRes, publicKey);
|
|
402
|
+
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
403
|
+
} else {
|
|
404
|
+
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
326
405
|
}
|
|
406
|
+
CryptoCipher.logChecksumInfo("Calculated checksum", checksum);
|
|
407
|
+
CryptoCipher.logChecksumInfo("Expected checksum", checksumDecrypted);
|
|
327
408
|
if ((!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) && !checksumDecrypted.equals(checksum)) {
|
|
328
|
-
|
|
409
|
+
logger.error("Error checksum '" + checksumDecrypted + "' '" + checksum + "' '");
|
|
329
410
|
this.sendStats("checksum_fail");
|
|
330
411
|
throw new IOException("Checksum failed: " + id);
|
|
331
412
|
}
|
|
332
413
|
}
|
|
333
414
|
// Remove the decryption for manifest downloads
|
|
334
|
-
} catch (
|
|
415
|
+
} catch (Exception e) {
|
|
416
|
+
if (!isManifest) {
|
|
417
|
+
safeDelete(downloaded);
|
|
418
|
+
}
|
|
335
419
|
final Boolean res = this.delete(id);
|
|
336
420
|
if (!res) {
|
|
337
|
-
|
|
421
|
+
logger.info("Double error, cannot cleanup: " + version);
|
|
338
422
|
}
|
|
339
423
|
|
|
340
|
-
final
|
|
341
|
-
ret.put("version",
|
|
424
|
+
final Map<String, Object> ret = new HashMap<>();
|
|
425
|
+
ret.put("version", version);
|
|
342
426
|
|
|
343
|
-
|
|
344
|
-
|
|
427
|
+
CapgoUpdater.this.notifyListeners("downloadFailed", ret);
|
|
428
|
+
CapgoUpdater.this.sendStats("download_fail");
|
|
345
429
|
return false;
|
|
346
430
|
}
|
|
347
431
|
|
|
348
432
|
try {
|
|
349
433
|
if (!isManifest) {
|
|
350
|
-
|
|
434
|
+
extractedDir = this.unzip(id, downloaded, this.randomString());
|
|
351
435
|
this.notifyDownload(id, 91);
|
|
352
436
|
final String idName = bundleDirectory + "/" + id;
|
|
353
|
-
this.flattenAssets(
|
|
437
|
+
this.flattenAssets(extractedDir, idName);
|
|
354
438
|
} else {
|
|
355
439
|
this.notifyDownload(id, 91);
|
|
356
440
|
final String idName = bundleDirectory + "/" + id;
|
|
357
441
|
this.flattenAssets(downloaded, idName);
|
|
358
442
|
downloaded.delete();
|
|
359
443
|
}
|
|
360
|
-
|
|
444
|
+
// Remove old bundle info and set new one
|
|
361
445
|
this.saveBundleInfo(id, null);
|
|
362
446
|
BundleInfo next = new BundleInfo(id, version, BundleStatus.PENDING, new Date(System.currentTimeMillis()), checksum);
|
|
363
447
|
this.saveBundleInfo(id, next);
|
|
448
|
+
this.notifyDownload(id, 100);
|
|
364
449
|
|
|
365
|
-
final
|
|
366
|
-
ret.put("bundle", next.
|
|
367
|
-
|
|
450
|
+
final Map<String, Object> ret = new HashMap<>();
|
|
451
|
+
ret.put("bundle", next.toJSONMap());
|
|
452
|
+
logger.info("updateAvailable: " + ret);
|
|
453
|
+
CapgoUpdater.this.notifyListeners("updateAvailable", ret);
|
|
454
|
+
logger.info("setNext: " + setNext);
|
|
368
455
|
if (setNext) {
|
|
456
|
+
logger.info("directUpdate: " + this.directUpdate);
|
|
369
457
|
if (this.directUpdate) {
|
|
370
|
-
|
|
458
|
+
CapgoUpdater.this.directUpdateFinish(next);
|
|
371
459
|
this.directUpdate = false;
|
|
372
460
|
} else {
|
|
373
461
|
this.setNextBundle(next.getId());
|
|
374
462
|
}
|
|
375
463
|
}
|
|
376
464
|
} catch (IOException e) {
|
|
465
|
+
if (!isManifest) {
|
|
466
|
+
safeDelete(extractedDir);
|
|
467
|
+
safeDelete(downloaded);
|
|
468
|
+
}
|
|
377
469
|
e.printStackTrace();
|
|
378
|
-
final
|
|
379
|
-
ret.put("version",
|
|
380
|
-
|
|
381
|
-
|
|
470
|
+
final Map<String, Object> ret = new HashMap<>();
|
|
471
|
+
ret.put("version", version);
|
|
472
|
+
CapgoUpdater.this.notifyListeners("downloadFailed", ret);
|
|
473
|
+
CapgoUpdater.this.sendStats("download_fail");
|
|
382
474
|
return false;
|
|
383
475
|
}
|
|
476
|
+
if (!isManifest) {
|
|
477
|
+
safeDelete(downloaded);
|
|
478
|
+
}
|
|
384
479
|
return true;
|
|
385
480
|
}
|
|
386
481
|
|
|
@@ -398,9 +493,76 @@ public class CapacitorUpdater {
|
|
|
398
493
|
}
|
|
399
494
|
}
|
|
400
495
|
|
|
496
|
+
public void cleanupDeltaCache() {
|
|
497
|
+
if (this.activity == null) {
|
|
498
|
+
logger.warn("Activity is null, skipping delta cache cleanup");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
final File cacheFolder = new File(this.activity.getCacheDir(), "capgo_downloads");
|
|
502
|
+
if (!cacheFolder.exists()) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
this.deleteDirectory(cacheFolder);
|
|
507
|
+
logger.info("Cleaned up delta cache folder");
|
|
508
|
+
} catch (IOException e) {
|
|
509
|
+
logger.error("Failed to cleanup delta cache: " + e.getMessage());
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
public void cleanupDownloadDirectories(final Set<String> allowedIds) {
|
|
514
|
+
if (this.documentsDir == null) {
|
|
515
|
+
logger.warn("Documents directory is null, skipping download cleanup");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
final File bundleRoot = new File(this.documentsDir, bundleDirectory);
|
|
520
|
+
if (!bundleRoot.exists() || !bundleRoot.isDirectory()) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
final File[] entries = bundleRoot.listFiles();
|
|
525
|
+
if (entries != null) {
|
|
526
|
+
for (final File entry : entries) {
|
|
527
|
+
if (!entry.isDirectory()) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
final String id = entry.getName();
|
|
532
|
+
|
|
533
|
+
if (allowedIds != null && allowedIds.contains(id)) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
this.deleteDirectory(entry);
|
|
539
|
+
this.removeBundleInfo(id);
|
|
540
|
+
logger.info("Deleted orphan bundle directory: " + id);
|
|
541
|
+
} catch (IOException e) {
|
|
542
|
+
logger.error("Failed to delete orphan bundle directory: " + id + " " + e.getMessage());
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private void safeDelete(final File target) {
|
|
549
|
+
if (target == null || !target.exists()) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
if (target.isDirectory()) {
|
|
554
|
+
this.deleteDirectory(target);
|
|
555
|
+
} else if (!target.delete()) {
|
|
556
|
+
logger.warn("Failed to delete file: " + target.getAbsolutePath());
|
|
557
|
+
}
|
|
558
|
+
} catch (IOException cleanupError) {
|
|
559
|
+
logger.warn("Cleanup failed for " + target.getAbsolutePath() + ": " + cleanupError.getMessage());
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
401
563
|
private void setCurrentBundle(final File bundle) {
|
|
402
|
-
this.editor.putString(
|
|
403
|
-
|
|
564
|
+
this.editor.putString(this.CAP_SERVER_PATH, bundle.getPath());
|
|
565
|
+
logger.info("Current bundle set to: " + bundle);
|
|
404
566
|
this.editor.commit();
|
|
405
567
|
}
|
|
406
568
|
|
|
@@ -413,13 +575,21 @@ public class CapacitorUpdater {
|
|
|
413
575
|
) {
|
|
414
576
|
final String id = this.randomString();
|
|
415
577
|
|
|
416
|
-
// Check if version is already downloading
|
|
417
|
-
if (this.activity != null && DownloadWorkerManager.isVersionDownloading(version)) {
|
|
418
|
-
|
|
419
|
-
|
|
578
|
+
// Check if version is already downloading, but allow retry if previous download failed
|
|
579
|
+
if (this.activity != null && DownloadWorkerManager.isVersionDownloading(this.activity, version)) {
|
|
580
|
+
// Check if there's an existing bundle with error status that we can retry
|
|
581
|
+
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
582
|
+
if (existingBundle != null && existingBundle.isErrorStatus()) {
|
|
583
|
+
// Cancel the failed download and allow retry
|
|
584
|
+
DownloadWorkerManager.cancelVersionDownload(this.activity, version);
|
|
585
|
+
logger.info("Retrying failed download for version: " + version);
|
|
586
|
+
} else {
|
|
587
|
+
logger.info("Version already downloading: " + version);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
420
590
|
}
|
|
421
591
|
|
|
422
|
-
|
|
592
|
+
saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
|
|
423
593
|
this.notifyDownload(id, 0);
|
|
424
594
|
this.notifyDownload(id, 5);
|
|
425
595
|
|
|
@@ -427,42 +597,43 @@ public class CapacitorUpdater {
|
|
|
427
597
|
}
|
|
428
598
|
|
|
429
599
|
public BundleInfo download(final String url, final String version, final String sessionKey, final String checksum) throws IOException {
|
|
600
|
+
// Check for existing bundle with same version and clean up if in error state
|
|
601
|
+
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
602
|
+
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted())) {
|
|
603
|
+
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
604
|
+
this.delete(existingBundle.getId(), true);
|
|
605
|
+
}
|
|
606
|
+
|
|
430
607
|
final String id = this.randomString();
|
|
431
|
-
|
|
608
|
+
saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
|
|
432
609
|
this.notifyDownload(id, 0);
|
|
433
610
|
this.notifyDownload(id, 5);
|
|
434
611
|
final String dest = this.randomString();
|
|
435
612
|
|
|
436
|
-
//
|
|
613
|
+
// Create a CompletableFuture to track download completion
|
|
614
|
+
CompletableFuture<BundleInfo> downloadFuture = new CompletableFuture<>();
|
|
615
|
+
downloadFutures.put(id, downloadFuture);
|
|
616
|
+
|
|
617
|
+
// Start the download
|
|
437
618
|
this.download(id, url, dest, version, sessionKey, checksum, null);
|
|
438
619
|
|
|
439
|
-
// Wait for completion
|
|
620
|
+
// Wait for completion without timeout
|
|
440
621
|
try {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (workInfos != null && !workInfos.isEmpty()) {
|
|
446
|
-
WorkInfo workInfo = workInfos.get(0);
|
|
447
|
-
while (!workInfo.getState().isFinished()) {
|
|
448
|
-
Thread.sleep(100);
|
|
449
|
-
workInfos = Futures.getChecked(WorkManager.getInstance(activity).getWorkInfosByTag(id), IOException.class);
|
|
450
|
-
if (workInfos != null && !workInfos.isEmpty()) {
|
|
451
|
-
workInfo = workInfos.get(0);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (workInfo.getState() != WorkInfo.State.SUCCEEDED) {
|
|
456
|
-
Data outputData = workInfo.getOutputData();
|
|
457
|
-
String error = outputData.getString(DownloadService.ERROR);
|
|
458
|
-
throw new IOException(error != null ? error : "Download failed: " + workInfo.getState());
|
|
459
|
-
}
|
|
622
|
+
BundleInfo result = downloadFuture.get();
|
|
623
|
+
if (result.isErrorStatus()) {
|
|
624
|
+
throw new IOException("Download failed with status: " + result.getStatus());
|
|
460
625
|
}
|
|
461
|
-
return
|
|
626
|
+
return result;
|
|
462
627
|
} catch (Exception e) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
628
|
+
// Clean up on failure
|
|
629
|
+
downloadFutures.remove(id);
|
|
630
|
+
logger.error("Error waiting for download: " + e.getMessage());
|
|
631
|
+
BundleInfo errorBundle = new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "");
|
|
632
|
+
saveBundleInfo(id, errorBundle);
|
|
633
|
+
if (e instanceof IOException) {
|
|
634
|
+
throw (IOException) e;
|
|
635
|
+
}
|
|
636
|
+
throw new IOException("Error waiting for download: " + e.getMessage(), e);
|
|
466
637
|
}
|
|
467
638
|
}
|
|
468
639
|
|
|
@@ -470,14 +641,14 @@ public class CapacitorUpdater {
|
|
|
470
641
|
if (!rawList) {
|
|
471
642
|
final List<BundleInfo> res = new ArrayList<>();
|
|
472
643
|
final File destHot = new File(this.documentsDir, bundleDirectory);
|
|
473
|
-
|
|
644
|
+
logger.debug("list File : " + destHot.getPath());
|
|
474
645
|
if (destHot.exists()) {
|
|
475
646
|
for (final File i : Objects.requireNonNull(destHot.listFiles())) {
|
|
476
647
|
final String id = i.getName();
|
|
477
648
|
res.add(this.getBundleInfo(id));
|
|
478
649
|
}
|
|
479
650
|
} else {
|
|
480
|
-
|
|
651
|
+
logger.info("No versions available to list" + destHot);
|
|
481
652
|
}
|
|
482
653
|
return res;
|
|
483
654
|
} else {
|
|
@@ -496,12 +667,12 @@ public class CapacitorUpdater {
|
|
|
496
667
|
public Boolean delete(final String id, final Boolean removeInfo) throws IOException {
|
|
497
668
|
final BundleInfo deleted = this.getBundleInfo(id);
|
|
498
669
|
if (deleted.isBuiltin() || this.getCurrentBundleId().equals(id)) {
|
|
499
|
-
|
|
670
|
+
logger.error("Cannot delete " + id);
|
|
500
671
|
return false;
|
|
501
672
|
}
|
|
502
673
|
final BundleInfo next = this.getNextBundle();
|
|
503
674
|
if (next != null && !next.isDeleted() && !next.isErrorStatus() && next.getId().equals(id)) {
|
|
504
|
-
|
|
675
|
+
logger.error("Cannot delete the next bundle" + id);
|
|
505
676
|
return false;
|
|
506
677
|
}
|
|
507
678
|
// Cancel download for this version if active
|
|
@@ -518,7 +689,7 @@ public class CapacitorUpdater {
|
|
|
518
689
|
}
|
|
519
690
|
return true;
|
|
520
691
|
}
|
|
521
|
-
|
|
692
|
+
logger.error("bundle removed: " + deleted.getVersionName());
|
|
522
693
|
// perhaps we did not find the bundle in the files, but if the user requested a delete, we delete
|
|
523
694
|
if (removeInfo) {
|
|
524
695
|
this.removeBundleInfo(id);
|
|
@@ -532,7 +703,7 @@ public class CapacitorUpdater {
|
|
|
532
703
|
return this.delete(id, true);
|
|
533
704
|
} catch (IOException e) {
|
|
534
705
|
e.printStackTrace();
|
|
535
|
-
|
|
706
|
+
logger.info("Failed to delete bundle (" + id + ")" + "\nError:\n" + e.toString());
|
|
536
707
|
return false;
|
|
537
708
|
}
|
|
538
709
|
}
|
|
@@ -558,7 +729,7 @@ public class CapacitorUpdater {
|
|
|
558
729
|
return true;
|
|
559
730
|
}
|
|
560
731
|
final File bundle = this.getBundleDirectory(id);
|
|
561
|
-
|
|
732
|
+
logger.info("Setting next active bundle: " + id);
|
|
562
733
|
if (this.bundleExists(id)) {
|
|
563
734
|
var currentBundleName = this.getCurrentBundle().getVersionName();
|
|
564
735
|
this.setCurrentBundle(bundle);
|
|
@@ -574,7 +745,7 @@ public class CapacitorUpdater {
|
|
|
574
745
|
public void autoReset() {
|
|
575
746
|
final BundleInfo currentBundle = this.getCurrentBundle();
|
|
576
747
|
if (!currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())) {
|
|
577
|
-
|
|
748
|
+
logger.info("Folder at bundle path does not exist. Triggering reset.");
|
|
578
749
|
this.reset();
|
|
579
750
|
}
|
|
580
751
|
}
|
|
@@ -586,12 +757,17 @@ public class CapacitorUpdater {
|
|
|
586
757
|
public void setSuccess(final BundleInfo bundle, Boolean autoDeletePrevious) {
|
|
587
758
|
this.setBundleStatus(bundle.getId(), BundleStatus.SUCCESS);
|
|
588
759
|
final BundleInfo fallback = this.getFallbackBundle();
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
760
|
+
logger.debug("Fallback bundle is: " + fallback);
|
|
761
|
+
logger.info("Version successfully loaded: " + bundle.getVersionName());
|
|
762
|
+
// Only attempt to delete when the fallback is a different bundle than the
|
|
763
|
+
// currently loaded one. Otherwise we spam logs with "Cannot delete <id>"
|
|
764
|
+
// because delete() protects the current bundle from removal.
|
|
765
|
+
if (autoDeletePrevious && !fallback.isBuiltin() && fallback.getId() != null && !fallback.getId().equals(bundle.getId())) {
|
|
592
766
|
final Boolean res = this.delete(fallback.getId());
|
|
593
767
|
if (res) {
|
|
594
|
-
|
|
768
|
+
logger.info("Deleted previous bundle: " + fallback.getVersionName());
|
|
769
|
+
} else {
|
|
770
|
+
logger.debug("Skip deleting previous bundle (same as current or protected): " + fallback.getId());
|
|
595
771
|
}
|
|
596
772
|
}
|
|
597
773
|
this.setFallbackBundle(bundle);
|
|
@@ -602,7 +778,7 @@ public class CapacitorUpdater {
|
|
|
602
778
|
}
|
|
603
779
|
|
|
604
780
|
public void reset(final boolean internal) {
|
|
605
|
-
|
|
781
|
+
logger.debug("reset: " + internal);
|
|
606
782
|
var currentBundleName = this.getCurrentBundle().getVersionName();
|
|
607
783
|
this.setCurrentBundle(new File("public"));
|
|
608
784
|
this.setFallbackBundle(null);
|
|
@@ -626,26 +802,79 @@ public class CapacitorUpdater {
|
|
|
626
802
|
json.put("version_code", this.versionCode);
|
|
627
803
|
json.put("version_os", this.versionOs);
|
|
628
804
|
json.put("version_name", this.getCurrentBundle().getVersionName());
|
|
629
|
-
json.put("plugin_version", this.
|
|
805
|
+
json.put("plugin_version", this.pluginVersion);
|
|
630
806
|
json.put("is_emulator", this.isEmulator());
|
|
631
807
|
json.put("is_prod", this.isProd());
|
|
632
808
|
json.put("defaultChannel", this.defaultChannel);
|
|
633
809
|
return json;
|
|
634
810
|
}
|
|
635
811
|
|
|
812
|
+
/**
|
|
813
|
+
* Check if a 429 (Too Many Requests) response was received and set the flag
|
|
814
|
+
*/
|
|
815
|
+
private boolean checkAndHandleRateLimitResponse(Response response) {
|
|
816
|
+
if (response.code() == 429) {
|
|
817
|
+
// Send a statistic about the rate limit BEFORE setting the flag
|
|
818
|
+
// Only send once to prevent infinite loop if the stat request itself gets rate limited
|
|
819
|
+
if (!rateLimitExceeded && !rateLimitStatisticSent) {
|
|
820
|
+
rateLimitStatisticSent = true;
|
|
821
|
+
sendRateLimitStatistic();
|
|
822
|
+
}
|
|
823
|
+
rateLimitExceeded = true;
|
|
824
|
+
logger.warn("Rate limit exceeded (429). Stopping all stats and channel requests until app restart.");
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Send a synchronous statistic about rate limiting
|
|
832
|
+
*/
|
|
833
|
+
private void sendRateLimitStatistic() {
|
|
834
|
+
String statsUrl = this.statsUrl;
|
|
835
|
+
if (statsUrl == null || statsUrl.isEmpty()) {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
try {
|
|
840
|
+
BundleInfo current = this.getCurrentBundle();
|
|
841
|
+
JSONObject json = this.createInfoObject();
|
|
842
|
+
json.put("version_name", current.getVersionName());
|
|
843
|
+
json.put("old_version_name", "");
|
|
844
|
+
json.put("action", "rate_limit_reached");
|
|
845
|
+
|
|
846
|
+
Request request = new Request.Builder()
|
|
847
|
+
.url(statsUrl)
|
|
848
|
+
.post(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
849
|
+
.build();
|
|
850
|
+
|
|
851
|
+
// Send synchronously to ensure it goes out before the flag is set
|
|
852
|
+
// User-Agent header is automatically added by DownloadService.sharedClient interceptor
|
|
853
|
+
try (Response response = DownloadService.sharedClient.newCall(request).execute()) {
|
|
854
|
+
if (response.isSuccessful()) {
|
|
855
|
+
logger.info("Rate limit statistic sent");
|
|
856
|
+
} else {
|
|
857
|
+
logger.error("Error sending rate limit statistic: " + response.code());
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
} catch (final Exception e) {
|
|
861
|
+
logger.error("Failed to send rate limit statistic: " + e.getMessage());
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
636
865
|
private void makeJsonRequest(String url, JSONObject jsonBody, Callback callback) {
|
|
637
866
|
MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
|
638
867
|
RequestBody body = RequestBody.create(jsonBody.toString(), JSON);
|
|
639
868
|
|
|
640
869
|
Request request = new Request.Builder().url(url).post(body).build();
|
|
641
870
|
|
|
642
|
-
|
|
871
|
+
DownloadService.sharedClient
|
|
643
872
|
.newCall(request)
|
|
644
873
|
.enqueue(
|
|
645
874
|
new okhttp3.Callback() {
|
|
646
875
|
@Override
|
|
647
876
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
648
|
-
|
|
877
|
+
Map<String, Object> retError = new HashMap<>();
|
|
649
878
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
650
879
|
retError.put("error", "network_error");
|
|
651
880
|
callback.callback(retError);
|
|
@@ -654,10 +883,22 @@ public class CapacitorUpdater {
|
|
|
654
883
|
@Override
|
|
655
884
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
656
885
|
try (ResponseBody responseBody = response.body()) {
|
|
886
|
+
final int statusCode = response.code();
|
|
887
|
+
// Check for 429 rate limit
|
|
888
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
889
|
+
Map<String, Object> retError = new HashMap<>();
|
|
890
|
+
retError.put("message", "Rate limit exceeded");
|
|
891
|
+
retError.put("error", "rate_limit_exceeded");
|
|
892
|
+
retError.put("statusCode", statusCode);
|
|
893
|
+
callback.callback(retError);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
657
897
|
if (!response.isSuccessful()) {
|
|
658
|
-
|
|
898
|
+
Map<String, Object> retError = new HashMap<>();
|
|
659
899
|
retError.put("message", "Server error: " + response.code());
|
|
660
900
|
retError.put("error", "response_error");
|
|
901
|
+
retError.put("statusCode", statusCode);
|
|
661
902
|
callback.callback(retError);
|
|
662
903
|
return;
|
|
663
904
|
}
|
|
@@ -665,7 +906,23 @@ public class CapacitorUpdater {
|
|
|
665
906
|
assert responseBody != null;
|
|
666
907
|
String responseData = responseBody.string();
|
|
667
908
|
JSONObject jsonResponse = new JSONObject(responseData);
|
|
668
|
-
|
|
909
|
+
|
|
910
|
+
// Check for server-side errors first
|
|
911
|
+
if (jsonResponse.has("error")) {
|
|
912
|
+
Map<String, Object> retError = new HashMap<>();
|
|
913
|
+
retError.put("error", jsonResponse.getString("error"));
|
|
914
|
+
if (jsonResponse.has("message")) {
|
|
915
|
+
retError.put("message", jsonResponse.getString("message"));
|
|
916
|
+
} else {
|
|
917
|
+
retError.put("message", "server did not provide a message");
|
|
918
|
+
}
|
|
919
|
+
retError.put("statusCode", statusCode);
|
|
920
|
+
callback.callback(retError);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
Map<String, Object> ret = new HashMap<>();
|
|
925
|
+
ret.put("statusCode", statusCode);
|
|
669
926
|
|
|
670
927
|
Iterator<String> keys = jsonResponse.keys();
|
|
671
928
|
while (keys.hasNext()) {
|
|
@@ -680,7 +937,7 @@ public class CapacitorUpdater {
|
|
|
680
937
|
}
|
|
681
938
|
callback.callback(ret);
|
|
682
939
|
} catch (JSONException e) {
|
|
683
|
-
|
|
940
|
+
Map<String, Object> retError = new HashMap<>();
|
|
684
941
|
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
685
942
|
retError.put("error", "parse_error");
|
|
686
943
|
callback.callback(retError);
|
|
@@ -698,35 +955,126 @@ public class CapacitorUpdater {
|
|
|
698
955
|
json.put("defaultChannel", channel);
|
|
699
956
|
}
|
|
700
957
|
} catch (JSONException e) {
|
|
701
|
-
|
|
702
|
-
final
|
|
958
|
+
logger.error("Error getLatest JSONException " + e.getMessage());
|
|
959
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
703
960
|
retError.put("message", "Cannot get info: " + e);
|
|
704
961
|
retError.put("error", "json_error");
|
|
705
962
|
callback.callback(retError);
|
|
706
963
|
return;
|
|
707
964
|
}
|
|
708
965
|
|
|
709
|
-
|
|
966
|
+
logger.info("Auto-update parameters: " + json);
|
|
710
967
|
|
|
711
968
|
makeJsonRequest(updateUrl, json, callback);
|
|
712
969
|
}
|
|
713
970
|
|
|
714
|
-
public void unsetChannel(
|
|
971
|
+
public void unsetChannel(
|
|
972
|
+
final SharedPreferences.Editor editor,
|
|
973
|
+
final String defaultChannelKey,
|
|
974
|
+
final String configDefaultChannel,
|
|
975
|
+
final Callback callback
|
|
976
|
+
) {
|
|
977
|
+
// Clear persisted defaultChannel and revert to config value
|
|
978
|
+
editor.remove(defaultChannelKey);
|
|
979
|
+
editor.apply();
|
|
980
|
+
this.defaultChannel = configDefaultChannel;
|
|
981
|
+
logger.info("Persisted defaultChannel cleared, reverted to config value: " + configDefaultChannel);
|
|
982
|
+
|
|
983
|
+
Map<String, Object> ret = new HashMap<>();
|
|
984
|
+
ret.put("status", "ok");
|
|
985
|
+
ret.put("message", "Channel override removed");
|
|
986
|
+
callback.callback(ret);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
public void setChannel(
|
|
990
|
+
final String channel,
|
|
991
|
+
final SharedPreferences.Editor editor,
|
|
992
|
+
final String defaultChannelKey,
|
|
993
|
+
final boolean allowSetDefaultChannel,
|
|
994
|
+
final Callback callback
|
|
995
|
+
) {
|
|
996
|
+
// Check if setting defaultChannel is allowed
|
|
997
|
+
if (!allowSetDefaultChannel) {
|
|
998
|
+
logger.error("setChannel is disabled by allowSetDefaultChannel config");
|
|
999
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1000
|
+
retError.put("message", "setChannel is disabled by configuration");
|
|
1001
|
+
retError.put("error", "disabled_by_config");
|
|
1002
|
+
callback.callback(retError);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Check if rate limit was exceeded
|
|
1007
|
+
if (rateLimitExceeded) {
|
|
1008
|
+
logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.");
|
|
1009
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1010
|
+
retError.put("message", "Rate limit exceeded");
|
|
1011
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1012
|
+
callback.callback(retError);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
715
1016
|
String channelUrl = this.channelUrl;
|
|
716
1017
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
717
|
-
|
|
718
|
-
final
|
|
1018
|
+
logger.error("Channel URL is not set");
|
|
1019
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
719
1020
|
retError.put("message", "channelUrl missing");
|
|
720
1021
|
retError.put("error", "missing_config");
|
|
721
1022
|
callback.callback(retError);
|
|
722
1023
|
return;
|
|
723
1024
|
}
|
|
724
1025
|
JSONObject json;
|
|
1026
|
+
try {
|
|
1027
|
+
json = this.createInfoObject();
|
|
1028
|
+
json.put("channel", channel);
|
|
1029
|
+
} catch (JSONException e) {
|
|
1030
|
+
logger.error("Error setChannel JSONException " + e.getMessage());
|
|
1031
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1032
|
+
retError.put("message", "Cannot get info: " + e);
|
|
1033
|
+
retError.put("error", "json_error");
|
|
1034
|
+
callback.callback(retError);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
makeJsonRequest(channelUrl, json, (res) -> {
|
|
1039
|
+
if (res.containsKey("error")) {
|
|
1040
|
+
callback.callback(res);
|
|
1041
|
+
} else {
|
|
1042
|
+
// Success - persist defaultChannel
|
|
1043
|
+
this.defaultChannel = channel;
|
|
1044
|
+
editor.putString(defaultChannelKey, channel);
|
|
1045
|
+
editor.apply();
|
|
1046
|
+
logger.info("defaultChannel persisted locally: " + channel);
|
|
1047
|
+
callback.callback(res);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
public void getChannel(final Callback callback) {
|
|
1053
|
+
// Check if rate limit was exceeded
|
|
1054
|
+
if (rateLimitExceeded) {
|
|
1055
|
+
logger.debug("Skipping getChannel due to rate limit (429). Requests will resume after app restart.");
|
|
1056
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1057
|
+
retError.put("message", "Rate limit exceeded");
|
|
1058
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1059
|
+
callback.callback(retError);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
String channelUrl = this.channelUrl;
|
|
1064
|
+
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
1065
|
+
logger.error("Channel URL is not set");
|
|
1066
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1067
|
+
retError.put("message", "Channel URL is not set");
|
|
1068
|
+
retError.put("error", "missing_config");
|
|
1069
|
+
callback.callback(retError);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
JSONObject json;
|
|
725
1073
|
try {
|
|
726
1074
|
json = this.createInfoObject();
|
|
727
1075
|
} catch (JSONException e) {
|
|
728
|
-
|
|
729
|
-
final
|
|
1076
|
+
logger.error("Error getChannel JSONException " + e.getMessage());
|
|
1077
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
730
1078
|
retError.put("message", "Cannot get info: " + e);
|
|
731
1079
|
retError.put("error", "json_error");
|
|
732
1080
|
callback.callback(retError);
|
|
@@ -735,16 +1083,16 @@ public class CapacitorUpdater {
|
|
|
735
1083
|
|
|
736
1084
|
Request request = new Request.Builder()
|
|
737
1085
|
.url(channelUrl)
|
|
738
|
-
.
|
|
1086
|
+
.put(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
739
1087
|
.build();
|
|
740
1088
|
|
|
741
|
-
|
|
1089
|
+
DownloadService.sharedClient
|
|
742
1090
|
.newCall(request)
|
|
743
1091
|
.enqueue(
|
|
744
1092
|
new okhttp3.Callback() {
|
|
745
1093
|
@Override
|
|
746
1094
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
747
|
-
|
|
1095
|
+
Map<String, Object> retError = new HashMap<>();
|
|
748
1096
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
749
1097
|
retError.put("error", "network_error");
|
|
750
1098
|
callback.callback(retError);
|
|
@@ -753,8 +1101,30 @@ public class CapacitorUpdater {
|
|
|
753
1101
|
@Override
|
|
754
1102
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
755
1103
|
try (ResponseBody responseBody = response.body()) {
|
|
1104
|
+
// Check for 429 rate limit
|
|
1105
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1106
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1107
|
+
retError.put("message", "Rate limit exceeded");
|
|
1108
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1109
|
+
callback.callback(retError);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (response.code() == 400) {
|
|
1114
|
+
assert responseBody != null;
|
|
1115
|
+
String data = responseBody.string();
|
|
1116
|
+
if (data.contains("channel_not_found") && !defaultChannel.isEmpty()) {
|
|
1117
|
+
Map<String, Object> ret = new HashMap<>();
|
|
1118
|
+
ret.put("channel", defaultChannel);
|
|
1119
|
+
ret.put("status", "default");
|
|
1120
|
+
logger.info("Channel get to \"" + ret);
|
|
1121
|
+
callback.callback(ret);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
756
1126
|
if (!response.isSuccessful()) {
|
|
757
|
-
|
|
1127
|
+
Map<String, Object> retError = new HashMap<>();
|
|
758
1128
|
retError.put("message", "Server error: " + response.code());
|
|
759
1129
|
retError.put("error", "response_error");
|
|
760
1130
|
callback.callback(retError);
|
|
@@ -764,7 +1134,21 @@ public class CapacitorUpdater {
|
|
|
764
1134
|
assert responseBody != null;
|
|
765
1135
|
String responseData = responseBody.string();
|
|
766
1136
|
JSONObject jsonResponse = new JSONObject(responseData);
|
|
767
|
-
|
|
1137
|
+
|
|
1138
|
+
// Check for server-side errors first
|
|
1139
|
+
if (jsonResponse.has("error")) {
|
|
1140
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1141
|
+
retError.put("error", jsonResponse.getString("error"));
|
|
1142
|
+
if (jsonResponse.has("message")) {
|
|
1143
|
+
retError.put("message", jsonResponse.getString("message"));
|
|
1144
|
+
} else {
|
|
1145
|
+
retError.put("message", "server did not provide a message");
|
|
1146
|
+
}
|
|
1147
|
+
callback.callback(retError);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
Map<String, Object> ret = new HashMap<>();
|
|
768
1152
|
|
|
769
1153
|
Iterator<String> keys = jsonResponse.keys();
|
|
770
1154
|
while (keys.hasNext()) {
|
|
@@ -773,10 +1157,10 @@ public class CapacitorUpdater {
|
|
|
773
1157
|
ret.put(key, jsonResponse.get(key));
|
|
774
1158
|
}
|
|
775
1159
|
}
|
|
776
|
-
|
|
1160
|
+
logger.info("Channel get to \"" + ret);
|
|
777
1161
|
callback.callback(ret);
|
|
778
1162
|
} catch (JSONException e) {
|
|
779
|
-
|
|
1163
|
+
Map<String, Object> retError = new HashMap<>();
|
|
780
1164
|
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
781
1165
|
retError.put("error", "parse_error");
|
|
782
1166
|
callback.callback(retError);
|
|
@@ -786,66 +1170,63 @@ public class CapacitorUpdater {
|
|
|
786
1170
|
);
|
|
787
1171
|
}
|
|
788
1172
|
|
|
789
|
-
public void
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
final
|
|
794
|
-
retError.put("message", "
|
|
795
|
-
retError.put("error", "
|
|
796
|
-
callback.callback(retError);
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
JSONObject json;
|
|
800
|
-
try {
|
|
801
|
-
json = this.createInfoObject();
|
|
802
|
-
json.put("channel", channel);
|
|
803
|
-
} catch (JSONException e) {
|
|
804
|
-
Log.e(TAG, "Error setChannel JSONException", e);
|
|
805
|
-
final JSObject retError = new JSObject();
|
|
806
|
-
retError.put("message", "Cannot get info: " + e);
|
|
807
|
-
retError.put("error", "json_error");
|
|
1173
|
+
public void listChannels(final Callback callback) {
|
|
1174
|
+
// Check if rate limit was exceeded
|
|
1175
|
+
if (rateLimitExceeded) {
|
|
1176
|
+
logger.debug("Skipping listChannels due to rate limit (429). Requests will resume after app restart.");
|
|
1177
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1178
|
+
retError.put("message", "Rate limit exceeded");
|
|
1179
|
+
retError.put("error", "rate_limit_exceeded");
|
|
808
1180
|
callback.callback(retError);
|
|
809
1181
|
return;
|
|
810
1182
|
}
|
|
811
1183
|
|
|
812
|
-
makeJsonRequest(channelUrl, json, callback);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
public void getChannel(final Callback callback) {
|
|
816
1184
|
String channelUrl = this.channelUrl;
|
|
817
1185
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
818
|
-
|
|
819
|
-
final
|
|
1186
|
+
logger.error("Channel URL is not set");
|
|
1187
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
820
1188
|
retError.put("message", "Channel URL is not set");
|
|
821
1189
|
retError.put("error", "missing_config");
|
|
822
1190
|
callback.callback(retError);
|
|
823
1191
|
return;
|
|
824
1192
|
}
|
|
1193
|
+
|
|
825
1194
|
JSONObject json;
|
|
826
1195
|
try {
|
|
827
1196
|
json = this.createInfoObject();
|
|
828
1197
|
} catch (JSONException e) {
|
|
829
|
-
|
|
830
|
-
final
|
|
1198
|
+
logger.error("Error creating info object: " + e.getMessage());
|
|
1199
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
831
1200
|
retError.put("message", "Cannot get info: " + e);
|
|
832
1201
|
retError.put("error", "json_error");
|
|
833
1202
|
callback.callback(retError);
|
|
834
1203
|
return;
|
|
835
1204
|
}
|
|
836
1205
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
.
|
|
1206
|
+
// Build URL with query parameters from JSON
|
|
1207
|
+
HttpUrl.Builder urlBuilder = HttpUrl.parse(channelUrl).newBuilder();
|
|
1208
|
+
try {
|
|
1209
|
+
Iterator<String> keys = json.keys();
|
|
1210
|
+
while (keys.hasNext()) {
|
|
1211
|
+
String key = keys.next();
|
|
1212
|
+
Object value = json.get(key);
|
|
1213
|
+
if (value != null) {
|
|
1214
|
+
urlBuilder.addQueryParameter(key, value.toString());
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
} catch (JSONException e) {
|
|
1218
|
+
logger.error("Error adding query parameters: " + e.getMessage());
|
|
1219
|
+
}
|
|
841
1220
|
|
|
842
|
-
|
|
1221
|
+
Request request = new Request.Builder().url(urlBuilder.build()).get().build();
|
|
1222
|
+
|
|
1223
|
+
DownloadService.sharedClient
|
|
843
1224
|
.newCall(request)
|
|
844
1225
|
.enqueue(
|
|
845
1226
|
new okhttp3.Callback() {
|
|
846
1227
|
@Override
|
|
847
1228
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
848
|
-
|
|
1229
|
+
Map<String, Object> retError = new HashMap<>();
|
|
849
1230
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
850
1231
|
retError.put("error", "network_error");
|
|
851
1232
|
callback.callback(retError);
|
|
@@ -854,21 +1235,17 @@ public class CapacitorUpdater {
|
|
|
854
1235
|
@Override
|
|
855
1236
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
856
1237
|
try (ResponseBody responseBody = response.body()) {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
String
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
Log.i(TAG, "Channel get to \"" + ret);
|
|
865
|
-
callback.callback(ret);
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
1238
|
+
// Check for 429 rate limit
|
|
1239
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1240
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1241
|
+
retError.put("message", "Rate limit exceeded");
|
|
1242
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1243
|
+
callback.callback(retError);
|
|
1244
|
+
return;
|
|
868
1245
|
}
|
|
869
1246
|
|
|
870
1247
|
if (!response.isSuccessful()) {
|
|
871
|
-
|
|
1248
|
+
Map<String, Object> retError = new HashMap<>();
|
|
872
1249
|
retError.put("message", "Server error: " + response.code());
|
|
873
1250
|
retError.put("error", "response_error");
|
|
874
1251
|
callback.callback(retError);
|
|
@@ -876,24 +1253,57 @@ public class CapacitorUpdater {
|
|
|
876
1253
|
}
|
|
877
1254
|
|
|
878
1255
|
assert responseBody != null;
|
|
879
|
-
String
|
|
880
|
-
|
|
881
|
-
|
|
1256
|
+
String data = responseBody.string();
|
|
1257
|
+
|
|
1258
|
+
try {
|
|
1259
|
+
Map<String, Object> ret = new HashMap<>();
|
|
1260
|
+
|
|
1261
|
+
try {
|
|
1262
|
+
// Try to parse as direct array first
|
|
1263
|
+
JSONArray channelsJson = new JSONArray(data);
|
|
1264
|
+
List<Map<String, Object>> channelsList = new ArrayList<>();
|
|
1265
|
+
|
|
1266
|
+
for (int i = 0; i < channelsJson.length(); i++) {
|
|
1267
|
+
JSONObject channelJson = channelsJson.getJSONObject(i);
|
|
1268
|
+
Map<String, Object> channel = new HashMap<>();
|
|
1269
|
+
channel.put("id", channelJson.optString("id", ""));
|
|
1270
|
+
channel.put("name", channelJson.optString("name", ""));
|
|
1271
|
+
channel.put("public", channelJson.optBoolean("public", false));
|
|
1272
|
+
channel.put("allow_self_set", channelJson.optBoolean("allow_self_set", false));
|
|
1273
|
+
channelsList.add(channel);
|
|
1274
|
+
}
|
|
882
1275
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1276
|
+
// Wrap in channels object for JS API
|
|
1277
|
+
ret.put("channels", channelsList);
|
|
1278
|
+
|
|
1279
|
+
logger.info("Channels listed successfully");
|
|
1280
|
+
callback.callback(ret);
|
|
1281
|
+
} catch (JSONException arrayException) {
|
|
1282
|
+
// If not an array, try to parse as error object
|
|
1283
|
+
try {
|
|
1284
|
+
JSONObject json = new JSONObject(data);
|
|
1285
|
+
if (json.has("error")) {
|
|
1286
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1287
|
+
retError.put("error", json.getString("error"));
|
|
1288
|
+
if (json.has("message")) {
|
|
1289
|
+
retError.put("message", json.getString("message"));
|
|
1290
|
+
} else {
|
|
1291
|
+
retError.put("message", "server did not provide a message");
|
|
1292
|
+
}
|
|
1293
|
+
callback.callback(retError);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
} catch (JSONException objException) {
|
|
1297
|
+
// If neither array nor object, throw parse error
|
|
1298
|
+
throw arrayException;
|
|
1299
|
+
}
|
|
888
1300
|
}
|
|
1301
|
+
} catch (JSONException e) {
|
|
1302
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1303
|
+
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
1304
|
+
retError.put("error", "parse_error");
|
|
1305
|
+
callback.callback(retError);
|
|
889
1306
|
}
|
|
890
|
-
Log.i(TAG, "Channel get to \"" + ret);
|
|
891
|
-
callback.callback(ret);
|
|
892
|
-
} catch (JSONException e) {
|
|
893
|
-
JSObject retError = new JSObject();
|
|
894
|
-
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
895
|
-
retError.put("error", "parse_error");
|
|
896
|
-
callback.callback(retError);
|
|
897
1307
|
}
|
|
898
1308
|
}
|
|
899
1309
|
}
|
|
@@ -909,6 +1319,12 @@ public class CapacitorUpdater {
|
|
|
909
1319
|
}
|
|
910
1320
|
|
|
911
1321
|
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
1322
|
+
// Check if rate limit was exceeded
|
|
1323
|
+
if (rateLimitExceeded) {
|
|
1324
|
+
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.");
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
912
1328
|
String statsUrl = this.statsUrl;
|
|
913
1329
|
if (statsUrl == null || statsUrl.isEmpty()) {
|
|
914
1330
|
return;
|
|
@@ -920,7 +1336,7 @@ public class CapacitorUpdater {
|
|
|
920
1336
|
json.put("old_version_name", oldVersionName);
|
|
921
1337
|
json.put("action", action);
|
|
922
1338
|
} catch (JSONException e) {
|
|
923
|
-
|
|
1339
|
+
logger.error("Error sendStats JSONException " + e.getMessage());
|
|
924
1340
|
return;
|
|
925
1341
|
}
|
|
926
1342
|
|
|
@@ -929,21 +1345,28 @@ public class CapacitorUpdater {
|
|
|
929
1345
|
.post(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
930
1346
|
.build();
|
|
931
1347
|
|
|
932
|
-
|
|
1348
|
+
DownloadService.sharedClient
|
|
933
1349
|
.newCall(request)
|
|
934
1350
|
.enqueue(
|
|
935
1351
|
new okhttp3.Callback() {
|
|
936
1352
|
@Override
|
|
937
1353
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
938
|
-
|
|
1354
|
+
logger.error("Failed to send stats: " + e.getMessage());
|
|
939
1355
|
}
|
|
940
1356
|
|
|
941
1357
|
@Override
|
|
942
1358
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1359
|
+
try (ResponseBody responseBody = response.body()) {
|
|
1360
|
+
// Check for 429 rate limit
|
|
1361
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (response.isSuccessful()) {
|
|
1366
|
+
logger.info("Stats send for \"" + action + "\", version " + versionName);
|
|
1367
|
+
} else {
|
|
1368
|
+
logger.error("Error sending stats: " + response.code());
|
|
1369
|
+
}
|
|
947
1370
|
}
|
|
948
1371
|
}
|
|
949
1372
|
}
|
|
@@ -963,13 +1386,26 @@ public class CapacitorUpdater {
|
|
|
963
1386
|
} else {
|
|
964
1387
|
try {
|
|
965
1388
|
String stored = this.prefs.getString(trueId + INFO_SUFFIX, "");
|
|
966
|
-
|
|
1389
|
+
if (stored.isEmpty()) {
|
|
1390
|
+
result = new BundleInfo(trueId, null, BundleStatus.PENDING, "", "");
|
|
1391
|
+
} else {
|
|
1392
|
+
result = BundleInfo.fromJSON(stored);
|
|
1393
|
+
}
|
|
967
1394
|
} catch (JSONException e) {
|
|
968
|
-
|
|
969
|
-
|
|
1395
|
+
logger.error(
|
|
1396
|
+
"Failed to parse info for bundle [" +
|
|
1397
|
+
trueId +
|
|
1398
|
+
"] stored value: '" +
|
|
1399
|
+
this.prefs.getString(trueId + INFO_SUFFIX, "") +
|
|
1400
|
+
"' error: " +
|
|
1401
|
+
e.getMessage()
|
|
1402
|
+
);
|
|
1403
|
+
// Clear corrupted data
|
|
1404
|
+
this.editor.remove(trueId + INFO_SUFFIX);
|
|
1405
|
+
this.editor.commit();
|
|
1406
|
+
result = new BundleInfo(trueId, null, BundleStatus.ERROR, "", "");
|
|
970
1407
|
}
|
|
971
1408
|
}
|
|
972
|
-
// Log.d(TAG, "Returning info [" + trueId + "] " + result);
|
|
973
1409
|
return result;
|
|
974
1410
|
}
|
|
975
1411
|
|
|
@@ -989,17 +1425,18 @@ public class CapacitorUpdater {
|
|
|
989
1425
|
|
|
990
1426
|
public void saveBundleInfo(final String id, final BundleInfo info) {
|
|
991
1427
|
if (id == null || (info != null && (info.isBuiltin() || info.isUnknown()))) {
|
|
992
|
-
|
|
1428
|
+
logger.debug("Not saving info for bundle: [" + id + "] " + info);
|
|
993
1429
|
return;
|
|
994
1430
|
}
|
|
995
1431
|
|
|
996
1432
|
if (info == null) {
|
|
997
|
-
|
|
1433
|
+
logger.debug("Removing info for bundle [" + id + "]");
|
|
998
1434
|
this.editor.remove(id + INFO_SUFFIX);
|
|
999
1435
|
} else {
|
|
1000
1436
|
final BundleInfo update = info.setId(id);
|
|
1001
|
-
|
|
1002
|
-
|
|
1437
|
+
String jsonString = update.toString();
|
|
1438
|
+
logger.debug("Storing info for bundle [" + id + "] " + update.getClass().getName() + " -> " + jsonString);
|
|
1439
|
+
this.editor.putString(id + INFO_SUFFIX, jsonString);
|
|
1003
1440
|
}
|
|
1004
1441
|
this.editor.commit();
|
|
1005
1442
|
}
|
|
@@ -1007,7 +1444,7 @@ public class CapacitorUpdater {
|
|
|
1007
1444
|
private void setBundleStatus(final String id, final BundleStatus status) {
|
|
1008
1445
|
if (id != null && status != null) {
|
|
1009
1446
|
BundleInfo info = this.getBundleInfo(id);
|
|
1010
|
-
|
|
1447
|
+
logger.debug("Setting status for bundle [" + id + "] to " + status);
|
|
1011
1448
|
this.saveBundleInfo(id, info.setStatus(status));
|
|
1012
1449
|
}
|
|
1013
1450
|
}
|
|
@@ -1026,7 +1463,7 @@ public class CapacitorUpdater {
|
|
|
1026
1463
|
}
|
|
1027
1464
|
|
|
1028
1465
|
public String getCurrentBundlePath() {
|
|
1029
|
-
String path = this.prefs.getString(
|
|
1466
|
+
String path = this.prefs.getString(this.CAP_SERVER_PATH, "public");
|
|
1030
1467
|
if (path.trim().isEmpty()) {
|
|
1031
1468
|
return "public";
|
|
1032
1469
|
}
|