@capawesome/capacitor-live-update 6.8.0 → 7.0.0-dev.2633ff2.1765953041
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 +3 -3
- package/Package.swift +32 -0
- package/README.md +438 -86
- package/android/build.gradle +12 -12
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +754 -279
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +29 -27
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +40 -7
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +210 -67
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +52 -3
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +11 -2
- 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/{results/GetBundleResult.java → events/NextBundleSetEvent.java} +9 -7
- 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/FetchLatestBundleResult.java +8 -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/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 +597 -102
- package/dist/esm/definitions.d.ts +338 -39
- 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 +9 -2
- package/dist/esm/web.js +21 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +22 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +22 -3
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Classes/Events/DownloadBundleProgressEvent.swift +25 -0
- package/ios/Plugin/Classes/{Results/GetBundleResult.swift → Events/NextBundleSetEvent.swift} +3 -3
- package/ios/Plugin/Classes/Options/SetConfigOptions.swift +14 -0
- package/ios/Plugin/Classes/Options/SyncOptions.swift +4 -0
- package/ios/Plugin/Classes/Results/{FetchLatestBundle.swift → FetchLatestBundleResult.swift} +6 -1
- 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/Classes/Results/ReadyResult.swift +22 -0
- package/ios/Plugin/LiveUpdate.swift +365 -149
- package/ios/Plugin/LiveUpdateConfig.swift +4 -4
- package/ios/Plugin/LiveUpdateHttpClient.swift +10 -4
- package/ios/Plugin/LiveUpdatePlugin.swift +136 -34
- package/ios/Plugin/LiveUpdatePreferences.swift +54 -12
- package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +1 -0
- package/package.json +19 -20
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetBundleOptions.java +0 -18
- package/ios/Plugin/Classes/Options/SetBundleOptions.swift +0 -14
- package/ios/Plugin/LiveUpdatePlugin.h +0 -10
- package/ios/Plugin/LiveUpdatePlugin.m +0 -27
|
@@ -10,31 +10,39 @@ 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;
|
|
20
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
|
|
18
21
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
|
|
19
22
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
|
|
20
23
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
|
|
21
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetBundleOptions;
|
|
22
24
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
|
|
25
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetConfigOptions;
|
|
23
26
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
|
|
24
27
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetNextBundleOptions;
|
|
25
28
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SyncOptions;
|
|
26
29
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
|
|
27
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.
|
|
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;
|
|
42
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.ReadyResult;
|
|
36
43
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
|
|
37
44
|
import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
|
|
45
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.DownloadProgressCallback;
|
|
38
46
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
|
|
39
47
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
|
|
40
48
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
|
|
@@ -52,8 +60,12 @@ import java.util.ArrayList;
|
|
|
52
60
|
import java.util.Arrays;
|
|
53
61
|
import java.util.List;
|
|
54
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;
|
|
55
66
|
import java.util.concurrent.atomic.AtomicReference;
|
|
56
67
|
import net.lingala.zip4j.ZipFile;
|
|
68
|
+
import okhttp3.Call;
|
|
57
69
|
import okhttp3.HttpUrl;
|
|
58
70
|
import okhttp3.Response;
|
|
59
71
|
import okhttp3.ResponseBody;
|
|
@@ -65,13 +77,12 @@ import org.json.JSONObject;
|
|
|
65
77
|
|
|
66
78
|
public class LiveUpdate {
|
|
67
79
|
|
|
80
|
+
private final long autoUpdateIntervalMs = 15 * 60 * 1000; // 15 minutes
|
|
81
|
+
|
|
68
82
|
@NonNull
|
|
69
83
|
private final LiveUpdateConfig config;
|
|
70
84
|
|
|
71
|
-
private final String defaultWebAssetDir;
|
|
72
|
-
|
|
73
|
-
@NonNull
|
|
74
|
-
private final String host;
|
|
85
|
+
private final String defaultWebAssetDir = Bridge.DEFAULT_WEB_ASSET_DIR;
|
|
75
86
|
|
|
76
87
|
@NonNull
|
|
77
88
|
private final LiveUpdateHttpClient httpClient;
|
|
@@ -89,44 +100,44 @@ public class LiveUpdate {
|
|
|
89
100
|
private final Handler rollbackHandler = new Handler();
|
|
90
101
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
91
102
|
|
|
103
|
+
private boolean initialPageLoaded = false;
|
|
104
|
+
private long lastAutoUpdateCheckTimestamp = 0;
|
|
105
|
+
private boolean rollbackPerformed = false;
|
|
106
|
+
private boolean syncInProgress = false;
|
|
107
|
+
|
|
92
108
|
public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
|
|
93
109
|
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
110
|
this.httpClient = new LiveUpdateHttpClient(config);
|
|
101
111
|
this.plugin = plugin;
|
|
102
112
|
this.preferences = new LiveUpdatePreferences(plugin.getContext());
|
|
103
113
|
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
// Check version and reset config if version changed
|
|
116
|
+
checkAndResetConfigIfVersionChanged();
|
|
117
|
+
|
|
118
|
+
// Start the rollback timer to rollback to the default bundle
|
|
119
|
+
// if the app is not ready after a certain time
|
|
120
|
+
startRollbackTimer();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public void clearBlockedBundles() {
|
|
124
|
+
preferences.setBlockedBundleIds(null);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
|
|
117
128
|
String bundleId = options.getBundleId();
|
|
118
129
|
|
|
119
|
-
if (!
|
|
130
|
+
if (!hasBundleById(bundleId)) {
|
|
120
131
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
121
132
|
callback.error(exception);
|
|
122
133
|
return;
|
|
123
134
|
}
|
|
124
|
-
|
|
135
|
+
deleteBundleById(bundleId);
|
|
125
136
|
|
|
126
137
|
callback.success();
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback)
|
|
140
|
+
public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback) {
|
|
130
141
|
ArtifactType artifactType = options.getArtifactType();
|
|
131
142
|
String bundleId = options.getBundleId();
|
|
132
143
|
String checksum = options.getChecksum();
|
|
@@ -134,7 +145,7 @@ public class LiveUpdate {
|
|
|
134
145
|
String url = options.getUrl();
|
|
135
146
|
|
|
136
147
|
// Check if the bundle already exists
|
|
137
|
-
if (
|
|
148
|
+
if (hasBundleById(bundleId)) {
|
|
138
149
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_EXISTS);
|
|
139
150
|
callback.error(exception);
|
|
140
151
|
return;
|
|
@@ -142,50 +153,82 @@ public class LiveUpdate {
|
|
|
142
153
|
|
|
143
154
|
// Download the bundle
|
|
144
155
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
145
|
-
downloadBundleOfTypeManifest(bundleId, url);
|
|
156
|
+
downloadBundleOfTypeManifest(bundleId, url, callback);
|
|
146
157
|
} else {
|
|
147
|
-
downloadBundleOfTypeZip(bundleId,
|
|
158
|
+
downloadBundleOfTypeZip(bundleId, checksum, signature, url, callback);
|
|
148
159
|
}
|
|
149
|
-
callback.success();
|
|
150
160
|
}
|
|
151
161
|
|
|
152
|
-
public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback callback)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
}
|
|
190
|
+
);
|
|
161
191
|
}
|
|
162
192
|
|
|
163
|
-
public void
|
|
164
|
-
String
|
|
165
|
-
|
|
166
|
-
|
|
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(",");
|
|
167
200
|
}
|
|
168
|
-
|
|
201
|
+
GetBlockedBundlesResult result = new GetBlockedBundlesResult(bundleIds);
|
|
169
202
|
callback.success(result);
|
|
170
203
|
}
|
|
171
204
|
|
|
172
205
|
public void getBundles(@NonNull NonEmptyCallback callback) {
|
|
173
|
-
String[] bundleIds =
|
|
206
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
174
207
|
GetBundlesResult result = new GetBundlesResult(bundleIds);
|
|
175
208
|
callback.success(result);
|
|
176
209
|
}
|
|
177
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
|
+
|
|
178
217
|
public void getChannel(@NonNull NonEmptyCallback callback) {
|
|
179
218
|
String channel = getChannel();
|
|
180
219
|
GetChannelResult result = new GetChannelResult(channel);
|
|
181
220
|
callback.success(result);
|
|
182
221
|
}
|
|
183
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
|
+
|
|
184
230
|
public void getCurrentBundle(@NonNull NonEmptyCallback<GetCurrentBundleResult> callback) {
|
|
185
231
|
String bundleId = getCurrentBundleId();
|
|
186
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
187
|
-
bundleId = null;
|
|
188
|
-
}
|
|
189
232
|
GetCurrentBundleResult result = new GetCurrentBundleResult(bundleId);
|
|
190
233
|
callback.success(result);
|
|
191
234
|
}
|
|
@@ -204,15 +247,12 @@ public class LiveUpdate {
|
|
|
204
247
|
|
|
205
248
|
public void getNextBundle(@NonNull NonEmptyCallback<GetNextBundleResult> callback) {
|
|
206
249
|
String bundleId = getNextBundleId();
|
|
207
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
208
|
-
bundleId = null;
|
|
209
|
-
}
|
|
210
250
|
GetNextBundleResult result = new GetNextBundleResult(bundleId);
|
|
211
251
|
callback.success(result);
|
|
212
252
|
}
|
|
213
253
|
|
|
214
254
|
public void getVersionCode(@NonNull NonEmptyCallback callback) throws PackageManager.NameNotFoundException {
|
|
215
|
-
String versionCode =
|
|
255
|
+
String versionCode = getVersionCodeAsString();
|
|
216
256
|
GetVersionCodeResult result = new GetVersionCodeResult(versionCode);
|
|
217
257
|
callback.success(result);
|
|
218
258
|
}
|
|
@@ -223,14 +263,51 @@ public class LiveUpdate {
|
|
|
223
263
|
callback.success(result);
|
|
224
264
|
}
|
|
225
265
|
|
|
226
|
-
public void
|
|
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
|
+
|
|
286
|
+
public void ready(@NonNull NonEmptyCallback callback) {
|
|
227
287
|
Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
|
|
288
|
+
if (config.getReadyTimeout() <= 0) {
|
|
289
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
|
|
290
|
+
}
|
|
228
291
|
// Stop the rollback timer
|
|
229
292
|
stopRollbackTimer();
|
|
230
293
|
// Delete unused bundles
|
|
231
294
|
if (config.getAutoDeleteBundles()) {
|
|
232
295
|
deleteUnusedBundles();
|
|
233
296
|
}
|
|
297
|
+
// Get the current and previous bundle IDs
|
|
298
|
+
String currentBundleId = getCurrentBundleId();
|
|
299
|
+
String previousBundleId = getPreviousBundleId();
|
|
300
|
+
// Block the rolled back bundle if enabled
|
|
301
|
+
if (config.getAutoBlockRolledBackBundles() && rollbackPerformed && previousBundleId != null) {
|
|
302
|
+
addBlockedBundleId(previousBundleId);
|
|
303
|
+
}
|
|
304
|
+
// Return the result
|
|
305
|
+
ReadyResult result = new ReadyResult(currentBundleId, previousBundleId, rollbackPerformed);
|
|
306
|
+
callback.success(result);
|
|
307
|
+
// Set the new previous bundle ID
|
|
308
|
+
setPreviousBundleId(currentBundleId);
|
|
309
|
+
// Reset the rollback flag
|
|
310
|
+
rollbackPerformed = false;
|
|
234
311
|
}
|
|
235
312
|
|
|
236
313
|
public void reload() {
|
|
@@ -240,20 +317,11 @@ public class LiveUpdate {
|
|
|
240
317
|
}
|
|
241
318
|
|
|
242
319
|
public void reset() {
|
|
243
|
-
|
|
320
|
+
setNextBundleById(null);
|
|
244
321
|
}
|
|
245
322
|
|
|
246
|
-
public void
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (!hasBundle(bundleId)) {
|
|
250
|
-
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
251
|
-
callback.error(exception);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
setNextBundle(bundleId);
|
|
256
|
-
callback.success();
|
|
323
|
+
public void resetConfig() {
|
|
324
|
+
preferences.setAppId(null);
|
|
257
325
|
}
|
|
258
326
|
|
|
259
327
|
public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
|
|
@@ -263,6 +331,11 @@ public class LiveUpdate {
|
|
|
263
331
|
callback.success();
|
|
264
332
|
}
|
|
265
333
|
|
|
334
|
+
public void setConfig(@NonNull SetConfigOptions options) {
|
|
335
|
+
String appId = options.getAppId();
|
|
336
|
+
preferences.setAppId(appId);
|
|
337
|
+
}
|
|
338
|
+
|
|
266
339
|
public void setCustomId(@NonNull SetCustomIdOptions options, @NonNull EmptyCallback callback) {
|
|
267
340
|
String customId = options.getCustomId();
|
|
268
341
|
|
|
@@ -276,8 +349,8 @@ public class LiveUpdate {
|
|
|
276
349
|
if (bundleId == null) {
|
|
277
350
|
reset();
|
|
278
351
|
} else {
|
|
279
|
-
if (
|
|
280
|
-
|
|
352
|
+
if (hasBundleById(bundleId)) {
|
|
353
|
+
setNextBundleById(bundleId);
|
|
281
354
|
} else {
|
|
282
355
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
283
356
|
callback.error(exception);
|
|
@@ -287,45 +360,99 @@ public class LiveUpdate {
|
|
|
287
360
|
callback.success();
|
|
288
361
|
}
|
|
289
362
|
|
|
290
|
-
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
|
+
|
|
291
371
|
String channel = options.getChannel();
|
|
292
372
|
// Fetch the latest bundle
|
|
293
373
|
FetchLatestBundleOptions fetchLatestBundleOptions = new FetchLatestBundleOptions(channel);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
+
}
|
|
314
454
|
}
|
|
315
|
-
|
|
316
|
-
callback.success(syncResult);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
// Download the bundle
|
|
320
|
-
if (artifactType == ArtifactType.MANIFEST) {
|
|
321
|
-
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
322
|
-
} else {
|
|
323
|
-
downloadBundleOfTypeZip(latestBundleId, url, checksum, signature);
|
|
324
|
-
}
|
|
325
|
-
// Set the next bundle
|
|
326
|
-
setNextBundle(latestBundleId);
|
|
327
|
-
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
328
|
-
callback.success(syncResult);
|
|
455
|
+
);
|
|
329
456
|
}
|
|
330
457
|
|
|
331
458
|
private void addBundle(@NonNull String bundleId, @NonNull File sourceDirectory) throws Exception {
|
|
@@ -372,9 +499,10 @@ public class LiveUpdate {
|
|
|
372
499
|
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
373
500
|
}
|
|
374
501
|
|
|
375
|
-
private void copyCurrentBundleFile(@NonNull
|
|
502
|
+
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
503
|
+
String href = fileToCopy.getHref();
|
|
376
504
|
String currentBundleId = getCurrentBundleId();
|
|
377
|
-
if (currentBundleId
|
|
505
|
+
if (currentBundleId == null) {
|
|
378
506
|
// Create the source input stream
|
|
379
507
|
AssetManager assets = plugin.getContext().getAssets();
|
|
380
508
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
|
|
@@ -396,10 +524,20 @@ public class LiveUpdate {
|
|
|
396
524
|
}
|
|
397
525
|
}
|
|
398
526
|
|
|
399
|
-
private
|
|
400
|
-
|
|
401
|
-
|
|
527
|
+
private List<ManifestItem> copyCurrentBundleFilesAndReturnFailures(
|
|
528
|
+
@NonNull List<ManifestItem> filesToCopy,
|
|
529
|
+
@NonNull File destinationDirectory
|
|
530
|
+
) {
|
|
531
|
+
List<ManifestItem> missingItems = new ArrayList<>();
|
|
532
|
+
for (ManifestItem fileToCopy : filesToCopy) {
|
|
533
|
+
boolean success = tryCopyCurrentBundleFile(fileToCopy, destinationDirectory);
|
|
534
|
+
if (!success) {
|
|
535
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Failed to copy file: " + fileToCopy.getHref());
|
|
536
|
+
// If the file could not be copied, add it to the list of missing items
|
|
537
|
+
missingItems.add(fileToCopy);
|
|
538
|
+
}
|
|
402
539
|
}
|
|
540
|
+
return missingItems;
|
|
403
541
|
}
|
|
404
542
|
|
|
405
543
|
private void copyFile(File input, File output) throws IOException {
|
|
@@ -451,14 +589,14 @@ public class LiveUpdate {
|
|
|
451
589
|
return file;
|
|
452
590
|
}
|
|
453
591
|
|
|
454
|
-
private void
|
|
592
|
+
private void deleteBundleById(@NonNull String bundleId) {
|
|
455
593
|
// Delete the bundle directory
|
|
456
594
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
457
595
|
deleteFileRecursively(bundleDirectory);
|
|
458
596
|
// Reset the next bundle if it is the deleted bundle
|
|
459
597
|
String nextBundleId = getNextBundleId();
|
|
460
598
|
if (bundleId.equals(nextBundleId)) {
|
|
461
|
-
|
|
599
|
+
setNextBundleById(null);
|
|
462
600
|
}
|
|
463
601
|
}
|
|
464
602
|
|
|
@@ -472,170 +610,357 @@ public class LiveUpdate {
|
|
|
472
610
|
}
|
|
473
611
|
|
|
474
612
|
private void deleteUnusedBundles() {
|
|
475
|
-
String[] bundleIds =
|
|
613
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
476
614
|
for (String bundleId : bundleIds) {
|
|
477
615
|
if (!isBundleInUse(bundleId)) {
|
|
478
|
-
|
|
616
|
+
deleteBundleById(bundleId);
|
|
479
617
|
}
|
|
480
618
|
}
|
|
481
619
|
}
|
|
482
620
|
|
|
483
|
-
private
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
621
|
+
private Call downloadAndVerifyFile(
|
|
622
|
+
@NonNull String url,
|
|
623
|
+
@NonNull File file,
|
|
624
|
+
@Nullable String checksum,
|
|
625
|
+
@Nullable String signature,
|
|
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
|
+
}
|
|
661
|
+
}
|
|
662
|
+
);
|
|
498
663
|
}
|
|
499
664
|
|
|
500
|
-
private
|
|
665
|
+
private Call downloadBundleFile(
|
|
666
|
+
@NonNull String baseUrl,
|
|
667
|
+
@NonNull String href,
|
|
668
|
+
@NonNull File destinationDirectory,
|
|
669
|
+
@Nullable DownloadProgressCallback progressCallback,
|
|
670
|
+
@NonNull EmptyCallback completionCallback
|
|
671
|
+
) {
|
|
501
672
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
|
|
502
673
|
urlBuilder.addQueryParameter("href", href);
|
|
503
674
|
String url = urlBuilder.build().toString();
|
|
504
675
|
|
|
505
676
|
File destinationFile = new File(destinationDirectory, href);
|
|
506
677
|
destinationFile.getParentFile().mkdirs();
|
|
507
|
-
downloadAndVerifyFile(url, destinationFile, null, null);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
678
|
+
return downloadAndVerifyFile(url, destinationFile, null, null, progressCallback, completionCallback);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private void downloadBundleFiles(
|
|
682
|
+
@NonNull String baseUrl,
|
|
683
|
+
@NonNull List<ManifestItem> filesToDownload,
|
|
684
|
+
@NonNull File destinationDirectory,
|
|
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);
|
|
698
|
+
long totalBytesToDownload = 0;
|
|
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
|
+
}
|
|
524
768
|
}
|
|
525
769
|
}
|
|
526
|
-
|
|
527
|
-
|
|
770
|
+
);
|
|
771
|
+
synchronized (activeCalls) {
|
|
772
|
+
activeCalls.add(call);
|
|
528
773
|
}
|
|
529
|
-
// Stop if an exception occurred
|
|
530
|
-
if (exceptionReference.get() != null) {
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
// Start a new thread
|
|
534
|
-
Thread thread = new Thread(() -> {
|
|
535
|
-
try {
|
|
536
|
-
downloadBundleFile(baseUrl, href, destinationDirectory);
|
|
537
|
-
} catch (Exception exception) {
|
|
538
|
-
exceptionReference.set(exception);
|
|
539
|
-
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + href, exception);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
threads.add(thread);
|
|
543
|
-
thread.start();
|
|
544
|
-
}
|
|
545
|
-
// Wait for all threads to finish
|
|
546
|
-
for (Thread thread : threads) {
|
|
547
|
-
thread.join();
|
|
548
|
-
}
|
|
549
|
-
// Throw the exception if one occurred
|
|
550
|
-
Exception exception = exceptionReference.get();
|
|
551
|
-
if (exception != null) {
|
|
552
|
-
throw exception;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
private void downloadBundleOfTypeManifest(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
|
|
557
|
-
// Create a temporary directory
|
|
558
|
-
File temporaryDirectory = createTemporaryDirectory();
|
|
559
|
-
// Download the latest manifest
|
|
560
|
-
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory);
|
|
561
|
-
Manifest latestManifest = loadManifest(latestManifestFile);
|
|
562
|
-
// Load the current manifest
|
|
563
|
-
Manifest currentManifest = loadCurrentManifest();
|
|
564
|
-
// Compare the manifests
|
|
565
|
-
List<ManifestItem> itemsToCopy = new ArrayList<>();
|
|
566
|
-
List<ManifestItem> itemsToDownload = new ArrayList<>();
|
|
567
|
-
if (currentManifest == null) {
|
|
568
|
-
itemsToDownload.addAll(latestManifest.getItems());
|
|
569
|
-
} else {
|
|
570
|
-
itemsToCopy.addAll(Manifest.findDuplicateItems(latestManifest, currentManifest));
|
|
571
|
-
itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
|
|
572
774
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
@Override
|
|
853
|
+
public void error(@NonNull Exception exception) {
|
|
854
|
+
completionCallback.error(exception);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
} catch (Exception e) {
|
|
859
|
+
completionCallback.error(e);
|
|
583
860
|
}
|
|
584
|
-
downloadBundleFiles(downloadUrl, hrefsToDownload, temporaryDirectory);
|
|
585
|
-
// Add the bundle
|
|
586
|
-
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
587
861
|
}
|
|
588
862
|
|
|
589
863
|
private void downloadBundleOfTypeZip(
|
|
590
864
|
@NonNull String bundleId,
|
|
591
|
-
@NonNull String downloadUrl,
|
|
592
865
|
@Nullable String checksum,
|
|
593
|
-
@Nullable String signature
|
|
594
|
-
|
|
866
|
+
@Nullable String signature,
|
|
867
|
+
@NonNull String downloadUrl,
|
|
868
|
+
@NonNull EmptyCallback completionCallback
|
|
869
|
+
) {
|
|
595
870
|
File file = buildTemporaryZipFile();
|
|
596
871
|
// Download the bundle
|
|
597
|
-
downloadAndVerifyFile(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
+
);
|
|
602
903
|
}
|
|
603
904
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
.
|
|
610
|
-
.
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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);
|
|
635
960
|
}
|
|
636
961
|
}
|
|
637
962
|
|
|
638
|
-
private String[]
|
|
963
|
+
private String[] getDownloadedBundleIds() {
|
|
639
964
|
File bundlesDirectory = buildBundlesDirectory();
|
|
640
965
|
File[] bundles = bundlesDirectory.listFiles();
|
|
641
966
|
if (bundles == null) {
|
|
@@ -675,6 +1000,15 @@ public class LiveUpdate {
|
|
|
675
1000
|
return checksum.toString();
|
|
676
1001
|
}
|
|
677
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
|
+
|
|
678
1012
|
@Nullable
|
|
679
1013
|
private String getChannel() {
|
|
680
1014
|
String channel = null;
|
|
@@ -688,12 +1022,13 @@ public class LiveUpdate {
|
|
|
688
1022
|
}
|
|
689
1023
|
|
|
690
1024
|
/**
|
|
691
|
-
* @return The current bundle ID
|
|
1025
|
+
* @return The current bundle ID or `null` if the default bundle is in use.
|
|
692
1026
|
*/
|
|
1027
|
+
@Nullable
|
|
693
1028
|
private String getCurrentBundleId() {
|
|
694
1029
|
String currentPath = getCurrentCapacitorServerPath();
|
|
695
1030
|
if (currentPath.equals(defaultWebAssetDir)) {
|
|
696
|
-
return
|
|
1031
|
+
return null;
|
|
697
1032
|
}
|
|
698
1033
|
return new File(currentPath).getName();
|
|
699
1034
|
}
|
|
@@ -707,22 +1042,22 @@ public class LiveUpdate {
|
|
|
707
1042
|
|
|
708
1043
|
@NonNull
|
|
709
1044
|
private String getDeviceId() {
|
|
710
|
-
String deviceId = preferences.getDeviceIdForApp(
|
|
1045
|
+
String deviceId = preferences.getDeviceIdForApp(getAppId());
|
|
711
1046
|
if (deviceId == null) {
|
|
712
1047
|
deviceId = UUID.randomUUID().toString().toLowerCase();
|
|
713
|
-
preferences.setDeviceIdForApp(
|
|
1048
|
+
preferences.setDeviceIdForApp(getAppId(), deviceId);
|
|
714
1049
|
}
|
|
715
1050
|
return deviceId;
|
|
716
1051
|
}
|
|
717
1052
|
|
|
718
1053
|
/**
|
|
719
|
-
* @return The next bundle ID
|
|
1054
|
+
* @return The next bundle ID or `null` if the default bundle will be used.
|
|
720
1055
|
*/
|
|
721
|
-
@
|
|
1056
|
+
@Nullable
|
|
722
1057
|
private String getNextBundleId() {
|
|
723
1058
|
String nextPath = getNextCapacitorServerPath();
|
|
724
1059
|
if (nextPath.equals(defaultWebAssetDir)) {
|
|
725
|
-
return
|
|
1060
|
+
return null;
|
|
726
1061
|
}
|
|
727
1062
|
return new File(nextPath).getName();
|
|
728
1063
|
}
|
|
@@ -732,31 +1067,54 @@ public class LiveUpdate {
|
|
|
732
1067
|
*/
|
|
733
1068
|
@NonNull
|
|
734
1069
|
private String getNextCapacitorServerPath() {
|
|
735
|
-
|
|
1070
|
+
String path = plugin
|
|
736
1071
|
.getContext()
|
|
737
1072
|
.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
|
|
738
1073
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
1074
|
+
// Empty path means default path
|
|
1075
|
+
if (path.isEmpty()) {
|
|
1076
|
+
path = defaultWebAssetDir;
|
|
1077
|
+
}
|
|
1078
|
+
return path;
|
|
739
1079
|
}
|
|
740
1080
|
|
|
741
|
-
|
|
742
|
-
|
|
1081
|
+
/**
|
|
1082
|
+
* @return The previous bundle ID or `null` if the default bundle was used.
|
|
1083
|
+
*/
|
|
1084
|
+
@Nullable
|
|
1085
|
+
private String getPreviousBundleId() {
|
|
1086
|
+
return preferences.getPreviousBundleId();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
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());
|
|
743
1095
|
}
|
|
744
1096
|
|
|
745
1097
|
private String getVersionName() throws PackageManager.NameNotFoundException {
|
|
746
1098
|
return getPackageInfo().versionName;
|
|
747
1099
|
}
|
|
748
1100
|
|
|
749
|
-
private boolean
|
|
1101
|
+
private boolean hasBundleById(@NonNull String bundleId) {
|
|
750
1102
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
751
1103
|
return bundleDirectory.exists();
|
|
752
1104
|
}
|
|
753
1105
|
|
|
1106
|
+
private boolean isBundleInUse(@NonNull String bundleId) {
|
|
1107
|
+
String currentBundleId = getCurrentBundleId();
|
|
1108
|
+
String nextBundleId = getNextBundleId();
|
|
1109
|
+
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
754
1112
|
@Nullable
|
|
755
1113
|
private Manifest loadCurrentManifest() throws Exception {
|
|
756
1114
|
String currentBundleId = getCurrentBundleId();
|
|
757
|
-
if (currentBundleId
|
|
1115
|
+
if (currentBundleId == null) {
|
|
758
1116
|
AssetManager assets = plugin.getContext().getAssets();
|
|
759
|
-
|
|
1117
|
+
boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
|
|
760
1118
|
if (manifestFileExists) {
|
|
761
1119
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
|
|
762
1120
|
BufferedSource source = Okio.buffer(Okio.source(inputStream));
|
|
@@ -786,25 +1144,53 @@ public class LiveUpdate {
|
|
|
786
1144
|
return loadManifest(source);
|
|
787
1145
|
}
|
|
788
1146
|
|
|
789
|
-
private
|
|
790
|
-
|
|
791
|
-
String nextBundleId = getNextBundleId();
|
|
792
|
-
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
1147
|
+
private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
1148
|
+
plugin.notifyDownloadBundleProgressListeners(event);
|
|
793
1149
|
}
|
|
794
1150
|
|
|
795
|
-
private void
|
|
796
|
-
if
|
|
797
|
-
|
|
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.");
|
|
798
1156
|
return;
|
|
799
1157
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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);
|
|
803
1177
|
}
|
|
804
1178
|
|
|
805
|
-
private void
|
|
806
|
-
|
|
807
|
-
|
|
1179
|
+
private void rollback() {
|
|
1180
|
+
// Set the rollback flag
|
|
1181
|
+
rollbackPerformed = true;
|
|
1182
|
+
// Set the new previous bundle ID
|
|
1183
|
+
String currentBundleId = getCurrentBundleId();
|
|
1184
|
+
setPreviousBundleId(currentBundleId);
|
|
1185
|
+
// Log the rollback result
|
|
1186
|
+
if (currentBundleId == null) {
|
|
1187
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
|
|
1188
|
+
} else {
|
|
1189
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
|
|
1190
|
+
// Rollback to the default bundle
|
|
1191
|
+
setNextBundleById(null);
|
|
1192
|
+
setCurrentBundleById(null);
|
|
1193
|
+
}
|
|
808
1194
|
}
|
|
809
1195
|
|
|
810
1196
|
@Nullable
|
|
@@ -832,6 +1218,18 @@ public class LiveUpdate {
|
|
|
832
1218
|
return null;
|
|
833
1219
|
}
|
|
834
1220
|
|
|
1221
|
+
/**
|
|
1222
|
+
* @param bundleId The bundle ID to set as the current bundle. If `null`, the default bundle will be used.
|
|
1223
|
+
*/
|
|
1224
|
+
private void setCurrentBundleById(@Nullable String bundleId) {
|
|
1225
|
+
if (bundleId == null) {
|
|
1226
|
+
setCurrentCapacitorServerPath(defaultWebAssetDir);
|
|
1227
|
+
} else {
|
|
1228
|
+
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
1229
|
+
setCurrentCapacitorServerPath(bundleDirectory.getPath());
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
835
1233
|
private void setCurrentCapacitorServerPath(@NonNull String path) {
|
|
836
1234
|
if (path.equals(defaultWebAssetDir)) {
|
|
837
1235
|
this.plugin.getBridge().setServerAssetPath(path);
|
|
@@ -839,15 +1237,72 @@ public class LiveUpdate {
|
|
|
839
1237
|
this.plugin.getBridge().setServerBasePath(path);
|
|
840
1238
|
}
|
|
841
1239
|
this.plugin.getBridge().reload();
|
|
1240
|
+
// Notify listeners
|
|
1241
|
+
notifyReloadedListeners();
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* @param bundleId The bundle ID to set as the next bundle. If `null`, the default bundle will be used.
|
|
1246
|
+
*/
|
|
1247
|
+
private void setNextBundleById(@Nullable String bundleId) {
|
|
1248
|
+
if (bundleId == null) {
|
|
1249
|
+
setNextCapacitorServerPath(defaultWebAssetDir);
|
|
1250
|
+
} else {
|
|
1251
|
+
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
1252
|
+
setNextCapacitorServerPath(bundleDirectory.getPath());
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// Notify listeners
|
|
1256
|
+
notifyNextBundleSetListeners(bundleId);
|
|
842
1257
|
}
|
|
843
1258
|
|
|
844
|
-
private void
|
|
845
|
-
|
|
1259
|
+
private void notifyNextBundleSetListeners(@Nullable String bundleId) {
|
|
1260
|
+
NextBundleSetEvent event = new NextBundleSetEvent(bundleId);
|
|
1261
|
+
plugin.notifyNextBundleSetListeners(event);
|
|
846
1262
|
}
|
|
847
1263
|
|
|
848
|
-
private void
|
|
849
|
-
|
|
850
|
-
|
|
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);
|
|
851
1306
|
}
|
|
852
1307
|
|
|
853
1308
|
private void setNextCapacitorServerPath(@NonNull String path) {
|
|
@@ -855,11 +1310,28 @@ public class LiveUpdate {
|
|
|
855
1310
|
this.webViewSettingsEditor.commit();
|
|
856
1311
|
}
|
|
857
1312
|
|
|
858
|
-
private void
|
|
859
|
-
|
|
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
|
+
|
|
1327
|
+
private void setPreviousBundleId(@Nullable String bundleId) {
|
|
1328
|
+
preferences.setPreviousBundleId(bundleId);
|
|
860
1329
|
}
|
|
861
1330
|
|
|
862
1331
|
private void startRollbackTimer() {
|
|
1332
|
+
if (config.getReadyTimeout() <= 0) {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
863
1335
|
stopRollbackTimer();
|
|
864
1336
|
rollbackHandler.postDelayed(() -> rollback(), config.getReadyTimeout());
|
|
865
1337
|
}
|
|
@@ -868,6 +1340,15 @@ public class LiveUpdate {
|
|
|
868
1340
|
rollbackHandler.removeCallbacksAndMessages(null);
|
|
869
1341
|
}
|
|
870
1342
|
|
|
1343
|
+
private boolean tryCopyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) {
|
|
1344
|
+
try {
|
|
1345
|
+
copyCurrentBundleFile(fileToCopy, destinationDirectory);
|
|
1346
|
+
return true;
|
|
1347
|
+
} catch (IOException exception) {
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
871
1352
|
private File unzipFile(@NonNull File zipFile) throws IOException {
|
|
872
1353
|
File destination = buildTemporaryDirectory();
|
|
873
1354
|
String destinationPath = destination.getPath();
|
|
@@ -926,12 +1407,6 @@ public class LiveUpdate {
|
|
|
926
1407
|
}
|
|
927
1408
|
}
|
|
928
1409
|
|
|
929
|
-
private boolean wasUpdated() throws PackageManager.NameNotFoundException {
|
|
930
|
-
int lastVersionCode = preferences.getLastVersionCode();
|
|
931
|
-
int currentVersionCode = getPackageInfo().versionCode;
|
|
932
|
-
return lastVersionCode != currentVersionCode;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
1410
|
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
|
936
1411
|
String packageName = this.plugin.getContext().getPackageName();
|
|
937
1412
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|