@capawesome/capacitor-live-update 7.2.2 → 7.4.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/README.md +284 -26
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +623 -220
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +33 -5
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +185 -15
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +42 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +2 -2
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/NextBundleSetEvent.java +26 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetConfigOptions.java +19 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SyncOptions.java +4 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBlockedBundlesResult.java +27 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetConfigResult.java +40 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetDownloadedBundlesResult.java +27 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/IsSyncingResult.java +26 -0
- package/dist/docs.json +428 -1
- package/dist/esm/definitions.d.ts +230 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +8 -1
- package/dist/esm/web.js +21 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +21 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +21 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Classes/Events/NextBundleSetEvent.swift +16 -0
- package/ios/Plugin/Classes/Options/SetConfigOptions.swift +14 -0
- package/ios/Plugin/Classes/Options/SyncOptions.swift +4 -0
- package/ios/Plugin/Classes/Results/GetBlockedBundlesResult.swift +16 -0
- package/ios/Plugin/Classes/Results/GetConfigResult.swift +19 -0
- package/ios/Plugin/Classes/Results/GetDownloadedBundlesResult.swift +16 -0
- package/ios/Plugin/Classes/Results/IsSyncingResult.swift +16 -0
- package/ios/Plugin/LiveUpdate.swift +178 -4
- package/ios/Plugin/LiveUpdateConfig.swift +2 -0
- package/ios/Plugin/LiveUpdateHttpClient.swift +4 -2
- package/ios/Plugin/LiveUpdatePlugin.swift +106 -11
- package/ios/Plugin/LiveUpdatePreferences.swift +56 -0
- package/package.json +1 -1
|
@@ -17,22 +17,28 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
|
|
|
17
17
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
|
|
18
18
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
|
|
19
19
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
|
|
20
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
|
|
20
21
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
|
|
21
22
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
|
|
22
23
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
|
|
23
24
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
|
|
25
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetConfigOptions;
|
|
24
26
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
|
|
25
27
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetNextBundleOptions;
|
|
26
28
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SyncOptions;
|
|
27
29
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
|
|
30
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBlockedBundlesResult;
|
|
28
31
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundlesResult;
|
|
29
32
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetChannelResult;
|
|
33
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetConfigResult;
|
|
30
34
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetCurrentBundleResult;
|
|
31
35
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetCustomIdResult;
|
|
32
36
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDeviceIdResult;
|
|
37
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDownloadedBundlesResult;
|
|
33
38
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetNextBundleResult;
|
|
34
39
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionCodeResult;
|
|
35
40
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionNameResult;
|
|
41
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.IsSyncingResult;
|
|
36
42
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.ReadyResult;
|
|
37
43
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
|
|
38
44
|
import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
|
|
@@ -54,8 +60,12 @@ import java.util.ArrayList;
|
|
|
54
60
|
import java.util.Arrays;
|
|
55
61
|
import java.util.List;
|
|
56
62
|
import java.util.UUID;
|
|
63
|
+
import java.util.concurrent.CountDownLatch;
|
|
64
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
65
|
+
import java.util.concurrent.atomic.AtomicLong;
|
|
57
66
|
import java.util.concurrent.atomic.AtomicReference;
|
|
58
67
|
import net.lingala.zip4j.ZipFile;
|
|
68
|
+
import okhttp3.Call;
|
|
59
69
|
import okhttp3.HttpUrl;
|
|
60
70
|
import okhttp3.Response;
|
|
61
71
|
import okhttp3.ResponseBody;
|
|
@@ -67,6 +77,8 @@ import org.json.JSONObject;
|
|
|
67
77
|
|
|
68
78
|
public class LiveUpdate {
|
|
69
79
|
|
|
80
|
+
private final long autoUpdateIntervalMs = 15 * 60 * 1000; // 15 minutes
|
|
81
|
+
|
|
70
82
|
@NonNull
|
|
71
83
|
private final LiveUpdateConfig config;
|
|
72
84
|
|
|
@@ -88,7 +100,10 @@ public class LiveUpdate {
|
|
|
88
100
|
private final Handler rollbackHandler = new Handler();
|
|
89
101
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
90
102
|
|
|
103
|
+
private boolean initialPageLoaded = false;
|
|
104
|
+
private long lastAutoUpdateCheckTimestamp = 0;
|
|
91
105
|
private boolean rollbackPerformed = false;
|
|
106
|
+
private boolean syncInProgress = false;
|
|
92
107
|
|
|
93
108
|
public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
|
|
94
109
|
this.config = config;
|
|
@@ -97,11 +112,18 @@ public class LiveUpdate {
|
|
|
97
112
|
this.preferences = new LiveUpdatePreferences(plugin.getContext());
|
|
98
113
|
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
99
114
|
|
|
115
|
+
// Check version and reset config if version changed
|
|
116
|
+
checkAndResetConfigIfVersionChanged();
|
|
117
|
+
|
|
100
118
|
// Start the rollback timer to rollback to the default bundle
|
|
101
119
|
// if the app is not ready after a certain time
|
|
102
120
|
startRollbackTimer();
|
|
103
121
|
}
|
|
104
122
|
|
|
123
|
+
public void clearBlockedBundles() {
|
|
124
|
+
preferences.setBlockedBundleIds(null);
|
|
125
|
+
}
|
|
126
|
+
|
|
105
127
|
public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
|
|
106
128
|
String bundleId = options.getBundleId();
|
|
107
129
|
|
|
@@ -115,7 +137,7 @@ public class LiveUpdate {
|
|
|
115
137
|
callback.success();
|
|
116
138
|
}
|
|
117
139
|
|
|
118
|
-
public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback)
|
|
140
|
+
public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback) {
|
|
119
141
|
ArtifactType artifactType = options.getArtifactType();
|
|
120
142
|
String bundleId = options.getBundleId();
|
|
121
143
|
String checksum = options.getChecksum();
|
|
@@ -131,44 +153,80 @@ public class LiveUpdate {
|
|
|
131
153
|
|
|
132
154
|
// Download the bundle
|
|
133
155
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
134
|
-
downloadBundleOfTypeManifest(bundleId, url);
|
|
156
|
+
downloadBundleOfTypeManifest(bundleId, url, callback);
|
|
135
157
|
} else {
|
|
136
|
-
downloadBundleOfTypeZip(bundleId, checksum, signature, url);
|
|
158
|
+
downloadBundleOfTypeZip(bundleId, checksum, signature, url, callback);
|
|
137
159
|
}
|
|
138
|
-
callback.success();
|
|
139
160
|
}
|
|
140
161
|
|
|
141
|
-
public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback callback)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback<FetchLatestBundleResult> callback) {
|
|
163
|
+
fetchLatestBundleInternal(
|
|
164
|
+
options,
|
|
165
|
+
new NonEmptyCallback<GetLatestBundleResponse>() {
|
|
166
|
+
@Override
|
|
167
|
+
public void success(@Nullable GetLatestBundleResponse response) {
|
|
168
|
+
ArtifactType artifactType = response == null ? null : response.getArtifactType();
|
|
169
|
+
String bundleId = response == null ? null : response.getBundleId();
|
|
170
|
+
String checksum = response == null ? null : response.getChecksum();
|
|
171
|
+
JSONObject customProperties = response == null ? null : response.getCustomProperties();
|
|
172
|
+
String downloadUrl = response == null ? null : response.getUrl();
|
|
173
|
+
String signature = response == null ? null : response.getSignature();
|
|
174
|
+
FetchLatestBundleResult result = new FetchLatestBundleResult(
|
|
175
|
+
artifactType,
|
|
176
|
+
bundleId,
|
|
177
|
+
checksum,
|
|
178
|
+
customProperties,
|
|
179
|
+
downloadUrl,
|
|
180
|
+
signature
|
|
181
|
+
);
|
|
182
|
+
callback.success(result);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@Override
|
|
186
|
+
public void error(@NonNull Exception exception) {
|
|
187
|
+
callback.error(exception);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
156
190
|
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public void getBlockedBundles(@NonNull NonEmptyCallback<GetBlockedBundlesResult> callback) {
|
|
194
|
+
String blockedIds = preferences.getBlockedBundleIds();
|
|
195
|
+
String[] bundleIds;
|
|
196
|
+
if (blockedIds == null || blockedIds.isEmpty()) {
|
|
197
|
+
bundleIds = new String[0];
|
|
198
|
+
} else {
|
|
199
|
+
bundleIds = blockedIds.split(",");
|
|
200
|
+
}
|
|
201
|
+
GetBlockedBundlesResult result = new GetBlockedBundlesResult(bundleIds);
|
|
157
202
|
callback.success(result);
|
|
158
203
|
}
|
|
159
204
|
|
|
160
205
|
public void getBundles(@NonNull NonEmptyCallback callback) {
|
|
161
|
-
String[] bundleIds =
|
|
206
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
162
207
|
GetBundlesResult result = new GetBundlesResult(bundleIds);
|
|
163
208
|
callback.success(result);
|
|
164
209
|
}
|
|
165
210
|
|
|
211
|
+
public void getDownloadedBundles(@NonNull NonEmptyCallback<GetDownloadedBundlesResult> callback) {
|
|
212
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
213
|
+
GetDownloadedBundlesResult result = new GetDownloadedBundlesResult(bundleIds);
|
|
214
|
+
callback.success(result);
|
|
215
|
+
}
|
|
216
|
+
|
|
166
217
|
public void getChannel(@NonNull NonEmptyCallback callback) {
|
|
167
218
|
String channel = getChannel();
|
|
168
219
|
GetChannelResult result = new GetChannelResult(channel);
|
|
169
220
|
callback.success(result);
|
|
170
221
|
}
|
|
171
222
|
|
|
223
|
+
public void getConfig(@NonNull NonEmptyCallback<GetConfigResult> callback) {
|
|
224
|
+
String appId = getAppId();
|
|
225
|
+
String autoUpdateStrategy = config.getAutoUpdateStrategy();
|
|
226
|
+
GetConfigResult result = new GetConfigResult(appId, autoUpdateStrategy);
|
|
227
|
+
callback.success(result);
|
|
228
|
+
}
|
|
229
|
+
|
|
172
230
|
public void getCurrentBundle(@NonNull NonEmptyCallback<GetCurrentBundleResult> callback) {
|
|
173
231
|
String bundleId = getCurrentBundleId();
|
|
174
232
|
GetCurrentBundleResult result = new GetCurrentBundleResult(bundleId);
|
|
@@ -194,7 +252,7 @@ public class LiveUpdate {
|
|
|
194
252
|
}
|
|
195
253
|
|
|
196
254
|
public void getVersionCode(@NonNull NonEmptyCallback callback) throws PackageManager.NameNotFoundException {
|
|
197
|
-
String versionCode =
|
|
255
|
+
String versionCode = getVersionCodeAsString();
|
|
198
256
|
GetVersionCodeResult result = new GetVersionCodeResult(versionCode);
|
|
199
257
|
callback.success(result);
|
|
200
258
|
}
|
|
@@ -205,6 +263,26 @@ public class LiveUpdate {
|
|
|
205
263
|
callback.success(result);
|
|
206
264
|
}
|
|
207
265
|
|
|
266
|
+
public void isSyncing(@NonNull NonEmptyCallback<IsSyncingResult> callback) {
|
|
267
|
+
IsSyncingResult result = new IsSyncingResult(syncInProgress);
|
|
268
|
+
callback.success(result);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
public void handleOnPageLoaded() {
|
|
272
|
+
// Wait for initial page load to perform auto update to make sure the WebViewLocalServer is initialized.
|
|
273
|
+
// Otherwise, a NullPointerException may occur for `com.getcapacitor.WebViewLocalServer.getBasePath()`.
|
|
274
|
+
if ("background".equals(config.getAutoUpdateStrategy()) && !initialPageLoaded) {
|
|
275
|
+
initialPageLoaded = true;
|
|
276
|
+
performAutoUpdate();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public void handleOnResume() {
|
|
281
|
+
if ("background".equals(config.getAutoUpdateStrategy()) && initialPageLoaded) {
|
|
282
|
+
performAutoUpdate();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
208
286
|
public void ready(@NonNull NonEmptyCallback callback) {
|
|
209
287
|
Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
|
|
210
288
|
if (config.getReadyTimeout() <= 0) {
|
|
@@ -219,6 +297,10 @@ public class LiveUpdate {
|
|
|
219
297
|
// Get the current and previous bundle IDs
|
|
220
298
|
String currentBundleId = getCurrentBundleId();
|
|
221
299
|
String previousBundleId = getPreviousBundleId();
|
|
300
|
+
// Block the rolled back bundle if enabled
|
|
301
|
+
if (config.getAutoBlockRolledBackBundles() && rollbackPerformed && previousBundleId != null) {
|
|
302
|
+
addBlockedBundleId(previousBundleId);
|
|
303
|
+
}
|
|
222
304
|
// Return the result
|
|
223
305
|
ReadyResult result = new ReadyResult(currentBundleId, previousBundleId, rollbackPerformed);
|
|
224
306
|
callback.success(result);
|
|
@@ -238,6 +320,10 @@ public class LiveUpdate {
|
|
|
238
320
|
setNextBundleById(null);
|
|
239
321
|
}
|
|
240
322
|
|
|
323
|
+
public void resetConfig() {
|
|
324
|
+
preferences.setAppId(null);
|
|
325
|
+
}
|
|
326
|
+
|
|
241
327
|
public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
|
|
242
328
|
String channel = options.getChannel();
|
|
243
329
|
|
|
@@ -245,6 +331,11 @@ public class LiveUpdate {
|
|
|
245
331
|
callback.success();
|
|
246
332
|
}
|
|
247
333
|
|
|
334
|
+
public void setConfig(@NonNull SetConfigOptions options) {
|
|
335
|
+
String appId = options.getAppId();
|
|
336
|
+
preferences.setAppId(appId);
|
|
337
|
+
}
|
|
338
|
+
|
|
248
339
|
public void setCustomId(@NonNull SetCustomIdOptions options, @NonNull EmptyCallback callback) {
|
|
249
340
|
String customId = options.getCustomId();
|
|
250
341
|
|
|
@@ -269,45 +360,99 @@ public class LiveUpdate {
|
|
|
269
360
|
callback.success();
|
|
270
361
|
}
|
|
271
362
|
|
|
272
|
-
public void sync(@NonNull SyncOptions options, @NonNull NonEmptyCallback<Result> callback)
|
|
363
|
+
public void sync(@NonNull SyncOptions options, @NonNull NonEmptyCallback<Result> callback) {
|
|
364
|
+
if (syncInProgress) {
|
|
365
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_SYNC_IN_PROGRESS);
|
|
366
|
+
callback.error(exception);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
syncInProgress = true;
|
|
370
|
+
|
|
273
371
|
String channel = options.getChannel();
|
|
274
372
|
// Fetch the latest bundle
|
|
275
373
|
FetchLatestBundleOptions fetchLatestBundleOptions = new FetchLatestBundleOptions(channel);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
374
|
+
fetchLatestBundleInternal(
|
|
375
|
+
fetchLatestBundleOptions,
|
|
376
|
+
new NonEmptyCallback<GetLatestBundleResponse>() {
|
|
377
|
+
@Override
|
|
378
|
+
public void success(@Nullable GetLatestBundleResponse response) {
|
|
379
|
+
try {
|
|
380
|
+
if (response == null) {
|
|
381
|
+
Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
|
|
382
|
+
syncInProgress = false;
|
|
383
|
+
SyncResult syncResult = new SyncResult(null);
|
|
384
|
+
callback.success(syncResult);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
ArtifactType artifactType = response.getArtifactType();
|
|
388
|
+
String latestBundleId = response.getBundleId();
|
|
389
|
+
String checksum = response.getChecksum();
|
|
390
|
+
String signature = response.getSignature();
|
|
391
|
+
String url = response.getUrl();
|
|
392
|
+
// Check if the bundle is blocked
|
|
393
|
+
if (isBlockedBundleId(latestBundleId)) {
|
|
394
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Bundle is blocked and will not be downloaded.");
|
|
395
|
+
syncInProgress = false;
|
|
396
|
+
SyncResult syncResult = new SyncResult(null);
|
|
397
|
+
callback.success(syncResult);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// Check if the bundle already exists
|
|
401
|
+
if (hasBundleById(latestBundleId)) {
|
|
402
|
+
String nextBundleId = null;
|
|
403
|
+
String currentBundleId = getCurrentBundleId();
|
|
404
|
+
if (!latestBundleId.equals(currentBundleId)) {
|
|
405
|
+
// Set the next bundle
|
|
406
|
+
setNextBundleById(latestBundleId);
|
|
407
|
+
nextBundleId = latestBundleId;
|
|
408
|
+
}
|
|
409
|
+
syncInProgress = false;
|
|
410
|
+
SyncResult syncResult = new SyncResult(nextBundleId);
|
|
411
|
+
callback.success(syncResult);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Download the bundle
|
|
416
|
+
EmptyCallback downloadCallback = new EmptyCallback() {
|
|
417
|
+
@Override
|
|
418
|
+
public void success() {
|
|
419
|
+
try {
|
|
420
|
+
// Set the next bundle
|
|
421
|
+
setNextBundleById(latestBundleId);
|
|
422
|
+
syncInProgress = false;
|
|
423
|
+
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
424
|
+
callback.success(syncResult);
|
|
425
|
+
} catch (Exception e) {
|
|
426
|
+
syncInProgress = false;
|
|
427
|
+
callback.error(e);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@Override
|
|
432
|
+
public void error(@NonNull Exception exception) {
|
|
433
|
+
syncInProgress = false;
|
|
434
|
+
callback.error(exception);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
if (artifactType == ArtifactType.MANIFEST) {
|
|
439
|
+
downloadBundleOfTypeManifest(latestBundleId, url, downloadCallback);
|
|
440
|
+
} else {
|
|
441
|
+
downloadBundleOfTypeZip(latestBundleId, checksum, signature, url, downloadCallback);
|
|
442
|
+
}
|
|
443
|
+
} catch (Exception e) {
|
|
444
|
+
syncInProgress = false;
|
|
445
|
+
callback.error(e);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@Override
|
|
450
|
+
public void error(@NonNull Exception exception) {
|
|
451
|
+
syncInProgress = false;
|
|
452
|
+
callback.error(exception);
|
|
453
|
+
}
|
|
296
454
|
}
|
|
297
|
-
|
|
298
|
-
callback.success(syncResult);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
// Download the bundle
|
|
302
|
-
if (artifactType == ArtifactType.MANIFEST) {
|
|
303
|
-
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
304
|
-
} else {
|
|
305
|
-
downloadBundleOfTypeZip(latestBundleId, checksum, signature, url);
|
|
306
|
-
}
|
|
307
|
-
// Set the next bundle
|
|
308
|
-
setNextBundleById(latestBundleId);
|
|
309
|
-
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
310
|
-
callback.success(syncResult);
|
|
455
|
+
);
|
|
311
456
|
}
|
|
312
457
|
|
|
313
458
|
private void addBundle(@NonNull String bundleId, @NonNull File sourceDirectory) throws Exception {
|
|
@@ -465,7 +610,7 @@ public class LiveUpdate {
|
|
|
465
610
|
}
|
|
466
611
|
|
|
467
612
|
private void deleteUnusedBundles() {
|
|
468
|
-
String[] bundleIds =
|
|
613
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
469
614
|
for (String bundleId : bundleIds) {
|
|
470
615
|
if (!isBundleInUse(bundleId)) {
|
|
471
616
|
deleteBundleById(bundleId);
|
|
@@ -473,202 +618,349 @@ public class LiveUpdate {
|
|
|
473
618
|
}
|
|
474
619
|
}
|
|
475
620
|
|
|
476
|
-
private
|
|
621
|
+
private Call downloadAndVerifyFile(
|
|
477
622
|
@NonNull String url,
|
|
478
623
|
@NonNull File file,
|
|
479
624
|
@Nullable String checksum,
|
|
480
625
|
@Nullable String signature,
|
|
481
|
-
@Nullable DownloadProgressCallback
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
626
|
+
@Nullable DownloadProgressCallback progressCallback,
|
|
627
|
+
@NonNull EmptyCallback completionCallback
|
|
628
|
+
) {
|
|
629
|
+
return httpClient.enqueue(
|
|
630
|
+
url,
|
|
631
|
+
new NonEmptyCallback<Response>() {
|
|
632
|
+
@Override
|
|
633
|
+
public void success(@NonNull Response response) {
|
|
634
|
+
try {
|
|
635
|
+
if (response.isSuccessful()) {
|
|
636
|
+
ResponseBody responseBody = response.body();
|
|
637
|
+
LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file, progressCallback);
|
|
638
|
+
|
|
639
|
+
// Extract checksum/signature from headers
|
|
640
|
+
String finalChecksum = checksum == null ? LiveUpdateHttpClient.getChecksumFromResponse(response) : checksum;
|
|
641
|
+
String finalSignature = signature == null ? LiveUpdateHttpClient.getSignatureFromResponse(response) : signature;
|
|
642
|
+
|
|
643
|
+
// Verify file
|
|
644
|
+
verifyFile(file, finalChecksum, finalSignature);
|
|
645
|
+
completionCallback.success();
|
|
646
|
+
} else {
|
|
647
|
+
String errorMessage = response.body().string();
|
|
648
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_FAILED);
|
|
649
|
+
Logger.error(LiveUpdatePlugin.TAG, errorMessage, exception);
|
|
650
|
+
completionCallback.error(exception);
|
|
651
|
+
}
|
|
652
|
+
} catch (Exception e) {
|
|
653
|
+
completionCallback.error(e);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
@Override
|
|
658
|
+
public void error(@NonNull Exception exception) {
|
|
659
|
+
completionCallback.error(exception);
|
|
660
|
+
}
|
|
493
661
|
}
|
|
494
|
-
|
|
495
|
-
} else {
|
|
496
|
-
Exception exception = new Exception(responseBody.string());
|
|
497
|
-
Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
|
|
498
|
-
throw new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_FAILED);
|
|
499
|
-
}
|
|
662
|
+
);
|
|
500
663
|
}
|
|
501
664
|
|
|
502
|
-
private
|
|
665
|
+
private Call downloadBundleFile(
|
|
503
666
|
@NonNull String baseUrl,
|
|
504
667
|
@NonNull String href,
|
|
505
668
|
@NonNull File destinationDirectory,
|
|
506
|
-
@Nullable DownloadProgressCallback
|
|
507
|
-
|
|
669
|
+
@Nullable DownloadProgressCallback progressCallback,
|
|
670
|
+
@NonNull EmptyCallback completionCallback
|
|
671
|
+
) {
|
|
508
672
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
|
|
509
673
|
urlBuilder.addQueryParameter("href", href);
|
|
510
674
|
String url = urlBuilder.build().toString();
|
|
511
675
|
|
|
512
676
|
File destinationFile = new File(destinationDirectory, href);
|
|
513
677
|
destinationFile.getParentFile().mkdirs();
|
|
514
|
-
downloadAndVerifyFile(url, destinationFile, null, null,
|
|
515
|
-
return destinationFile;
|
|
678
|
+
return downloadAndVerifyFile(url, destinationFile, null, null, progressCallback, completionCallback);
|
|
516
679
|
}
|
|
517
680
|
|
|
518
681
|
private void downloadBundleFiles(
|
|
519
682
|
@NonNull String baseUrl,
|
|
520
683
|
@NonNull List<ManifestItem> filesToDownload,
|
|
521
684
|
@NonNull File destinationDirectory,
|
|
522
|
-
@Nullable DownloadProgressCallback
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
685
|
+
@Nullable DownloadProgressCallback progressCallback,
|
|
686
|
+
@NonNull EmptyCallback completionCallback
|
|
687
|
+
) {
|
|
688
|
+
if (filesToDownload.isEmpty()) {
|
|
689
|
+
if (progressCallback != null) {
|
|
690
|
+
progressCallback.onProgress(0, 0);
|
|
691
|
+
}
|
|
692
|
+
completionCallback.success();
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Thread-safe progress tracking
|
|
697
|
+
AtomicLong totalBytesDownloaded = new AtomicLong(0);
|
|
526
698
|
long totalBytesToDownload = 0;
|
|
527
|
-
for (ManifestItem
|
|
528
|
-
totalBytesToDownload +=
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
699
|
+
for (ManifestItem item : filesToDownload) {
|
|
700
|
+
totalBytesToDownload += item.getSizeInBytes();
|
|
701
|
+
}
|
|
702
|
+
final long finalTotalBytesToDownload = totalBytesToDownload;
|
|
703
|
+
|
|
704
|
+
// Coordination primitives
|
|
705
|
+
CountDownLatch latch = new CountDownLatch(filesToDownload.size());
|
|
706
|
+
AtomicReference<Exception> firstError = new AtomicReference<>();
|
|
707
|
+
AtomicBoolean completionHandled = new AtomicBoolean(false);
|
|
708
|
+
List<Call> activeCalls = new ArrayList<>();
|
|
709
|
+
|
|
710
|
+
// Start all downloads asynchronously (OkHttp's dispatcher handles parallelization)
|
|
711
|
+
for (ManifestItem item : filesToDownload) {
|
|
712
|
+
Call call = downloadBundleFile(
|
|
713
|
+
baseUrl,
|
|
714
|
+
item.getHref(),
|
|
715
|
+
destinationDirectory,
|
|
716
|
+
(downloadedBytes, totalBytes) -> {
|
|
717
|
+
// Per-file progress
|
|
718
|
+
if (progressCallback != null) {
|
|
719
|
+
long totalProgress = totalBytesDownloaded.get() + downloadedBytes;
|
|
720
|
+
progressCallback.onProgress(totalProgress, finalTotalBytesToDownload);
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
new EmptyCallback() {
|
|
724
|
+
@Override
|
|
725
|
+
public void success() {
|
|
726
|
+
// Update total progress
|
|
727
|
+
totalBytesDownloaded.addAndGet(item.getSizeInBytes());
|
|
728
|
+
if (progressCallback != null) {
|
|
729
|
+
progressCallback.onProgress(totalBytesDownloaded.get(), finalTotalBytesToDownload);
|
|
730
|
+
}
|
|
731
|
+
latch.countDown();
|
|
732
|
+
|
|
733
|
+
// Check if this was the last download to complete
|
|
734
|
+
if (latch.getCount() == 0 && completionHandled.compareAndSet(false, true)) {
|
|
735
|
+
Exception error = firstError.get();
|
|
736
|
+
if (error != null) {
|
|
737
|
+
completionCallback.error(error);
|
|
738
|
+
} else {
|
|
739
|
+
// Final progress update
|
|
740
|
+
if (progressCallback != null) {
|
|
741
|
+
progressCallback.onProgress(finalTotalBytesToDownload, finalTotalBytesToDownload);
|
|
742
|
+
}
|
|
743
|
+
completionCallback.success();
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
@Override
|
|
749
|
+
public void error(@NonNull Exception e) {
|
|
750
|
+
// Capture first error and cancel all remaining downloads
|
|
751
|
+
if (firstError.compareAndSet(null, e)) {
|
|
752
|
+
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + item.getHref(), e);
|
|
753
|
+
// Cancel all in-flight downloads (fail-fast)
|
|
754
|
+
synchronized (activeCalls) {
|
|
755
|
+
for (Call activeCall : activeCalls) {
|
|
756
|
+
if (!activeCall.isCanceled() && !activeCall.isExecuted()) {
|
|
757
|
+
activeCall.cancel();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
latch.countDown();
|
|
763
|
+
|
|
764
|
+
// Check if this was the last download to complete
|
|
765
|
+
if (latch.getCount() == 0 && completionHandled.compareAndSet(false, true)) {
|
|
766
|
+
completionCallback.error(firstError.get());
|
|
767
|
+
}
|
|
541
768
|
}
|
|
542
769
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
// Stop if an exception occurred
|
|
547
|
-
if (exceptionReference.get() != null) {
|
|
548
|
-
break;
|
|
770
|
+
);
|
|
771
|
+
synchronized (activeCalls) {
|
|
772
|
+
activeCalls.add(call);
|
|
549
773
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private void downloadBundleOfTypeManifest(
|
|
778
|
+
@NonNull String bundleId,
|
|
779
|
+
@NonNull String downloadUrl,
|
|
780
|
+
@NonNull EmptyCallback completionCallback
|
|
781
|
+
) {
|
|
782
|
+
try {
|
|
783
|
+
// Create a temporary directory
|
|
784
|
+
File temporaryDirectory = createTemporaryDirectory();
|
|
785
|
+
|
|
786
|
+
// Download the latest manifest
|
|
787
|
+
downloadBundleFile(
|
|
788
|
+
downloadUrl,
|
|
789
|
+
manifestFileName,
|
|
790
|
+
temporaryDirectory,
|
|
791
|
+
null,
|
|
792
|
+
new EmptyCallback() {
|
|
793
|
+
@Override
|
|
794
|
+
public void success() {
|
|
795
|
+
try {
|
|
796
|
+
File latestManifestFile = new File(temporaryDirectory, manifestFileName);
|
|
797
|
+
Manifest latestManifest = loadManifest(latestManifestFile);
|
|
798
|
+
// Load the current manifest
|
|
799
|
+
Manifest currentManifest = loadCurrentManifest();
|
|
800
|
+
// Compare the manifests
|
|
801
|
+
List<ManifestItem> itemsToCopy = new ArrayList<>();
|
|
802
|
+
List<ManifestItem> itemsToDownload = new ArrayList<>();
|
|
803
|
+
if (currentManifest == null) {
|
|
804
|
+
itemsToDownload.addAll(latestManifest.getItems());
|
|
805
|
+
} else {
|
|
806
|
+
itemsToCopy.addAll(Manifest.findDuplicateItems(latestManifest, currentManifest));
|
|
807
|
+
itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
|
|
808
|
+
}
|
|
809
|
+
// Copy the files
|
|
810
|
+
List<ManifestItem> missingItems = copyCurrentBundleFilesAndReturnFailures(itemsToCopy, temporaryDirectory);
|
|
811
|
+
// If items could not be copied, add them to the list of items to download
|
|
812
|
+
if (!missingItems.isEmpty()) {
|
|
813
|
+
itemsToDownload.addAll(missingItems);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Download the files
|
|
817
|
+
downloadBundleFiles(
|
|
818
|
+
downloadUrl,
|
|
819
|
+
itemsToDownload,
|
|
820
|
+
temporaryDirectory,
|
|
821
|
+
(downloadedBytes, totalBytes) -> {
|
|
822
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(
|
|
823
|
+
bundleId,
|
|
824
|
+
downloadedBytes,
|
|
825
|
+
totalBytes
|
|
826
|
+
);
|
|
827
|
+
notifyDownloadBundleProgressListeners(event);
|
|
828
|
+
},
|
|
829
|
+
new EmptyCallback() {
|
|
830
|
+
@Override
|
|
831
|
+
public void success() {
|
|
832
|
+
try {
|
|
833
|
+
// Add the bundle
|
|
834
|
+
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
835
|
+
completionCallback.success();
|
|
836
|
+
} catch (Exception e) {
|
|
837
|
+
completionCallback.error(e);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@Override
|
|
842
|
+
public void error(@NonNull Exception exception) {
|
|
843
|
+
completionCallback.error(exception);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
} catch (Exception e) {
|
|
848
|
+
completionCallback.error(e);
|
|
558
849
|
}
|
|
559
|
-
});
|
|
560
|
-
if (callback != null) {
|
|
561
|
-
totalBytesDownloaded[0] += fileToDownload.getSizeInBytes();
|
|
562
|
-
callback.onProgress(totalBytesDownloaded[0], finalTotalBytesToDownload);
|
|
563
850
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
851
|
+
|
|
852
|
+
@Override
|
|
853
|
+
public void error(@NonNull Exception exception) {
|
|
854
|
+
completionCallback.error(exception);
|
|
855
|
+
}
|
|
567
856
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
// Wait for all threads to finish
|
|
573
|
-
for (Thread thread : threads) {
|
|
574
|
-
thread.join();
|
|
575
|
-
}
|
|
576
|
-
// Throw the exception if one occurred
|
|
577
|
-
Exception exception = exceptionReference.get();
|
|
578
|
-
if (exception != null) {
|
|
579
|
-
throw exception;
|
|
580
|
-
}
|
|
581
|
-
// Call the callback one last time to make sure the progress is at 100%
|
|
582
|
-
if (callback != null) {
|
|
583
|
-
callback.onProgress(totalBytesToDownload, totalBytesToDownload);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
private void downloadBundleOfTypeManifest(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
|
|
588
|
-
// Create a temporary directory
|
|
589
|
-
File temporaryDirectory = createTemporaryDirectory();
|
|
590
|
-
// Download the latest manifest
|
|
591
|
-
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory, null);
|
|
592
|
-
Manifest latestManifest = loadManifest(latestManifestFile);
|
|
593
|
-
// Load the current manifest
|
|
594
|
-
Manifest currentManifest = loadCurrentManifest();
|
|
595
|
-
// Compare the manifests
|
|
596
|
-
List<ManifestItem> itemsToCopy = new ArrayList<>();
|
|
597
|
-
List<ManifestItem> itemsToDownload = new ArrayList<>();
|
|
598
|
-
if (currentManifest == null) {
|
|
599
|
-
itemsToDownload.addAll(latestManifest.getItems());
|
|
600
|
-
} else {
|
|
601
|
-
itemsToCopy.addAll(Manifest.findDuplicateItems(latestManifest, currentManifest));
|
|
602
|
-
itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
|
|
603
|
-
}
|
|
604
|
-
// Copy the files
|
|
605
|
-
List<ManifestItem> missingItems = copyCurrentBundleFilesAndReturnFailures(itemsToCopy, temporaryDirectory);
|
|
606
|
-
// If items could not be copied, add them to the list of items to download
|
|
607
|
-
if (!missingItems.isEmpty()) {
|
|
608
|
-
itemsToDownload.addAll(missingItems);
|
|
609
|
-
}
|
|
610
|
-
// Download the files
|
|
611
|
-
downloadBundleFiles(downloadUrl, itemsToDownload, temporaryDirectory, (downloadedBytes, totalBytes) -> {
|
|
612
|
-
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
613
|
-
notifyDownloadBundleProgressListeners(event);
|
|
614
|
-
});
|
|
615
|
-
// Add the bundle
|
|
616
|
-
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
857
|
+
);
|
|
858
|
+
} catch (Exception e) {
|
|
859
|
+
completionCallback.error(e);
|
|
860
|
+
}
|
|
617
861
|
}
|
|
618
862
|
|
|
619
863
|
private void downloadBundleOfTypeZip(
|
|
620
864
|
@NonNull String bundleId,
|
|
621
865
|
@Nullable String checksum,
|
|
622
866
|
@Nullable String signature,
|
|
623
|
-
@NonNull String downloadUrl
|
|
624
|
-
|
|
867
|
+
@NonNull String downloadUrl,
|
|
868
|
+
@NonNull EmptyCallback completionCallback
|
|
869
|
+
) {
|
|
625
870
|
File file = buildTemporaryZipFile();
|
|
626
871
|
// Download the bundle
|
|
627
|
-
downloadAndVerifyFile(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
872
|
+
downloadAndVerifyFile(
|
|
873
|
+
downloadUrl,
|
|
874
|
+
file,
|
|
875
|
+
checksum,
|
|
876
|
+
signature,
|
|
877
|
+
(downloadedBytes, totalBytes) -> {
|
|
878
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
879
|
+
notifyDownloadBundleProgressListeners(event);
|
|
880
|
+
},
|
|
881
|
+
new EmptyCallback() {
|
|
882
|
+
@Override
|
|
883
|
+
public void success() {
|
|
884
|
+
try {
|
|
885
|
+
// Add the bundle
|
|
886
|
+
addBundleOfTypeZip(bundleId, file);
|
|
887
|
+
// Delete the temporary file
|
|
888
|
+
file.delete();
|
|
889
|
+
completionCallback.success();
|
|
890
|
+
} catch (Exception e) {
|
|
891
|
+
completionCallback.error(e);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
@Override
|
|
896
|
+
public void error(@NonNull Exception exception) {
|
|
897
|
+
// Delete the temporary file on error
|
|
898
|
+
file.delete();
|
|
899
|
+
completionCallback.error(exception);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
);
|
|
635
903
|
}
|
|
636
904
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
.
|
|
643
|
-
.
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
905
|
+
private void fetchLatestBundleInternal(
|
|
906
|
+
@NonNull FetchLatestBundleOptions options,
|
|
907
|
+
@NonNull NonEmptyCallback<GetLatestBundleResponse> callback
|
|
908
|
+
) {
|
|
909
|
+
try {
|
|
910
|
+
String channel = options.getChannel() == null ? getChannel() : options.getChannel();
|
|
911
|
+
String url = new HttpUrl.Builder()
|
|
912
|
+
.scheme("https")
|
|
913
|
+
.host(config.getServerDomain())
|
|
914
|
+
.addPathSegment("v1")
|
|
915
|
+
.addPathSegment("apps")
|
|
916
|
+
.addPathSegment(getAppId())
|
|
917
|
+
.addPathSegment("bundles")
|
|
918
|
+
.addPathSegment("latest")
|
|
919
|
+
.addQueryParameter("appVersionCode", getVersionCodeAsString())
|
|
920
|
+
.addQueryParameter("appVersionName", getVersionName())
|
|
921
|
+
.addQueryParameter("bundleId", getCurrentBundleId())
|
|
922
|
+
.addQueryParameter("channelName", channel)
|
|
923
|
+
.addQueryParameter("customId", preferences.getCustomId())
|
|
924
|
+
.addQueryParameter("deviceId", getDeviceId())
|
|
925
|
+
.addQueryParameter("osVersion", String.valueOf(Build.VERSION.SDK_INT))
|
|
926
|
+
.addQueryParameter("platform", "0")
|
|
927
|
+
.addQueryParameter("pluginVersion", LiveUpdatePlugin.VERSION)
|
|
928
|
+
.build()
|
|
929
|
+
.toString();
|
|
930
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Fetching latest bundle: " + url);
|
|
931
|
+
|
|
932
|
+
httpClient.enqueue(
|
|
933
|
+
url,
|
|
934
|
+
new NonEmptyCallback<Response>() {
|
|
935
|
+
@Override
|
|
936
|
+
public void success(@NonNull Response response) {
|
|
937
|
+
try {
|
|
938
|
+
String responseBodyString = response.body().string();
|
|
939
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Latest bundle response: " + responseBodyString);
|
|
940
|
+
if (response.isSuccessful()) {
|
|
941
|
+
JSONObject responseJson = new JSONObject(responseBodyString);
|
|
942
|
+
GetLatestBundleResponse result = new GetLatestBundleResponse(responseJson);
|
|
943
|
+
callback.success(result);
|
|
944
|
+
} else {
|
|
945
|
+
callback.success(null);
|
|
946
|
+
}
|
|
947
|
+
} catch (Exception e) {
|
|
948
|
+
callback.error(e);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
@Override
|
|
953
|
+
public void error(@NonNull Exception exception) {
|
|
954
|
+
callback.error(exception);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
);
|
|
958
|
+
} catch (Exception e) {
|
|
959
|
+
callback.error(e);
|
|
668
960
|
}
|
|
669
961
|
}
|
|
670
962
|
|
|
671
|
-
private String[]
|
|
963
|
+
private String[] getDownloadedBundleIds() {
|
|
672
964
|
File bundlesDirectory = buildBundlesDirectory();
|
|
673
965
|
File[] bundles = bundlesDirectory.listFiles();
|
|
674
966
|
if (bundles == null) {
|
|
@@ -708,6 +1000,15 @@ public class LiveUpdate {
|
|
|
708
1000
|
return checksum.toString();
|
|
709
1001
|
}
|
|
710
1002
|
|
|
1003
|
+
@Nullable
|
|
1004
|
+
private String getAppId() {
|
|
1005
|
+
String appId = preferences.getAppId();
|
|
1006
|
+
if (appId != null) {
|
|
1007
|
+
return appId;
|
|
1008
|
+
}
|
|
1009
|
+
return config.getAppId();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
711
1012
|
@Nullable
|
|
712
1013
|
private String getChannel() {
|
|
713
1014
|
String channel = null;
|
|
@@ -741,10 +1042,10 @@ public class LiveUpdate {
|
|
|
741
1042
|
|
|
742
1043
|
@NonNull
|
|
743
1044
|
private String getDeviceId() {
|
|
744
|
-
String deviceId = preferences.getDeviceIdForApp(
|
|
1045
|
+
String deviceId = preferences.getDeviceIdForApp(getAppId());
|
|
745
1046
|
if (deviceId == null) {
|
|
746
1047
|
deviceId = UUID.randomUUID().toString().toLowerCase();
|
|
747
|
-
preferences.setDeviceIdForApp(
|
|
1048
|
+
preferences.setDeviceIdForApp(getAppId(), deviceId);
|
|
748
1049
|
}
|
|
749
1050
|
return deviceId;
|
|
750
1051
|
}
|
|
@@ -766,10 +1067,15 @@ public class LiveUpdate {
|
|
|
766
1067
|
*/
|
|
767
1068
|
@NonNull
|
|
768
1069
|
private String getNextCapacitorServerPath() {
|
|
769
|
-
|
|
1070
|
+
String path = plugin
|
|
770
1071
|
.getContext()
|
|
771
1072
|
.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
|
|
772
1073
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
1074
|
+
// Empty path means default path
|
|
1075
|
+
if (path.isEmpty()) {
|
|
1076
|
+
path = defaultWebAssetDir;
|
|
1077
|
+
}
|
|
1078
|
+
return path;
|
|
773
1079
|
}
|
|
774
1080
|
|
|
775
1081
|
/**
|
|
@@ -780,8 +1086,12 @@ public class LiveUpdate {
|
|
|
780
1086
|
return preferences.getPreviousBundleId();
|
|
781
1087
|
}
|
|
782
1088
|
|
|
783
|
-
private
|
|
784
|
-
return
|
|
1089
|
+
private int getVersionCodeAsInt() throws PackageManager.NameNotFoundException {
|
|
1090
|
+
return getPackageInfo().versionCode;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
private String getVersionCodeAsString() throws PackageManager.NameNotFoundException {
|
|
1094
|
+
return String.valueOf(getVersionCodeAsInt());
|
|
785
1095
|
}
|
|
786
1096
|
|
|
787
1097
|
private String getVersionName() throws PackageManager.NameNotFoundException {
|
|
@@ -834,10 +1144,38 @@ public class LiveUpdate {
|
|
|
834
1144
|
return loadManifest(source);
|
|
835
1145
|
}
|
|
836
1146
|
|
|
837
|
-
|
|
1147
|
+
private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
838
1148
|
plugin.notifyDownloadBundleProgressListeners(event);
|
|
839
1149
|
}
|
|
840
1150
|
|
|
1151
|
+
private void performAutoUpdate() {
|
|
1152
|
+
// Check if enough time has passed since the last check
|
|
1153
|
+
long now = System.currentTimeMillis();
|
|
1154
|
+
if (lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs) {
|
|
1155
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update skipped. Last check was less than 15 minutes ago.");
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Update the timestamp
|
|
1160
|
+
lastAutoUpdateCheckTimestamp = now;
|
|
1161
|
+
|
|
1162
|
+
// Run sync
|
|
1163
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update started.");
|
|
1164
|
+
SyncOptions options = new SyncOptions((String) null);
|
|
1165
|
+
NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
|
|
1166
|
+
@Override
|
|
1167
|
+
public void success(@NonNull Result result) {
|
|
1168
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update completed successfully.");
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
@Override
|
|
1172
|
+
public void error(@NonNull Exception exception) {
|
|
1173
|
+
Logger.error(LiveUpdatePlugin.TAG, "Auto-update failed: " + exception.getMessage(), exception);
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
sync(options, callback);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
841
1179
|
private void rollback() {
|
|
842
1180
|
// Set the rollback flag
|
|
843
1181
|
rollbackPerformed = true;
|
|
@@ -899,6 +1237,8 @@ public class LiveUpdate {
|
|
|
899
1237
|
this.plugin.getBridge().setServerBasePath(path);
|
|
900
1238
|
}
|
|
901
1239
|
this.plugin.getBridge().reload();
|
|
1240
|
+
// Notify listeners
|
|
1241
|
+
notifyReloadedListeners();
|
|
902
1242
|
}
|
|
903
1243
|
|
|
904
1244
|
/**
|
|
@@ -911,6 +1251,58 @@ public class LiveUpdate {
|
|
|
911
1251
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
912
1252
|
setNextCapacitorServerPath(bundleDirectory.getPath());
|
|
913
1253
|
}
|
|
1254
|
+
|
|
1255
|
+
// Notify listeners
|
|
1256
|
+
notifyNextBundleSetListeners(bundleId);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
private void notifyNextBundleSetListeners(@Nullable String bundleId) {
|
|
1260
|
+
NextBundleSetEvent event = new NextBundleSetEvent(bundleId);
|
|
1261
|
+
plugin.notifyNextBundleSetListeners(event);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
private void notifyReloadedListeners() {
|
|
1265
|
+
plugin.notifyReloadedListeners();
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
private void addBlockedBundleId(@NonNull String bundleId) {
|
|
1269
|
+
String blockedIds = preferences.getBlockedBundleIds();
|
|
1270
|
+
List<String> blockedList = new ArrayList<>();
|
|
1271
|
+
|
|
1272
|
+
// Parse existing blocked IDs
|
|
1273
|
+
if (blockedIds != null && !blockedIds.isEmpty()) {
|
|
1274
|
+
String[] ids = blockedIds.split(",");
|
|
1275
|
+
blockedList.addAll(Arrays.asList(ids));
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Skip if already blocked
|
|
1279
|
+
if (blockedList.contains(bundleId)) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Remove oldest if limit reached
|
|
1284
|
+
if (blockedList.size() >= 100) {
|
|
1285
|
+
blockedList.remove(0);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Add new bundle
|
|
1289
|
+
blockedList.add(bundleId);
|
|
1290
|
+
|
|
1291
|
+
// Save back to preferences
|
|
1292
|
+
String newBlockedIds = String.join(",", blockedList);
|
|
1293
|
+
preferences.setBlockedBundleIds(newBlockedIds);
|
|
1294
|
+
|
|
1295
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Bundle blocked: " + bundleId);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
private boolean isBlockedBundleId(@NonNull String bundleId) {
|
|
1299
|
+
String blockedIds = preferences.getBlockedBundleIds();
|
|
1300
|
+
if (blockedIds == null || blockedIds.isEmpty()) {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
String[] ids = blockedIds.split(",");
|
|
1305
|
+
return Arrays.asList(ids).contains(bundleId);
|
|
914
1306
|
}
|
|
915
1307
|
|
|
916
1308
|
private void setNextCapacitorServerPath(@NonNull String path) {
|
|
@@ -918,9 +1310,20 @@ public class LiveUpdate {
|
|
|
918
1310
|
this.webViewSettingsEditor.commit();
|
|
919
1311
|
}
|
|
920
1312
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1313
|
+
private void checkAndResetConfigIfVersionChanged() throws PackageManager.NameNotFoundException {
|
|
1314
|
+
int currentVersionCode = getVersionCodeAsInt();
|
|
1315
|
+
int lastVersionCode = preferences.getLastVersionCode();
|
|
1316
|
+
|
|
1317
|
+
if (lastVersionCode == -1 || lastVersionCode != currentVersionCode) {
|
|
1318
|
+
Logger.debug(
|
|
1319
|
+
LiveUpdatePlugin.TAG,
|
|
1320
|
+
"App version changed (last: " + lastVersionCode + ", current: " + currentVersionCode + "), resetting config."
|
|
1321
|
+
);
|
|
1322
|
+
resetConfig();
|
|
1323
|
+
preferences.setLastVersionCode(currentVersionCode);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
924
1327
|
private void setPreviousBundleId(@Nullable String bundleId) {
|
|
925
1328
|
preferences.setPreviousBundleId(bundleId);
|
|
926
1329
|
}
|