@capawesome/capacitor-live-update 6.7.2 → 7.0.0-dev.1765885034
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 +419 -83
- package/android/build.gradle +12 -12
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +758 -273
- 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 +212 -68
- 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 +27 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/DownloadBundleProgressEvent.java +32 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/{results/GetBundleResult.java → events/NextBundleSetEvent.java} +9 -7
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +16 -1
- 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 +29 -1
- 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 +630 -103
- package/dist/esm/definitions.d.ts +365 -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/DownloadBundleOptions.swift +7 -1
- 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} +16 -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 +370 -151
- package/ios/Plugin/LiveUpdateConfig.swift +4 -4
- package/ios/Plugin/LiveUpdateHttpClient.swift +10 -4
- package/ios/Plugin/LiveUpdatePlugin.swift +138 -35
- package/ios/Plugin/LiveUpdatePreferences.swift +56 -14
- package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +3 -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,51 +100,52 @@ 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();
|
|
144
|
+
String signature = options.getSignature();
|
|
133
145
|
String url = options.getUrl();
|
|
134
146
|
|
|
135
147
|
// Check if the bundle already exists
|
|
136
|
-
if (
|
|
148
|
+
if (hasBundleById(bundleId)) {
|
|
137
149
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_EXISTS);
|
|
138
150
|
callback.error(exception);
|
|
139
151
|
return;
|
|
@@ -141,48 +153,82 @@ public class LiveUpdate {
|
|
|
141
153
|
|
|
142
154
|
// Download the bundle
|
|
143
155
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
144
|
-
downloadBundleOfTypeManifest(bundleId, url);
|
|
156
|
+
downloadBundleOfTypeManifest(bundleId, url, callback);
|
|
145
157
|
} else {
|
|
146
|
-
downloadBundleOfTypeZip(bundleId, url,
|
|
158
|
+
downloadBundleOfTypeZip(bundleId, checksum, signature, url, callback);
|
|
147
159
|
}
|
|
148
|
-
callback.success();
|
|
149
160
|
}
|
|
150
161
|
|
|
151
|
-
public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback callback)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
);
|
|
158
191
|
}
|
|
159
192
|
|
|
160
|
-
public void
|
|
161
|
-
String
|
|
162
|
-
|
|
163
|
-
|
|
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(",");
|
|
164
200
|
}
|
|
165
|
-
|
|
201
|
+
GetBlockedBundlesResult result = new GetBlockedBundlesResult(bundleIds);
|
|
166
202
|
callback.success(result);
|
|
167
203
|
}
|
|
168
204
|
|
|
169
205
|
public void getBundles(@NonNull NonEmptyCallback callback) {
|
|
170
|
-
String[] bundleIds =
|
|
206
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
171
207
|
GetBundlesResult result = new GetBundlesResult(bundleIds);
|
|
172
208
|
callback.success(result);
|
|
173
209
|
}
|
|
174
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
|
+
|
|
175
217
|
public void getChannel(@NonNull NonEmptyCallback callback) {
|
|
176
218
|
String channel = getChannel();
|
|
177
219
|
GetChannelResult result = new GetChannelResult(channel);
|
|
178
220
|
callback.success(result);
|
|
179
221
|
}
|
|
180
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
|
+
|
|
181
230
|
public void getCurrentBundle(@NonNull NonEmptyCallback<GetCurrentBundleResult> callback) {
|
|
182
231
|
String bundleId = getCurrentBundleId();
|
|
183
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
184
|
-
bundleId = null;
|
|
185
|
-
}
|
|
186
232
|
GetCurrentBundleResult result = new GetCurrentBundleResult(bundleId);
|
|
187
233
|
callback.success(result);
|
|
188
234
|
}
|
|
@@ -201,15 +247,12 @@ public class LiveUpdate {
|
|
|
201
247
|
|
|
202
248
|
public void getNextBundle(@NonNull NonEmptyCallback<GetNextBundleResult> callback) {
|
|
203
249
|
String bundleId = getNextBundleId();
|
|
204
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
205
|
-
bundleId = null;
|
|
206
|
-
}
|
|
207
250
|
GetNextBundleResult result = new GetNextBundleResult(bundleId);
|
|
208
251
|
callback.success(result);
|
|
209
252
|
}
|
|
210
253
|
|
|
211
254
|
public void getVersionCode(@NonNull NonEmptyCallback callback) throws PackageManager.NameNotFoundException {
|
|
212
|
-
String versionCode =
|
|
255
|
+
String versionCode = getVersionCodeAsString();
|
|
213
256
|
GetVersionCodeResult result = new GetVersionCodeResult(versionCode);
|
|
214
257
|
callback.success(result);
|
|
215
258
|
}
|
|
@@ -220,14 +263,51 @@ public class LiveUpdate {
|
|
|
220
263
|
callback.success(result);
|
|
221
264
|
}
|
|
222
265
|
|
|
223
|
-
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) {
|
|
224
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
|
+
}
|
|
225
291
|
// Stop the rollback timer
|
|
226
292
|
stopRollbackTimer();
|
|
227
293
|
// Delete unused bundles
|
|
228
294
|
if (config.getAutoDeleteBundles()) {
|
|
229
295
|
deleteUnusedBundles();
|
|
230
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;
|
|
231
311
|
}
|
|
232
312
|
|
|
233
313
|
public void reload() {
|
|
@@ -237,20 +317,11 @@ public class LiveUpdate {
|
|
|
237
317
|
}
|
|
238
318
|
|
|
239
319
|
public void reset() {
|
|
240
|
-
|
|
320
|
+
setNextBundleById(null);
|
|
241
321
|
}
|
|
242
322
|
|
|
243
|
-
public void
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (!hasBundle(bundleId)) {
|
|
247
|
-
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
248
|
-
callback.error(exception);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
setNextBundle(bundleId);
|
|
253
|
-
callback.success();
|
|
323
|
+
public void resetConfig() {
|
|
324
|
+
preferences.setAppId(null);
|
|
254
325
|
}
|
|
255
326
|
|
|
256
327
|
public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
|
|
@@ -260,6 +331,11 @@ public class LiveUpdate {
|
|
|
260
331
|
callback.success();
|
|
261
332
|
}
|
|
262
333
|
|
|
334
|
+
public void setConfig(@NonNull SetConfigOptions options) {
|
|
335
|
+
String appId = options.getAppId();
|
|
336
|
+
preferences.setAppId(appId);
|
|
337
|
+
}
|
|
338
|
+
|
|
263
339
|
public void setCustomId(@NonNull SetCustomIdOptions options, @NonNull EmptyCallback callback) {
|
|
264
340
|
String customId = options.getCustomId();
|
|
265
341
|
|
|
@@ -273,8 +349,8 @@ public class LiveUpdate {
|
|
|
273
349
|
if (bundleId == null) {
|
|
274
350
|
reset();
|
|
275
351
|
} else {
|
|
276
|
-
if (
|
|
277
|
-
|
|
352
|
+
if (hasBundleById(bundleId)) {
|
|
353
|
+
setNextBundleById(bundleId);
|
|
278
354
|
} else {
|
|
279
355
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
280
356
|
callback.error(exception);
|
|
@@ -284,43 +360,99 @@ public class LiveUpdate {
|
|
|
284
360
|
callback.success();
|
|
285
361
|
}
|
|
286
362
|
|
|
287
|
-
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
|
+
|
|
288
371
|
String channel = options.getChannel();
|
|
289
372
|
// Fetch the latest bundle
|
|
290
373
|
FetchLatestBundleOptions fetchLatestBundleOptions = new FetchLatestBundleOptions(channel);
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
454
|
}
|
|
310
|
-
|
|
311
|
-
callback.success(syncResult);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
// Download the bundle
|
|
315
|
-
if (artifactType == ArtifactType.MANIFEST) {
|
|
316
|
-
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
317
|
-
} else {
|
|
318
|
-
downloadBundleOfTypeZip(latestBundleId, url, null);
|
|
319
|
-
}
|
|
320
|
-
// Set the next bundle
|
|
321
|
-
setNextBundle(latestBundleId);
|
|
322
|
-
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
323
|
-
callback.success(syncResult);
|
|
455
|
+
);
|
|
324
456
|
}
|
|
325
457
|
|
|
326
458
|
private void addBundle(@NonNull String bundleId, @NonNull File sourceDirectory) throws Exception {
|
|
@@ -367,9 +499,10 @@ public class LiveUpdate {
|
|
|
367
499
|
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
368
500
|
}
|
|
369
501
|
|
|
370
|
-
private void copyCurrentBundleFile(@NonNull
|
|
502
|
+
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
503
|
+
String href = fileToCopy.getHref();
|
|
371
504
|
String currentBundleId = getCurrentBundleId();
|
|
372
|
-
if (currentBundleId
|
|
505
|
+
if (currentBundleId == null) {
|
|
373
506
|
// Create the source input stream
|
|
374
507
|
AssetManager assets = plugin.getContext().getAssets();
|
|
375
508
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
|
|
@@ -391,10 +524,20 @@ public class LiveUpdate {
|
|
|
391
524
|
}
|
|
392
525
|
}
|
|
393
526
|
|
|
394
|
-
private
|
|
395
|
-
|
|
396
|
-
|
|
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
|
+
}
|
|
397
539
|
}
|
|
540
|
+
return missingItems;
|
|
398
541
|
}
|
|
399
542
|
|
|
400
543
|
private void copyFile(File input, File output) throws IOException {
|
|
@@ -446,14 +589,14 @@ public class LiveUpdate {
|
|
|
446
589
|
return file;
|
|
447
590
|
}
|
|
448
591
|
|
|
449
|
-
private void
|
|
592
|
+
private void deleteBundleById(@NonNull String bundleId) {
|
|
450
593
|
// Delete the bundle directory
|
|
451
594
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
452
595
|
deleteFileRecursively(bundleDirectory);
|
|
453
596
|
// Reset the next bundle if it is the deleted bundle
|
|
454
597
|
String nextBundleId = getNextBundleId();
|
|
455
598
|
if (bundleId.equals(nextBundleId)) {
|
|
456
|
-
|
|
599
|
+
setNextBundleById(null);
|
|
457
600
|
}
|
|
458
601
|
}
|
|
459
602
|
|
|
@@ -467,165 +610,357 @@ public class LiveUpdate {
|
|
|
467
610
|
}
|
|
468
611
|
|
|
469
612
|
private void deleteUnusedBundles() {
|
|
470
|
-
String[] bundleIds =
|
|
613
|
+
String[] bundleIds = getDownloadedBundleIds();
|
|
471
614
|
for (String bundleId : bundleIds) {
|
|
472
615
|
if (!isBundleInUse(bundleId)) {
|
|
473
|
-
|
|
616
|
+
deleteBundleById(bundleId);
|
|
474
617
|
}
|
|
475
618
|
}
|
|
476
619
|
}
|
|
477
620
|
|
|
478
|
-
private
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
+
);
|
|
492
663
|
}
|
|
493
664
|
|
|
494
|
-
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
|
+
) {
|
|
495
672
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
|
|
496
673
|
urlBuilder.addQueryParameter("href", href);
|
|
497
674
|
String url = urlBuilder.build().toString();
|
|
498
675
|
|
|
499
676
|
File destinationFile = new File(destinationDirectory, href);
|
|
500
677
|
destinationFile.getParentFile().mkdirs();
|
|
501
|
-
downloadAndVerifyFile(url, destinationFile, null);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
+
}
|
|
518
768
|
}
|
|
519
769
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
// Stop if an exception occurred
|
|
524
|
-
if (exceptionReference.get() != null) {
|
|
525
|
-
break;
|
|
770
|
+
);
|
|
771
|
+
synchronized (activeCalls) {
|
|
772
|
+
activeCalls.add(call);
|
|
526
773
|
}
|
|
527
|
-
// Start a new thread
|
|
528
|
-
Thread thread = new Thread(() -> {
|
|
529
|
-
try {
|
|
530
|
-
downloadBundleFile(baseUrl, href, destinationDirectory);
|
|
531
|
-
} catch (Exception exception) {
|
|
532
|
-
exceptionReference.set(exception);
|
|
533
|
-
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + href, exception);
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
threads.add(thread);
|
|
537
|
-
thread.start();
|
|
538
|
-
}
|
|
539
|
-
// Wait for all threads to finish
|
|
540
|
-
for (Thread thread : threads) {
|
|
541
|
-
thread.join();
|
|
542
|
-
}
|
|
543
|
-
// Throw the exception if one occurred
|
|
544
|
-
Exception exception = exceptionReference.get();
|
|
545
|
-
if (exception != null) {
|
|
546
|
-
throw exception;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
private void downloadBundleOfTypeManifest(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
|
|
551
|
-
// Create a temporary directory
|
|
552
|
-
File temporaryDirectory = createTemporaryDirectory();
|
|
553
|
-
// Download the latest manifest
|
|
554
|
-
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory);
|
|
555
|
-
Manifest latestManifest = loadManifest(latestManifestFile);
|
|
556
|
-
// Load the current manifest
|
|
557
|
-
Manifest currentManifest = loadCurrentManifest();
|
|
558
|
-
// Compare the manifests
|
|
559
|
-
List<ManifestItem> itemsToCopy = new ArrayList<>();
|
|
560
|
-
List<ManifestItem> itemsToDownload = new ArrayList<>();
|
|
561
|
-
if (currentManifest == null) {
|
|
562
|
-
itemsToDownload.addAll(latestManifest.getItems());
|
|
563
|
-
} else {
|
|
564
|
-
itemsToCopy.addAll(Manifest.findDuplicateItems(latestManifest, currentManifest));
|
|
565
|
-
itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
|
|
566
|
-
}
|
|
567
|
-
// Copy the files
|
|
568
|
-
List<String> hrefsToCopy = new ArrayList<>();
|
|
569
|
-
for (ManifestItem item : itemsToCopy) {
|
|
570
|
-
hrefsToCopy.add(item.getHref());
|
|
571
774
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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);
|
|
577
860
|
}
|
|
578
|
-
downloadBundleFiles(downloadUrl, hrefsToDownload, temporaryDirectory);
|
|
579
|
-
// Add the bundle
|
|
580
|
-
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
581
861
|
}
|
|
582
862
|
|
|
583
|
-
private void downloadBundleOfTypeZip(
|
|
584
|
-
|
|
863
|
+
private void downloadBundleOfTypeZip(
|
|
864
|
+
@NonNull String bundleId,
|
|
865
|
+
@Nullable String checksum,
|
|
866
|
+
@Nullable String signature,
|
|
867
|
+
@NonNull String downloadUrl,
|
|
868
|
+
@NonNull EmptyCallback completionCallback
|
|
869
|
+
) {
|
|
585
870
|
File file = buildTemporaryZipFile();
|
|
586
871
|
// Download the bundle
|
|
587
|
-
downloadAndVerifyFile(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
);
|
|
592
903
|
}
|
|
593
904
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
.
|
|
600
|
-
.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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);
|
|
625
960
|
}
|
|
626
961
|
}
|
|
627
962
|
|
|
628
|
-
private String[]
|
|
963
|
+
private String[] getDownloadedBundleIds() {
|
|
629
964
|
File bundlesDirectory = buildBundlesDirectory();
|
|
630
965
|
File[] bundles = bundlesDirectory.listFiles();
|
|
631
966
|
if (bundles == null) {
|
|
@@ -665,6 +1000,15 @@ public class LiveUpdate {
|
|
|
665
1000
|
return checksum.toString();
|
|
666
1001
|
}
|
|
667
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
|
+
|
|
668
1012
|
@Nullable
|
|
669
1013
|
private String getChannel() {
|
|
670
1014
|
String channel = null;
|
|
@@ -678,12 +1022,13 @@ public class LiveUpdate {
|
|
|
678
1022
|
}
|
|
679
1023
|
|
|
680
1024
|
/**
|
|
681
|
-
* @return The current bundle ID
|
|
1025
|
+
* @return The current bundle ID or `null` if the default bundle is in use.
|
|
682
1026
|
*/
|
|
1027
|
+
@Nullable
|
|
683
1028
|
private String getCurrentBundleId() {
|
|
684
1029
|
String currentPath = getCurrentCapacitorServerPath();
|
|
685
1030
|
if (currentPath.equals(defaultWebAssetDir)) {
|
|
686
|
-
return
|
|
1031
|
+
return null;
|
|
687
1032
|
}
|
|
688
1033
|
return new File(currentPath).getName();
|
|
689
1034
|
}
|
|
@@ -697,22 +1042,22 @@ public class LiveUpdate {
|
|
|
697
1042
|
|
|
698
1043
|
@NonNull
|
|
699
1044
|
private String getDeviceId() {
|
|
700
|
-
String deviceId = preferences.getDeviceIdForApp(
|
|
1045
|
+
String deviceId = preferences.getDeviceIdForApp(getAppId());
|
|
701
1046
|
if (deviceId == null) {
|
|
702
1047
|
deviceId = UUID.randomUUID().toString().toLowerCase();
|
|
703
|
-
preferences.setDeviceIdForApp(
|
|
1048
|
+
preferences.setDeviceIdForApp(getAppId(), deviceId);
|
|
704
1049
|
}
|
|
705
1050
|
return deviceId;
|
|
706
1051
|
}
|
|
707
1052
|
|
|
708
1053
|
/**
|
|
709
|
-
* @return The next bundle ID
|
|
1054
|
+
* @return The next bundle ID or `null` if the default bundle will be used.
|
|
710
1055
|
*/
|
|
711
|
-
@
|
|
1056
|
+
@Nullable
|
|
712
1057
|
private String getNextBundleId() {
|
|
713
1058
|
String nextPath = getNextCapacitorServerPath();
|
|
714
1059
|
if (nextPath.equals(defaultWebAssetDir)) {
|
|
715
|
-
return
|
|
1060
|
+
return null;
|
|
716
1061
|
}
|
|
717
1062
|
return new File(nextPath).getName();
|
|
718
1063
|
}
|
|
@@ -722,31 +1067,54 @@ public class LiveUpdate {
|
|
|
722
1067
|
*/
|
|
723
1068
|
@NonNull
|
|
724
1069
|
private String getNextCapacitorServerPath() {
|
|
725
|
-
|
|
1070
|
+
String path = plugin
|
|
726
1071
|
.getContext()
|
|
727
1072
|
.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
|
|
728
1073
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
1074
|
+
// Empty path means default path
|
|
1075
|
+
if (path.isEmpty()) {
|
|
1076
|
+
path = defaultWebAssetDir;
|
|
1077
|
+
}
|
|
1078
|
+
return path;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
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();
|
|
729
1087
|
}
|
|
730
1088
|
|
|
731
|
-
private
|
|
732
|
-
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());
|
|
733
1095
|
}
|
|
734
1096
|
|
|
735
1097
|
private String getVersionName() throws PackageManager.NameNotFoundException {
|
|
736
1098
|
return getPackageInfo().versionName;
|
|
737
1099
|
}
|
|
738
1100
|
|
|
739
|
-
private boolean
|
|
1101
|
+
private boolean hasBundleById(@NonNull String bundleId) {
|
|
740
1102
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
741
1103
|
return bundleDirectory.exists();
|
|
742
1104
|
}
|
|
743
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
|
+
|
|
744
1112
|
@Nullable
|
|
745
1113
|
private Manifest loadCurrentManifest() throws Exception {
|
|
746
1114
|
String currentBundleId = getCurrentBundleId();
|
|
747
|
-
if (currentBundleId
|
|
1115
|
+
if (currentBundleId == null) {
|
|
748
1116
|
AssetManager assets = plugin.getContext().getAssets();
|
|
749
|
-
|
|
1117
|
+
boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
|
|
750
1118
|
if (manifestFileExists) {
|
|
751
1119
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
|
|
752
1120
|
BufferedSource source = Okio.buffer(Okio.source(inputStream));
|
|
@@ -776,25 +1144,53 @@ public class LiveUpdate {
|
|
|
776
1144
|
return loadManifest(source);
|
|
777
1145
|
}
|
|
778
1146
|
|
|
779
|
-
private
|
|
780
|
-
|
|
781
|
-
String nextBundleId = getNextBundleId();
|
|
782
|
-
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
1147
|
+
private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
1148
|
+
plugin.notifyDownloadBundleProgressListeners(event);
|
|
783
1149
|
}
|
|
784
1150
|
|
|
785
|
-
private void
|
|
786
|
-
if
|
|
787
|
-
|
|
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.");
|
|
788
1156
|
return;
|
|
789
1157
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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);
|
|
793
1177
|
}
|
|
794
1178
|
|
|
795
|
-
private void
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
}
|
|
798
1194
|
}
|
|
799
1195
|
|
|
800
1196
|
@Nullable
|
|
@@ -822,6 +1218,18 @@ public class LiveUpdate {
|
|
|
822
1218
|
return null;
|
|
823
1219
|
}
|
|
824
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
|
+
|
|
825
1233
|
private void setCurrentCapacitorServerPath(@NonNull String path) {
|
|
826
1234
|
if (path.equals(defaultWebAssetDir)) {
|
|
827
1235
|
this.plugin.getBridge().setServerAssetPath(path);
|
|
@@ -829,15 +1237,72 @@ public class LiveUpdate {
|
|
|
829
1237
|
this.plugin.getBridge().setServerBasePath(path);
|
|
830
1238
|
}
|
|
831
1239
|
this.plugin.getBridge().reload();
|
|
1240
|
+
// Notify listeners
|
|
1241
|
+
notifyReloadedListeners();
|
|
832
1242
|
}
|
|
833
1243
|
|
|
834
|
-
|
|
835
|
-
|
|
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);
|
|
836
1257
|
}
|
|
837
1258
|
|
|
838
|
-
private void
|
|
839
|
-
|
|
840
|
-
|
|
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);
|
|
841
1306
|
}
|
|
842
1307
|
|
|
843
1308
|
private void setNextCapacitorServerPath(@NonNull String path) {
|
|
@@ -845,11 +1310,28 @@ public class LiveUpdate {
|
|
|
845
1310
|
this.webViewSettingsEditor.commit();
|
|
846
1311
|
}
|
|
847
1312
|
|
|
848
|
-
private void
|
|
849
|
-
|
|
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);
|
|
850
1329
|
}
|
|
851
1330
|
|
|
852
1331
|
private void startRollbackTimer() {
|
|
1332
|
+
if (config.getReadyTimeout() <= 0) {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
853
1335
|
stopRollbackTimer();
|
|
854
1336
|
rollbackHandler.postDelayed(() -> rollback(), config.getReadyTimeout());
|
|
855
1337
|
}
|
|
@@ -858,6 +1340,15 @@ public class LiveUpdate {
|
|
|
858
1340
|
rollbackHandler.removeCallbacksAndMessages(null);
|
|
859
1341
|
}
|
|
860
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
|
+
|
|
861
1352
|
private File unzipFile(@NonNull File zipFile) throws IOException {
|
|
862
1353
|
File destination = buildTemporaryDirectory();
|
|
863
1354
|
String destinationPath = destination.getPath();
|
|
@@ -916,12 +1407,6 @@ public class LiveUpdate {
|
|
|
916
1407
|
}
|
|
917
1408
|
}
|
|
918
1409
|
|
|
919
|
-
private boolean wasUpdated() throws PackageManager.NameNotFoundException {
|
|
920
|
-
int lastVersionCode = preferences.getLastVersionCode();
|
|
921
|
-
int currentVersionCode = getPackageInfo().versionCode;
|
|
922
|
-
return lastVersionCode != currentVersionCode;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
1410
|
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
|
926
1411
|
String packageName = this.plugin.getContext().getPackageName();
|
|
927
1412
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|