@capawesome/capacitor-live-update 6.7.1 → 7.0.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/CapawesomeCapacitorLiveUpdate.podspec +2 -2
- package/Package.swift +32 -0
- package/README.md +91 -81
- package/android/build.gradle +9 -9
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +165 -132
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +6 -27
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +7 -2
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +50 -78
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +12 -5
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +9 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/DownloadBundleProgressEvent.java +32 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +1 -10
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchLatestBundleResult.java +13 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ReadyResult.java +32 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/DownloadProgressCallback.java +5 -0
- package/dist/docs.json +191 -158
- package/dist/esm/definitions.d.ts +107 -52
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +1 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +1 -3
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Classes/Events/DownloadBundleProgressEvent.swift +25 -0
- package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +1 -7
- package/ios/Plugin/Classes/Results/{FetchLatestBundle.swift → FetchLatestBundleResult.swift} +6 -1
- package/ios/Plugin/Classes/Results/ReadyResult.swift +22 -0
- package/ios/Plugin/LiveUpdate.swift +167 -148
- package/ios/Plugin/LiveUpdateConfig.swift +2 -4
- package/ios/Plugin/LiveUpdateHttpClient.swift +6 -2
- package/ios/Plugin/LiveUpdatePlugin.swift +44 -38
- package/ios/Plugin/LiveUpdatePreferences.swift +9 -5
- package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +1 -0
- package/package.json +17 -18
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetBundleOptions.java +0 -18
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundleResult.java +0 -24
- package/ios/Plugin/Classes/Options/SetBundleOptions.swift +0 -14
- package/ios/Plugin/Classes/Results/GetBundleResult.swift +0 -16
- package/ios/Plugin/LiveUpdatePlugin.h +0 -10
- package/ios/Plugin/LiveUpdatePlugin.m +0 -27
|
@@ -10,21 +10,21 @@ import android.os.Handler;
|
|
|
10
10
|
import android.util.Base64;
|
|
11
11
|
import androidx.annotation.NonNull;
|
|
12
12
|
import androidx.annotation.Nullable;
|
|
13
|
+
import com.getcapacitor.Bridge;
|
|
13
14
|
import com.getcapacitor.Logger;
|
|
14
15
|
import com.getcapacitor.plugin.WebView;
|
|
15
16
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
|
|
16
17
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
|
|
17
18
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
|
|
19
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
|
|
18
20
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
|
|
19
21
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
|
|
20
22
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
|
|
21
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetBundleOptions;
|
|
22
23
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
|
|
23
24
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
|
|
24
25
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetNextBundleOptions;
|
|
25
26
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SyncOptions;
|
|
26
27
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
|
|
27
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundleResult;
|
|
28
28
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundlesResult;
|
|
29
29
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetChannelResult;
|
|
30
30
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetCurrentBundleResult;
|
|
@@ -33,8 +33,10 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDeviceIdR
|
|
|
33
33
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetNextBundleResult;
|
|
34
34
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionCodeResult;
|
|
35
35
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionNameResult;
|
|
36
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.ReadyResult;
|
|
36
37
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
|
|
37
38
|
import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
|
|
39
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.DownloadProgressCallback;
|
|
38
40
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
|
|
39
41
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
|
|
40
42
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
|
|
@@ -68,10 +70,7 @@ public class LiveUpdate {
|
|
|
68
70
|
@NonNull
|
|
69
71
|
private final LiveUpdateConfig config;
|
|
70
72
|
|
|
71
|
-
private final String defaultWebAssetDir;
|
|
72
|
-
|
|
73
|
-
@NonNull
|
|
74
|
-
private final String host;
|
|
73
|
+
private final String defaultWebAssetDir = Bridge.DEFAULT_WEB_ASSET_DIR;
|
|
75
74
|
|
|
76
75
|
@NonNull
|
|
77
76
|
private final LiveUpdateHttpClient httpClient;
|
|
@@ -89,38 +88,29 @@ public class LiveUpdate {
|
|
|
89
88
|
private final Handler rollbackHandler = new Handler();
|
|
90
89
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
91
90
|
|
|
91
|
+
private boolean rollbackPerformed = false;
|
|
92
|
+
|
|
92
93
|
public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
|
|
93
94
|
this.config = config;
|
|
94
|
-
this.defaultWebAssetDir = plugin.getBridge().DEFAULT_WEB_ASSET_DIR;
|
|
95
|
-
if (config.getLocation() != null && config.getLocation().equals("eu")) {
|
|
96
|
-
this.host = "api.cloud.capawesome.eu";
|
|
97
|
-
} else {
|
|
98
|
-
this.host = "api.cloud.capawesome.io";
|
|
99
|
-
}
|
|
100
95
|
this.httpClient = new LiveUpdateHttpClient(config);
|
|
101
96
|
this.plugin = plugin;
|
|
102
97
|
this.preferences = new LiveUpdatePreferences(plugin.getContext());
|
|
103
98
|
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
104
99
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
} else {
|
|
109
|
-
startRollbackTimer();
|
|
110
|
-
}
|
|
111
|
-
saveCurrentVersionCode();
|
|
112
|
-
}
|
|
100
|
+
// Start the rollback timer to rollback to the default bundle
|
|
101
|
+
// if the app is not ready after a certain time
|
|
102
|
+
startRollbackTimer();
|
|
113
103
|
}
|
|
114
104
|
|
|
115
105
|
public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
|
|
116
106
|
String bundleId = options.getBundleId();
|
|
117
107
|
|
|
118
|
-
if (!
|
|
108
|
+
if (!hasBundleById(bundleId)) {
|
|
119
109
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
120
110
|
callback.error(exception);
|
|
121
111
|
return;
|
|
122
112
|
}
|
|
123
|
-
|
|
113
|
+
deleteBundleById(bundleId);
|
|
124
114
|
|
|
125
115
|
callback.success();
|
|
126
116
|
}
|
|
@@ -128,11 +118,10 @@ public class LiveUpdate {
|
|
|
128
118
|
public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback) throws Exception {
|
|
129
119
|
ArtifactType artifactType = options.getArtifactType();
|
|
130
120
|
String bundleId = options.getBundleId();
|
|
131
|
-
String checksum = options.getChecksum();
|
|
132
121
|
String url = options.getUrl();
|
|
133
122
|
|
|
134
123
|
// Check if the bundle already exists
|
|
135
|
-
if (
|
|
124
|
+
if (hasBundleById(bundleId)) {
|
|
136
125
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_EXISTS);
|
|
137
126
|
callback.error(exception);
|
|
138
127
|
return;
|
|
@@ -142,7 +131,7 @@ public class LiveUpdate {
|
|
|
142
131
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
143
132
|
downloadBundleOfTypeManifest(bundleId, url);
|
|
144
133
|
} else {
|
|
145
|
-
downloadBundleOfTypeZip(bundleId, url
|
|
134
|
+
downloadBundleOfTypeZip(bundleId, url);
|
|
146
135
|
}
|
|
147
136
|
callback.success();
|
|
148
137
|
}
|
|
@@ -151,17 +140,9 @@ public class LiveUpdate {
|
|
|
151
140
|
GetLatestBundleResponse response = fetchLatestBundle(options);
|
|
152
141
|
ArtifactType artifactType = response == null ? null : response.getArtifactType();
|
|
153
142
|
String bundleId = response == null ? null : response.getBundleId();
|
|
143
|
+
JSONObject customProperties = response == null ? null : response.getCustomProperties();
|
|
154
144
|
String downloadUrl = response == null ? null : response.getUrl();
|
|
155
|
-
FetchLatestBundleResult result = new FetchLatestBundleResult(artifactType, bundleId, downloadUrl);
|
|
156
|
-
callback.success(result);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
public void getBundle(@NonNull NonEmptyCallback callback) {
|
|
160
|
-
String bundleId = getCurrentBundleId();
|
|
161
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
162
|
-
bundleId = null;
|
|
163
|
-
}
|
|
164
|
-
GetBundleResult result = new GetBundleResult(bundleId);
|
|
145
|
+
FetchLatestBundleResult result = new FetchLatestBundleResult(artifactType, bundleId, customProperties, downloadUrl);
|
|
165
146
|
callback.success(result);
|
|
166
147
|
}
|
|
167
148
|
|
|
@@ -179,9 +160,6 @@ public class LiveUpdate {
|
|
|
179
160
|
|
|
180
161
|
public void getCurrentBundle(@NonNull NonEmptyCallback<GetCurrentBundleResult> callback) {
|
|
181
162
|
String bundleId = getCurrentBundleId();
|
|
182
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
183
|
-
bundleId = null;
|
|
184
|
-
}
|
|
185
163
|
GetCurrentBundleResult result = new GetCurrentBundleResult(bundleId);
|
|
186
164
|
callback.success(result);
|
|
187
165
|
}
|
|
@@ -200,9 +178,6 @@ public class LiveUpdate {
|
|
|
200
178
|
|
|
201
179
|
public void getNextBundle(@NonNull NonEmptyCallback<GetNextBundleResult> callback) {
|
|
202
180
|
String bundleId = getNextBundleId();
|
|
203
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
204
|
-
bundleId = null;
|
|
205
|
-
}
|
|
206
181
|
GetNextBundleResult result = new GetNextBundleResult(bundleId);
|
|
207
182
|
callback.success(result);
|
|
208
183
|
}
|
|
@@ -219,14 +194,27 @@ public class LiveUpdate {
|
|
|
219
194
|
callback.success(result);
|
|
220
195
|
}
|
|
221
196
|
|
|
222
|
-
public void ready() {
|
|
197
|
+
public void ready(@NonNull NonEmptyCallback callback) {
|
|
223
198
|
Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
|
|
199
|
+
if (config.getReadyTimeout() <= 0) {
|
|
200
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
|
|
201
|
+
}
|
|
224
202
|
// Stop the rollback timer
|
|
225
203
|
stopRollbackTimer();
|
|
226
204
|
// Delete unused bundles
|
|
227
205
|
if (config.getAutoDeleteBundles()) {
|
|
228
206
|
deleteUnusedBundles();
|
|
229
207
|
}
|
|
208
|
+
// Get the current and previous bundle IDs
|
|
209
|
+
String currentBundleId = getCurrentBundleId();
|
|
210
|
+
String previousBundleId = getPreviousBundleId();
|
|
211
|
+
// Return the result
|
|
212
|
+
ReadyResult result = new ReadyResult(currentBundleId, previousBundleId, rollbackPerformed);
|
|
213
|
+
callback.success(result);
|
|
214
|
+
// Set the new previous bundle ID
|
|
215
|
+
setPreviousBundleId(currentBundleId);
|
|
216
|
+
// Reset the rollback flag
|
|
217
|
+
rollbackPerformed = false;
|
|
230
218
|
}
|
|
231
219
|
|
|
232
220
|
public void reload() {
|
|
@@ -236,20 +224,7 @@ public class LiveUpdate {
|
|
|
236
224
|
}
|
|
237
225
|
|
|
238
226
|
public void reset() {
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
public void setBundle(@NonNull SetBundleOptions options, @NonNull EmptyCallback callback) {
|
|
243
|
-
String bundleId = options.getBundleId();
|
|
244
|
-
|
|
245
|
-
if (!hasBundle(bundleId)) {
|
|
246
|
-
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
247
|
-
callback.error(exception);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
setNextBundle(bundleId);
|
|
252
|
-
callback.success();
|
|
227
|
+
setNextBundleById(null);
|
|
253
228
|
}
|
|
254
229
|
|
|
255
230
|
public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
|
|
@@ -272,8 +247,8 @@ public class LiveUpdate {
|
|
|
272
247
|
if (bundleId == null) {
|
|
273
248
|
reset();
|
|
274
249
|
} else {
|
|
275
|
-
if (
|
|
276
|
-
|
|
250
|
+
if (hasBundleById(bundleId)) {
|
|
251
|
+
setNextBundleById(bundleId);
|
|
277
252
|
} else {
|
|
278
253
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
279
254
|
callback.error(exception);
|
|
@@ -298,12 +273,12 @@ public class LiveUpdate {
|
|
|
298
273
|
String latestBundleId = response.getBundleId();
|
|
299
274
|
String url = response.getUrl();
|
|
300
275
|
// Check if the bundle already exists
|
|
301
|
-
if (
|
|
276
|
+
if (hasBundleById(latestBundleId)) {
|
|
302
277
|
String nextBundleId = null;
|
|
303
278
|
String currentBundleId = getCurrentBundleId();
|
|
304
279
|
if (!latestBundleId.equals(currentBundleId)) {
|
|
305
280
|
// Set the next bundle
|
|
306
|
-
|
|
281
|
+
setNextBundleById(latestBundleId);
|
|
307
282
|
nextBundleId = latestBundleId;
|
|
308
283
|
}
|
|
309
284
|
SyncResult syncResult = new SyncResult(nextBundleId);
|
|
@@ -314,10 +289,10 @@ public class LiveUpdate {
|
|
|
314
289
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
315
290
|
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
316
291
|
} else {
|
|
317
|
-
downloadBundleOfTypeZip(latestBundleId, url
|
|
292
|
+
downloadBundleOfTypeZip(latestBundleId, url);
|
|
318
293
|
}
|
|
319
294
|
// Set the next bundle
|
|
320
|
-
|
|
295
|
+
setNextBundleById(latestBundleId);
|
|
321
296
|
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
322
297
|
callback.success(syncResult);
|
|
323
298
|
}
|
|
@@ -366,9 +341,10 @@ public class LiveUpdate {
|
|
|
366
341
|
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
367
342
|
}
|
|
368
343
|
|
|
369
|
-
private void copyCurrentBundleFile(@NonNull
|
|
344
|
+
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
345
|
+
String href = fileToCopy.getHref();
|
|
370
346
|
String currentBundleId = getCurrentBundleId();
|
|
371
|
-
if (currentBundleId
|
|
347
|
+
if (currentBundleId == null) {
|
|
372
348
|
// Create the source input stream
|
|
373
349
|
AssetManager assets = plugin.getContext().getAssets();
|
|
374
350
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
|
|
@@ -390,9 +366,9 @@ public class LiveUpdate {
|
|
|
390
366
|
}
|
|
391
367
|
}
|
|
392
368
|
|
|
393
|
-
private void copyCurrentBundleFiles(@NonNull List<
|
|
394
|
-
for (
|
|
395
|
-
copyCurrentBundleFile(
|
|
369
|
+
private void copyCurrentBundleFiles(@NonNull List<ManifestItem> filesToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
370
|
+
for (ManifestItem fileToCopy : filesToCopy) {
|
|
371
|
+
copyCurrentBundleFile(fileToCopy, destinationDirectory);
|
|
396
372
|
}
|
|
397
373
|
}
|
|
398
374
|
|
|
@@ -445,14 +421,14 @@ public class LiveUpdate {
|
|
|
445
421
|
return file;
|
|
446
422
|
}
|
|
447
423
|
|
|
448
|
-
private void
|
|
424
|
+
private void deleteBundleById(@NonNull String bundleId) {
|
|
449
425
|
// Delete the bundle directory
|
|
450
426
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
451
427
|
deleteFileRecursively(bundleDirectory);
|
|
452
428
|
// Reset the next bundle if it is the deleted bundle
|
|
453
429
|
String nextBundleId = getNextBundleId();
|
|
454
430
|
if (bundleId.equals(nextBundleId)) {
|
|
455
|
-
|
|
431
|
+
setNextBundleById(null);
|
|
456
432
|
}
|
|
457
433
|
}
|
|
458
434
|
|
|
@@ -469,18 +445,19 @@ public class LiveUpdate {
|
|
|
469
445
|
String[] bundleIds = getBundleIds();
|
|
470
446
|
for (String bundleId : bundleIds) {
|
|
471
447
|
if (!isBundleInUse(bundleId)) {
|
|
472
|
-
|
|
448
|
+
deleteBundleById(bundleId);
|
|
473
449
|
}
|
|
474
450
|
}
|
|
475
451
|
}
|
|
476
452
|
|
|
477
|
-
private void downloadAndVerifyFile(@NonNull String url, @NonNull File file, @Nullable
|
|
453
|
+
private void downloadAndVerifyFile(@NonNull String url, @NonNull File file, @Nullable DownloadProgressCallback callback)
|
|
454
|
+
throws Exception {
|
|
478
455
|
Response response = httpClient.execute(url);
|
|
479
456
|
ResponseBody responseBody = response.body();
|
|
480
457
|
if (response.isSuccessful()) {
|
|
481
|
-
LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file);
|
|
458
|
+
LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file, callback);
|
|
482
459
|
// Verify the file
|
|
483
|
-
checksum =
|
|
460
|
+
String checksum = LiveUpdateHttpClient.getChecksumFromResponse(response);
|
|
484
461
|
String signature = LiveUpdateHttpClient.getSignatureFromResponse(response);
|
|
485
462
|
verifyFile(file, checksum, signature);
|
|
486
463
|
} else {
|
|
@@ -490,24 +467,39 @@ public class LiveUpdate {
|
|
|
490
467
|
}
|
|
491
468
|
}
|
|
492
469
|
|
|
493
|
-
private File downloadBundleFile(
|
|
470
|
+
private File downloadBundleFile(
|
|
471
|
+
@NonNull String baseUrl,
|
|
472
|
+
@NonNull String href,
|
|
473
|
+
@NonNull File destinationDirectory,
|
|
474
|
+
@Nullable DownloadProgressCallback callback
|
|
475
|
+
) throws Exception {
|
|
494
476
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
|
|
495
477
|
urlBuilder.addQueryParameter("href", href);
|
|
496
478
|
String url = urlBuilder.build().toString();
|
|
497
479
|
|
|
498
480
|
File destinationFile = new File(destinationDirectory, href);
|
|
499
481
|
destinationFile.getParentFile().mkdirs();
|
|
500
|
-
downloadAndVerifyFile(url, destinationFile,
|
|
482
|
+
downloadAndVerifyFile(url, destinationFile, callback);
|
|
501
483
|
return destinationFile;
|
|
502
484
|
}
|
|
503
485
|
|
|
504
|
-
private void downloadBundleFiles(
|
|
505
|
-
|
|
486
|
+
private void downloadBundleFiles(
|
|
487
|
+
@NonNull String baseUrl,
|
|
488
|
+
@NonNull List<ManifestItem> filesToDownload,
|
|
489
|
+
@NonNull File destinationDirectory,
|
|
490
|
+
@Nullable DownloadProgressCallback callback
|
|
491
|
+
) throws Exception {
|
|
492
|
+
// Track the total downloaded bytes and total bytes to download for the progress callback
|
|
493
|
+
final long[] totalBytesDownloaded = { 0 };
|
|
494
|
+
long totalBytesToDownload = 0;
|
|
495
|
+
for (ManifestItem fileToDownload : filesToDownload) {
|
|
496
|
+
totalBytesToDownload += fileToDownload.getSizeInBytes();
|
|
497
|
+
}
|
|
506
498
|
// Limit the number of threads to twice the number of processors
|
|
507
499
|
int maxThreads = Runtime.getRuntime().availableProcessors() * 2;
|
|
508
500
|
List<Thread> threads = new ArrayList<>();
|
|
509
501
|
AtomicReference<Exception> exceptionReference = new AtomicReference<>();
|
|
510
|
-
for (
|
|
502
|
+
for (ManifestItem fileToDownload : filesToDownload) {
|
|
511
503
|
// Wait until a thread is available
|
|
512
504
|
while (threads.size() >= maxThreads) {
|
|
513
505
|
for (Thread thread : threads) {
|
|
@@ -523,13 +515,23 @@ public class LiveUpdate {
|
|
|
523
515
|
if (exceptionReference.get() != null) {
|
|
524
516
|
break;
|
|
525
517
|
}
|
|
518
|
+
// Final variable that can be accessed from within inner class
|
|
519
|
+
long finalTotalBytesToDownload = totalBytesToDownload;
|
|
526
520
|
// Start a new thread
|
|
527
521
|
Thread thread = new Thread(() -> {
|
|
528
522
|
try {
|
|
529
|
-
downloadBundleFile(baseUrl,
|
|
523
|
+
downloadBundleFile(baseUrl, fileToDownload.getHref(), destinationDirectory, (downloadedBytes, totalBytes) -> {
|
|
524
|
+
if (callback != null) {
|
|
525
|
+
callback.onProgress(totalBytesDownloaded[0] + downloadedBytes, finalTotalBytesToDownload);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
if (callback != null) {
|
|
529
|
+
totalBytesDownloaded[0] += fileToDownload.getSizeInBytes();
|
|
530
|
+
callback.onProgress(totalBytesDownloaded[0], finalTotalBytesToDownload);
|
|
531
|
+
}
|
|
530
532
|
} catch (Exception exception) {
|
|
531
533
|
exceptionReference.set(exception);
|
|
532
|
-
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " +
|
|
534
|
+
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + fileToDownload.getHref(), exception);
|
|
533
535
|
}
|
|
534
536
|
});
|
|
535
537
|
threads.add(thread);
|
|
@@ -544,13 +546,17 @@ public class LiveUpdate {
|
|
|
544
546
|
if (exception != null) {
|
|
545
547
|
throw exception;
|
|
546
548
|
}
|
|
549
|
+
// Call the callback one last time to make sure the progress is at 100%
|
|
550
|
+
if (callback != null) {
|
|
551
|
+
callback.onProgress(totalBytesToDownload, totalBytesToDownload);
|
|
552
|
+
}
|
|
547
553
|
}
|
|
548
554
|
|
|
549
555
|
private void downloadBundleOfTypeManifest(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
|
|
550
556
|
// Create a temporary directory
|
|
551
557
|
File temporaryDirectory = createTemporaryDirectory();
|
|
552
558
|
// Download the latest manifest
|
|
553
|
-
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory);
|
|
559
|
+
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory, null);
|
|
554
560
|
Manifest latestManifest = loadManifest(latestManifestFile);
|
|
555
561
|
// Load the current manifest
|
|
556
562
|
Manifest currentManifest = loadCurrentManifest();
|
|
@@ -564,26 +570,23 @@ public class LiveUpdate {
|
|
|
564
570
|
itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
|
|
565
571
|
}
|
|
566
572
|
// Copy the files
|
|
567
|
-
|
|
568
|
-
for (ManifestItem item : itemsToCopy) {
|
|
569
|
-
hrefsToCopy.add(item.getHref());
|
|
570
|
-
}
|
|
571
|
-
copyCurrentBundleFiles(hrefsToCopy, temporaryDirectory);
|
|
573
|
+
copyCurrentBundleFiles(itemsToCopy, temporaryDirectory);
|
|
572
574
|
// Download the files
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
downloadBundleFiles(downloadUrl, hrefsToDownload, temporaryDirectory);
|
|
575
|
+
downloadBundleFiles(downloadUrl, itemsToDownload, temporaryDirectory, (downloadedBytes, totalBytes) -> {
|
|
576
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
577
|
+
notifyDownloadBundleProgressListeners(event);
|
|
578
|
+
});
|
|
578
579
|
// Add the bundle
|
|
579
580
|
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
580
581
|
}
|
|
581
582
|
|
|
582
|
-
private void downloadBundleOfTypeZip(@NonNull String bundleId, @NonNull String downloadUrl
|
|
583
|
-
throws Exception {
|
|
583
|
+
private void downloadBundleOfTypeZip(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
|
|
584
584
|
File file = buildTemporaryZipFile();
|
|
585
585
|
// Download the bundle
|
|
586
|
-
downloadAndVerifyFile(downloadUrl, file,
|
|
586
|
+
downloadAndVerifyFile(downloadUrl, file, (downloadedBytes, totalBytes) -> {
|
|
587
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
588
|
+
notifyDownloadBundleProgressListeners(event);
|
|
589
|
+
});
|
|
587
590
|
// Add the bundle
|
|
588
591
|
addBundleOfTypeZip(bundleId, file);
|
|
589
592
|
// Delete the temporary file
|
|
@@ -595,7 +598,7 @@ public class LiveUpdate {
|
|
|
595
598
|
String channel = options.getChannel() == null ? getChannel() : options.getChannel();
|
|
596
599
|
String url = new HttpUrl.Builder()
|
|
597
600
|
.scheme("https")
|
|
598
|
-
.host(
|
|
601
|
+
.host(config.getServerDomain())
|
|
599
602
|
.addPathSegment("v1")
|
|
600
603
|
.addPathSegment("apps")
|
|
601
604
|
.addPathSegment(config.getAppId())
|
|
@@ -677,12 +680,13 @@ public class LiveUpdate {
|
|
|
677
680
|
}
|
|
678
681
|
|
|
679
682
|
/**
|
|
680
|
-
* @return The current bundle ID
|
|
683
|
+
* @return The current bundle ID or `null` if the default bundle is in use.
|
|
681
684
|
*/
|
|
685
|
+
@Nullable
|
|
682
686
|
private String getCurrentBundleId() {
|
|
683
687
|
String currentPath = getCurrentCapacitorServerPath();
|
|
684
688
|
if (currentPath.equals(defaultWebAssetDir)) {
|
|
685
|
-
return
|
|
689
|
+
return null;
|
|
686
690
|
}
|
|
687
691
|
return new File(currentPath).getName();
|
|
688
692
|
}
|
|
@@ -705,13 +709,13 @@ public class LiveUpdate {
|
|
|
705
709
|
}
|
|
706
710
|
|
|
707
711
|
/**
|
|
708
|
-
* @return The next bundle ID
|
|
712
|
+
* @return The next bundle ID or `null` if the default bundle will be used.
|
|
709
713
|
*/
|
|
710
|
-
@
|
|
714
|
+
@Nullable
|
|
711
715
|
private String getNextBundleId() {
|
|
712
716
|
String nextPath = getNextCapacitorServerPath();
|
|
713
717
|
if (nextPath.equals(defaultWebAssetDir)) {
|
|
714
|
-
return
|
|
718
|
+
return null;
|
|
715
719
|
}
|
|
716
720
|
return new File(nextPath).getName();
|
|
717
721
|
}
|
|
@@ -727,6 +731,14 @@ public class LiveUpdate {
|
|
|
727
731
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
728
732
|
}
|
|
729
733
|
|
|
734
|
+
/**
|
|
735
|
+
* @return The previous bundle ID or `null` if the default bundle was used.
|
|
736
|
+
*/
|
|
737
|
+
@Nullable
|
|
738
|
+
private String getPreviousBundleId() {
|
|
739
|
+
return preferences.getPreviousBundleId();
|
|
740
|
+
}
|
|
741
|
+
|
|
730
742
|
private String getVersionCode() throws PackageManager.NameNotFoundException {
|
|
731
743
|
return String.valueOf(getPackageInfo().versionCode);
|
|
732
744
|
}
|
|
@@ -735,17 +747,23 @@ public class LiveUpdate {
|
|
|
735
747
|
return getPackageInfo().versionName;
|
|
736
748
|
}
|
|
737
749
|
|
|
738
|
-
private boolean
|
|
750
|
+
private boolean hasBundleById(@NonNull String bundleId) {
|
|
739
751
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
740
752
|
return bundleDirectory.exists();
|
|
741
753
|
}
|
|
742
754
|
|
|
755
|
+
private boolean isBundleInUse(@NonNull String bundleId) {
|
|
756
|
+
String currentBundleId = getCurrentBundleId();
|
|
757
|
+
String nextBundleId = getNextBundleId();
|
|
758
|
+
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
759
|
+
}
|
|
760
|
+
|
|
743
761
|
@Nullable
|
|
744
762
|
private Manifest loadCurrentManifest() throws Exception {
|
|
745
763
|
String currentBundleId = getCurrentBundleId();
|
|
746
|
-
if (currentBundleId
|
|
764
|
+
if (currentBundleId == null) {
|
|
747
765
|
AssetManager assets = plugin.getContext().getAssets();
|
|
748
|
-
|
|
766
|
+
boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
|
|
749
767
|
if (manifestFileExists) {
|
|
750
768
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
|
|
751
769
|
BufferedSource source = Okio.buffer(Okio.source(inputStream));
|
|
@@ -775,25 +793,25 @@ public class LiveUpdate {
|
|
|
775
793
|
return loadManifest(source);
|
|
776
794
|
}
|
|
777
795
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
String nextBundleId = getNextBundleId();
|
|
781
|
-
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
796
|
+
public void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
797
|
+
plugin.notifyDownloadBundleProgressListeners(event);
|
|
782
798
|
}
|
|
783
799
|
|
|
784
800
|
private void rollback() {
|
|
785
|
-
|
|
801
|
+
// Set the rollback flag
|
|
802
|
+
rollbackPerformed = true;
|
|
803
|
+
// Set the new previous bundle ID
|
|
804
|
+
String currentBundleId = getCurrentBundleId();
|
|
805
|
+
setPreviousBundleId(currentBundleId);
|
|
806
|
+
// Log the rollback result
|
|
807
|
+
if (currentBundleId == null) {
|
|
786
808
|
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
|
|
787
|
-
|
|
809
|
+
} else {
|
|
810
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
|
|
811
|
+
// Rollback to the default bundle
|
|
812
|
+
setNextBundleById(null);
|
|
813
|
+
setCurrentBundleById(null);
|
|
788
814
|
}
|
|
789
|
-
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
|
|
790
|
-
setNextCapacitorServerPathToDefaultWebAssetDir();
|
|
791
|
-
setCurrentCapacitorServerPathToDefaultWebAssetDir();
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
private void saveCurrentVersionCode() throws PackageManager.NameNotFoundException {
|
|
795
|
-
int currentVersionCode = getPackageInfo().versionCode;
|
|
796
|
-
preferences.setLastVersionCode(currentVersionCode);
|
|
797
815
|
}
|
|
798
816
|
|
|
799
817
|
@Nullable
|
|
@@ -821,6 +839,18 @@ public class LiveUpdate {
|
|
|
821
839
|
return null;
|
|
822
840
|
}
|
|
823
841
|
|
|
842
|
+
/**
|
|
843
|
+
* @param bundleId The bundle ID to set as the current bundle. If `null`, the default bundle will be used.
|
|
844
|
+
*/
|
|
845
|
+
private void setCurrentBundleById(@Nullable String bundleId) {
|
|
846
|
+
if (bundleId == null) {
|
|
847
|
+
setCurrentCapacitorServerPath(defaultWebAssetDir);
|
|
848
|
+
} else {
|
|
849
|
+
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
850
|
+
setCurrentCapacitorServerPath(bundleDirectory.getPath());
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
824
854
|
private void setCurrentCapacitorServerPath(@NonNull String path) {
|
|
825
855
|
if (path.equals(defaultWebAssetDir)) {
|
|
826
856
|
this.plugin.getBridge().setServerAssetPath(path);
|
|
@@ -830,13 +860,16 @@ public class LiveUpdate {
|
|
|
830
860
|
this.plugin.getBridge().reload();
|
|
831
861
|
}
|
|
832
862
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
863
|
+
/**
|
|
864
|
+
* @param bundleId The bundle ID to set as the next bundle. If `null`, the default bundle will be used.
|
|
865
|
+
*/
|
|
866
|
+
private void setNextBundleById(@Nullable String bundleId) {
|
|
867
|
+
if (bundleId == null) {
|
|
868
|
+
setNextCapacitorServerPath(defaultWebAssetDir);
|
|
869
|
+
} else {
|
|
870
|
+
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
871
|
+
setNextCapacitorServerPath(bundleDirectory.getPath());
|
|
872
|
+
}
|
|
840
873
|
}
|
|
841
874
|
|
|
842
875
|
private void setNextCapacitorServerPath(@NonNull String path) {
|
|
@@ -844,11 +877,17 @@ public class LiveUpdate {
|
|
|
844
877
|
this.webViewSettingsEditor.commit();
|
|
845
878
|
}
|
|
846
879
|
|
|
847
|
-
|
|
848
|
-
|
|
880
|
+
/**
|
|
881
|
+
* @param bundleId The bundle ID to save as the previous bundle. If `null`, the value will be removed.
|
|
882
|
+
*/
|
|
883
|
+
private void setPreviousBundleId(@Nullable String bundleId) {
|
|
884
|
+
preferences.setPreviousBundleId(bundleId);
|
|
849
885
|
}
|
|
850
886
|
|
|
851
887
|
private void startRollbackTimer() {
|
|
888
|
+
if (config.getReadyTimeout() <= 0) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
852
891
|
stopRollbackTimer();
|
|
853
892
|
rollbackHandler.postDelayed(() -> rollback(), config.getReadyTimeout());
|
|
854
893
|
}
|
|
@@ -915,12 +954,6 @@ public class LiveUpdate {
|
|
|
915
954
|
}
|
|
916
955
|
}
|
|
917
956
|
|
|
918
|
-
private boolean wasUpdated() throws PackageManager.NameNotFoundException {
|
|
919
|
-
int lastVersionCode = preferences.getLastVersionCode();
|
|
920
|
-
int currentVersionCode = getPackageInfo().versionCode;
|
|
921
|
-
return lastVersionCode != currentVersionCode;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
957
|
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
|
925
958
|
String packageName = this.plugin.getContext().getPackageName();
|
|
926
959
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|