@capgo/capacitor-updater 8.0.0 → 8.0.1

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.
Files changed (40) hide show
  1. package/CapgoCapacitorUpdater.podspec +2 -2
  2. package/Package.swift +35 -0
  3. package/README.md +667 -206
  4. package/android/build.gradle +16 -11
  5. package/android/proguard-rules.pro +28 -0
  6. package/android/src/main/AndroidManifest.xml +0 -1
  7. package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +134 -194
  8. package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
  9. package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
  10. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +967 -1027
  11. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1283 -1180
  12. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +276 -0
  13. package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
  14. package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +45 -48
  15. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
  16. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +440 -113
  17. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +101 -0
  18. package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
  19. package/dist/docs.json +1316 -473
  20. package/dist/esm/definitions.d.ts +518 -248
  21. package/dist/esm/definitions.js.map +1 -1
  22. package/dist/esm/index.d.ts +2 -2
  23. package/dist/esm/index.js +4 -4
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/web.d.ts +25 -41
  26. package/dist/esm/web.js +67 -35
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +67 -35
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +67 -35
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Plugin/CapacitorUpdater.swift +736 -361
  33. package/ios/Plugin/CapacitorUpdaterPlugin.swift +436 -136
  34. package/ios/Plugin/CryptoCipherV2.swift +310 -0
  35. package/ios/Plugin/InternalUtils.swift +258 -0
  36. package/package.json +33 -29
  37. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +0 -153
  38. package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
  39. package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
  40. package/ios/Plugin/CryptoCipher.swift +0 -240
@@ -7,1124 +7,1064 @@
7
7
  package ee.forgr.capacitor_updater;
8
8
 
9
9
  import android.app.Activity;
10
- import android.content.BroadcastReceiver;
11
10
  import android.content.Context;
12
- import android.content.Intent;
13
- import android.content.IntentFilter;
14
11
  import android.content.SharedPreferences;
15
12
  import android.os.Build;
16
- import android.os.Bundle;
17
- import android.util.Base64;
18
13
  import android.util.Log;
19
- import com.android.volley.BuildConfig;
20
- import com.android.volley.DefaultRetryPolicy;
21
- import com.android.volley.NetworkResponse;
22
- import com.android.volley.Request;
23
- import com.android.volley.RequestQueue;
24
- import com.android.volley.Response;
25
- import com.android.volley.VolleyError;
26
- import com.android.volley.toolbox.HttpHeaderParser;
27
- import com.android.volley.toolbox.JsonObjectRequest;
14
+ import androidx.annotation.NonNull;
15
+ import androidx.lifecycle.LifecycleOwner;
16
+ import androidx.work.Data;
17
+ import androidx.work.WorkInfo;
18
+ import androidx.work.WorkManager;
28
19
  import com.getcapacitor.JSObject;
29
20
  import com.getcapacitor.plugin.WebView;
21
+ import com.google.common.util.concurrent.Futures;
22
+ import com.google.common.util.concurrent.ListenableFuture;
30
23
  import java.io.BufferedInputStream;
31
- import java.io.DataInputStream;
32
24
  import java.io.File;
33
25
  import java.io.FileInputStream;
34
26
  import java.io.FileNotFoundException;
35
27
  import java.io.FileOutputStream;
36
28
  import java.io.FilenameFilter;
37
29
  import java.io.IOException;
38
- import java.io.InputStream;
39
- import java.io.UnsupportedEncodingException;
40
- import java.net.URL;
41
- import java.net.URLConnection;
42
- import java.security.GeneralSecurityException;
43
- import java.security.PrivateKey;
44
30
  import java.security.SecureRandom;
45
31
  import java.util.ArrayList;
46
32
  import java.util.Date;
47
33
  import java.util.Iterator;
48
34
  import java.util.List;
49
- import java.util.zip.CRC32;
35
+ import java.util.Objects;
50
36
  import java.util.zip.ZipEntry;
51
37
  import java.util.zip.ZipInputStream;
52
- import javax.crypto.SecretKey;
38
+ import okhttp3.*;
39
+ import org.json.JSONArray;
53
40
  import org.json.JSONException;
54
41
  import org.json.JSONObject;
55
42
 
56
- interface Callback {
57
- void callback(JSObject jsoObject);
58
- }
59
-
60
43
  public class CapacitorUpdater {
61
44
 
62
- private static final String AB =
63
- "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
64
- private static final SecureRandom rnd = new SecureRandom();
65
-
66
- private static final String INFO_SUFFIX = "_info";
67
-
68
- private static final String FALLBACK_VERSION = "pastVersion";
69
- private static final String NEXT_VERSION = "nextVersion";
70
- private static final String bundleDirectory = "versions";
71
-
72
- public static final String TAG = "Capacitor-updater";
73
- public static final int timeout = 20000;
74
-
75
- public SharedPreferences.Editor editor;
76
- public SharedPreferences prefs;
77
-
78
- public RequestQueue requestQueue;
79
-
80
- public File documentsDir;
81
- public Activity activity;
82
- public String PLUGIN_VERSION = "";
83
- public String versionBuild = "";
84
- public String versionCode = "";
85
- public String versionOs = "";
86
-
87
- public String customId = "";
88
- public String statsUrl = "";
89
- public String channelUrl = "";
90
- public String appId = "";
91
- public String privateKey = "";
92
- public String deviceID = "";
93
-
94
- private final FilenameFilter filter = new FilenameFilter() {
95
- @Override
96
- public boolean accept(final File f, final String name) {
97
- // ignore directories generated by mac os x
98
- return (
99
- !name.startsWith("__MACOSX") &&
100
- !name.startsWith(".") &&
101
- !name.startsWith(".DS_Store")
102
- );
45
+ private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
46
+ private static final SecureRandom rnd = new SecureRandom();
47
+
48
+ private static final String INFO_SUFFIX = "_info";
49
+
50
+ private static final String FALLBACK_VERSION = "pastVersion";
51
+ private static final String NEXT_VERSION = "nextVersion";
52
+ private static final String bundleDirectory = "versions";
53
+
54
+ public static final String TAG = "Capacitor-updater";
55
+ public SharedPreferences.Editor editor;
56
+ public SharedPreferences prefs;
57
+
58
+ public OkHttpClient client;
59
+
60
+ public File documentsDir;
61
+ public Boolean directUpdate = false;
62
+ public Activity activity;
63
+ public String PLUGIN_VERSION = "";
64
+ public String versionBuild = "";
65
+ public String versionCode = "";
66
+ public String versionOs = "";
67
+
68
+ public String customId = "";
69
+ public String statsUrl = "";
70
+ public String channelUrl = "";
71
+ public String defaultChannel = "";
72
+ public String appId = "";
73
+ public String publicKey = "";
74
+ public String deviceID = "";
75
+ public int timeout = 20000;
76
+
77
+ private final FilenameFilter filter = (f, name) -> {
78
+ // ignore directories generated by mac os x
79
+ return (!name.startsWith("__MACOSX") && !name.startsWith(".") && !name.startsWith(".DS_Store"));
80
+ };
81
+
82
+ private boolean isProd() {
83
+ try {
84
+ return !Objects.requireNonNull(getClass().getPackage()).getName().contains(".debug");
85
+ } catch (Exception e) {
86
+ return true; // Default to production if we can't determine
87
+ }
88
+ }
89
+
90
+ private boolean isEmulator() {
91
+ return (
92
+ (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) ||
93
+ Build.FINGERPRINT.startsWith("generic") ||
94
+ Build.FINGERPRINT.startsWith("unknown") ||
95
+ Build.HARDWARE.contains("goldfish") ||
96
+ Build.HARDWARE.contains("ranchu") ||
97
+ Build.MODEL.contains("google_sdk") ||
98
+ Build.MODEL.contains("Emulator") ||
99
+ Build.MODEL.contains("Android SDK built for x86") ||
100
+ Build.MANUFACTURER.contains("Genymotion") ||
101
+ Build.PRODUCT.contains("sdk_google") ||
102
+ Build.PRODUCT.contains("google_sdk") ||
103
+ Build.PRODUCT.contains("sdk") ||
104
+ Build.PRODUCT.contains("sdk_x86") ||
105
+ Build.PRODUCT.contains("sdk_gphone64_arm64") ||
106
+ Build.PRODUCT.contains("vbox86p") ||
107
+ Build.PRODUCT.contains("emulator") ||
108
+ Build.PRODUCT.contains("simulator")
109
+ );
110
+ }
111
+
112
+ private int calcTotalPercent(final int percent, final int min, final int max) {
113
+ return (percent * (max - min)) / 100 + min;
114
+ }
115
+
116
+ void notifyDownload(final String id, final int percent) {}
117
+
118
+ void directUpdateFinish(final BundleInfo latest) {}
119
+
120
+ void notifyListeners(final String id, final JSObject res) {}
121
+
122
+ private String randomString() {
123
+ final StringBuilder sb = new StringBuilder(10);
124
+ for (int i = 0; i < 10; i++) sb.append(AB.charAt(rnd.nextInt(AB.length())));
125
+ return sb.toString();
103
126
  }
104
- };
105
-
106
- private boolean isProd() {
107
- return !BuildConfig.DEBUG;
108
- }
109
-
110
- private boolean isEmulator() {
111
- return (
112
- (
113
- Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
114
- ) ||
115
- Build.FINGERPRINT.startsWith("generic") ||
116
- Build.FINGERPRINT.startsWith("unknown") ||
117
- Build.HARDWARE.contains("goldfish") ||
118
- Build.HARDWARE.contains("ranchu") ||
119
- Build.MODEL.contains("google_sdk") ||
120
- Build.MODEL.contains("Emulator") ||
121
- Build.MODEL.contains("Android SDK built for x86") ||
122
- Build.MANUFACTURER.contains("Genymotion") ||
123
- Build.PRODUCT.contains("sdk_google") ||
124
- Build.PRODUCT.contains("google_sdk") ||
125
- Build.PRODUCT.contains("sdk") ||
126
- Build.PRODUCT.contains("sdk_x86") ||
127
- Build.PRODUCT.contains("sdk_gphone64_arm64") ||
128
- Build.PRODUCT.contains("vbox86p") ||
129
- Build.PRODUCT.contains("emulator") ||
130
- Build.PRODUCT.contains("simulator")
131
- );
132
- }
133
-
134
- private int calcTotalPercent(
135
- final int percent,
136
- final int min,
137
- final int max
138
- ) {
139
- return (percent * (max - min)) / 100 + min;
140
- }
141
-
142
- void notifyDownload(final String id, final int percent) {
143
- return;
144
- }
145
-
146
- void notifyListeners(final String id, final JSObject res) {
147
- return;
148
- }
149
-
150
- private String randomString(final int len) {
151
- final StringBuilder sb = new StringBuilder(len);
152
- for (int i = 0; i < len; i++) sb.append(
153
- AB.charAt(rnd.nextInt(AB.length()))
154
- );
155
- return sb.toString();
156
- }
157
-
158
- private File unzip(final String id, final File zipFile, final String dest)
159
- throws IOException {
160
- final File targetDirectory = new File(this.documentsDir, dest);
161
- final ZipInputStream zis = new ZipInputStream(
162
- new BufferedInputStream(new FileInputStream(zipFile))
163
- );
164
- try {
165
- int count;
166
- final int bufferSize = 8192;
167
- final byte[] buffer = new byte[bufferSize];
168
- final long lengthTotal = zipFile.length();
169
- long lengthRead = bufferSize;
170
- int percent = 0;
171
- this.notifyDownload(id, 75);
172
-
173
- ZipEntry entry;
174
- while ((entry = zis.getNextEntry()) != null) {
175
- if (entry.getName().contains("\\")) {
176
- Log.e(
177
- TAG,
178
- "unzip: Windows path is not supported, please use unix path as require by zip RFC: " +
179
- entry.getName()
180
- );
127
+
128
+ private File unzip(final String id, final File zipFile, final String dest) throws IOException {
129
+ final File targetDirectory = new File(this.documentsDir, dest);
130
+ try (
131
+ final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFile));
132
+ final ZipInputStream zis = new ZipInputStream(bis)
133
+ ) {
134
+ int count;
135
+ final int bufferSize = 8192;
136
+ final byte[] buffer = new byte[bufferSize];
137
+ final long lengthTotal = zipFile.length();
138
+ long lengthRead = bufferSize;
139
+ int percent = 0;
140
+ this.notifyDownload(id, 75);
141
+
142
+ ZipEntry entry;
143
+ while ((entry = zis.getNextEntry()) != null) {
144
+ if (entry.getName().contains("\\")) {
145
+ Log.e(TAG, "unzip: Windows path is not supported, please use unix path as require by zip RFC: " + entry.getName());
146
+ this.sendStats("windows_path_fail");
147
+ }
148
+ final File file = new File(targetDirectory, entry.getName());
149
+ final String canonicalPath = file.getCanonicalPath();
150
+ final String canonicalDir = targetDirectory.getCanonicalPath();
151
+ final File dir = entry.isDirectory() ? file : file.getParentFile();
152
+
153
+ if (!canonicalPath.startsWith(canonicalDir)) {
154
+ this.sendStats("canonical_path_fail");
155
+ throw new FileNotFoundException(
156
+ "SecurityException, Failed to ensure directory is the start path : " + canonicalDir + " of " + canonicalPath
157
+ );
158
+ }
159
+
160
+ assert dir != null;
161
+ if (!dir.isDirectory() && !dir.mkdirs()) {
162
+ this.sendStats("directory_path_fail");
163
+ throw new FileNotFoundException("Failed to ensure directory: " + dir.getAbsolutePath());
164
+ }
165
+
166
+ if (entry.isDirectory()) {
167
+ continue;
168
+ }
169
+
170
+ try (final FileOutputStream outputStream = new FileOutputStream(file)) {
171
+ while ((count = zis.read(buffer)) != -1) outputStream.write(buffer, 0, count);
172
+ }
173
+
174
+ final int newPercent = (int) ((lengthRead / (float) lengthTotal) * 100);
175
+ if (lengthTotal > 1 && newPercent != percent) {
176
+ percent = newPercent;
177
+ this.notifyDownload(id, this.calcTotalPercent(percent, 75, 90));
178
+ }
179
+
180
+ lengthRead += entry.getCompressedSize();
181
+ }
182
+ return targetDirectory;
183
+ } catch (IOException e) {
184
+ this.sendStats("unzip_fail");
185
+ throw new IOException("Failed to unzip: " + zipFile.getPath());
186
+ }
187
+ }
188
+
189
+ private void flattenAssets(final File sourceFile, final String dest) throws IOException {
190
+ if (!sourceFile.exists()) {
191
+ throw new FileNotFoundException("Source file not found: " + sourceFile.getPath());
192
+ }
193
+ final File destinationFile = new File(this.documentsDir, dest);
194
+ Objects.requireNonNull(destinationFile.getParentFile()).mkdirs();
195
+ final String[] entries = sourceFile.list(this.filter);
196
+ if (entries == null || entries.length == 0) {
197
+ throw new IOException("Source file was not a directory or was empty: " + sourceFile.getPath());
181
198
  }
182
- final File file = new File(targetDirectory, entry.getName());
183
- final String canonicalPath = file.getCanonicalPath();
184
- final String canonicalDir = targetDirectory.getCanonicalPath();
185
- final File dir = entry.isDirectory() ? file : file.getParentFile();
186
-
187
- if (!canonicalPath.startsWith(canonicalDir)) {
188
- throw new FileNotFoundException(
189
- "SecurityException, Failed to ensure directory is the start path : " +
190
- canonicalDir +
191
- " of " +
192
- canonicalPath
193
- );
199
+ if (entries.length == 1 && !"index.html".equals(entries[0])) {
200
+ final File child = new File(sourceFile, entries[0]);
201
+ child.renameTo(destinationFile);
202
+ } else {
203
+ sourceFile.renameTo(destinationFile);
194
204
  }
205
+ sourceFile.delete();
206
+ }
195
207
 
196
- if (!dir.isDirectory() && !dir.mkdirs()) {
197
- throw new FileNotFoundException(
198
- "Failed to ensure directory: " + dir.getAbsolutePath()
199
- );
208
+ private void observeWorkProgress(Context context, String id) {
209
+ if (!(context instanceof LifecycleOwner)) {
210
+ Log.e(TAG, "Context is not a LifecycleOwner, cannot observe work progress");
211
+ return;
200
212
  }
201
213
 
202
- if (entry.isDirectory()) {
203
- continue;
214
+ activity.runOnUiThread(() -> {
215
+ WorkManager.getInstance(context)
216
+ .getWorkInfosByTagLiveData(id)
217
+ .observe((LifecycleOwner) context, workInfos -> {
218
+ if (workInfos == null || workInfos.isEmpty()) return;
219
+
220
+ WorkInfo workInfo = workInfos.get(0);
221
+ Data progress = workInfo.getProgress();
222
+
223
+ switch (workInfo.getState()) {
224
+ case RUNNING:
225
+ int percent = progress.getInt(DownloadService.PERCENT, 0);
226
+ notifyDownload(id, percent);
227
+ break;
228
+ case SUCCEEDED:
229
+ Data outputData = workInfo.getOutputData();
230
+ String dest = outputData.getString(DownloadService.FILEDEST);
231
+ String version = outputData.getString(DownloadService.VERSION);
232
+ String sessionKey = outputData.getString(DownloadService.SESSIONKEY);
233
+ String checksum = outputData.getString(DownloadService.CHECKSUM);
234
+ boolean isManifest = outputData.getBoolean(DownloadService.IS_MANIFEST, false);
235
+
236
+ boolean success = finishDownload(id, dest, version, sessionKey, checksum, true, isManifest);
237
+ if (!success) {
238
+ saveBundleInfo(
239
+ id,
240
+ new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "")
241
+ );
242
+ JSObject ret = new JSObject();
243
+ ret.put("version", getCurrentBundle().getVersionName());
244
+ ret.put("error", "finish_download_fail");
245
+ sendStats("finish_download_fail", version);
246
+ notifyListeners("downloadFailed", ret);
247
+ }
248
+ break;
249
+ case FAILED:
250
+ Data failedData = workInfo.getOutputData();
251
+ String error = failedData.getString(DownloadService.ERROR);
252
+ String failedVersion = failedData.getString(DownloadService.VERSION);
253
+ saveBundleInfo(
254
+ id,
255
+ new BundleInfo(id, failedVersion, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "")
256
+ );
257
+ JSObject ret = new JSObject();
258
+ ret.put("version", getCurrentBundle().getVersionName());
259
+ if ("low_mem_fail".equals(error)) {
260
+ sendStats("low_mem_fail", failedVersion);
261
+ }
262
+ ret.put("error", error != null ? error : "download_fail");
263
+ sendStats("download_fail", failedVersion);
264
+ notifyListeners("downloadFailed", ret);
265
+ break;
266
+ }
267
+ });
268
+ });
269
+ }
270
+
271
+ private void download(
272
+ final String id,
273
+ final String url,
274
+ final String dest,
275
+ final String version,
276
+ final String sessionKey,
277
+ final String checksum,
278
+ final JSONArray manifest
279
+ ) {
280
+ if (this.activity == null) {
281
+ Log.e(TAG, "Activity is null, cannot observe work progress");
282
+ return;
204
283
  }
284
+ observeWorkProgress(this.activity, id);
285
+
286
+ DownloadWorkerManager.enqueueDownload(
287
+ this.activity,
288
+ url,
289
+ id,
290
+ this.documentsDir.getAbsolutePath(),
291
+ dest,
292
+ version,
293
+ sessionKey,
294
+ checksum,
295
+ this.publicKey,
296
+ manifest != null
297
+ );
205
298
 
206
- try (final FileOutputStream outputStream = new FileOutputStream(file)) {
207
- while ((count = zis.read(buffer)) != -1) outputStream.write(
208
- buffer,
209
- 0,
210
- count
211
- );
299
+ if (manifest != null) {
300
+ DataManager.getInstance().setManifest(manifest);
212
301
  }
302
+ }
303
+
304
+ public Boolean finishDownload(
305
+ String id,
306
+ String dest,
307
+ String version,
308
+ String sessionKey,
309
+ String checksumRes,
310
+ Boolean setNext,
311
+ Boolean isManifest
312
+ ) {
313
+ File downloaded = null;
314
+ String checksum = "";
315
+
316
+ try {
317
+ this.notifyDownload(id, 71);
318
+ downloaded = new File(this.documentsDir, dest);
319
+
320
+ if (!isManifest) {
321
+ String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
322
+ if (!sessionKey.isEmpty()) {
323
+ CryptoCipherV2.decryptFile(downloaded, publicKey, sessionKey);
324
+ checksumDecrypted = CryptoCipherV2.decryptChecksum(checksumRes, publicKey);
325
+ checksum = CryptoCipherV2.calcChecksum(downloaded);
326
+ }
327
+ if ((!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) && !checksumDecrypted.equals(checksum)) {
328
+ Log.e(CapacitorUpdater.TAG, "Error checksum '" + checksumDecrypted + "' '" + checksum + "' '");
329
+ this.sendStats("checksum_fail");
330
+ throw new IOException("Checksum failed: " + id);
331
+ }
332
+ }
333
+ // Remove the decryption for manifest downloads
334
+ } catch (IOException e) {
335
+ final Boolean res = this.delete(id);
336
+ if (!res) {
337
+ Log.i(CapacitorUpdater.TAG, "Double error, cannot cleanup: " + version);
338
+ }
339
+
340
+ final JSObject ret = new JSObject();
341
+ ret.put("version", CapacitorUpdater.this.getCurrentBundle().getVersionName());
213
342
 
214
- final int newPercent = (int) ((lengthRead / (float) lengthTotal) * 100);
215
- if (lengthTotal > 1 && newPercent != percent) {
216
- percent = newPercent;
217
- this.notifyDownload(id, this.calcTotalPercent(percent, 75, 90));
343
+ CapacitorUpdater.this.notifyListeners("downloadFailed", ret);
344
+ CapacitorUpdater.this.sendStats("download_fail");
345
+ return false;
218
346
  }
219
347
 
220
- lengthRead += entry.getCompressedSize();
221
- }
222
- return targetDirectory;
223
- } finally {
224
- try {
225
- zis.close();
226
- } catch (final IOException e) {
227
- Log.e(TAG, "Failed to close zip input stream", e);
228
- }
229
- }
230
- }
231
-
232
- private void flattenAssets(final File sourceFile, final String dest)
233
- throws IOException {
234
- if (!sourceFile.exists()) {
235
- throw new FileNotFoundException(
236
- "Source file not found: " + sourceFile.getPath()
237
- );
238
- }
239
- final File destinationFile = new File(this.documentsDir, dest);
240
- destinationFile.getParentFile().mkdirs();
241
- final String[] entries = sourceFile.list(this.filter);
242
- if (entries == null || entries.length == 0) {
243
- throw new IOException(
244
- "Source file was not a directory or was empty: " + sourceFile.getPath()
245
- );
246
- }
247
- if (entries.length == 1 && !"index.html".equals(entries[0])) {
248
- final File child = new File(sourceFile, entries[0]);
249
- child.renameTo(destinationFile);
250
- } else {
251
- sourceFile.renameTo(destinationFile);
252
- }
253
- sourceFile.delete();
254
- }
255
-
256
- public void onResume() {
257
- this.activity.registerReceiver(
258
- receiver,
259
- new IntentFilter(DownloadService.NOTIFICATION)
260
- );
261
- }
262
-
263
- public void onPause() {
264
- this.activity.unregisterReceiver(receiver);
265
- }
266
-
267
- private BroadcastReceiver receiver = new BroadcastReceiver() {
268
- @Override
269
- public void onReceive(Context context, Intent intent) {
270
- String action = intent.getAction();
271
- Bundle bundle = intent.getExtras();
272
- if (bundle != null) {
273
- if (action == DownloadService.PERCENTDOWNLOAD) {
274
- String id = bundle.getString(DownloadService.ID);
275
- int percent = bundle.getInt(DownloadService.PERCENT);
276
- CapacitorUpdater.this.notifyDownload(id, percent);
277
- } else if (action == DownloadService.NOTIFICATION) {
278
- String id = bundle.getString(DownloadService.ID);
279
- String dest = bundle.getString(DownloadService.FILEDEST);
280
- String version = bundle.getString(DownloadService.VERSION);
281
- String sessionKey = bundle.getString(DownloadService.SESSIONKEY);
282
- String checksum = bundle.getString(DownloadService.CHECKSUM);
283
- Log.i(
284
- CapacitorUpdater.TAG,
285
- "res " +
286
- id +
287
- " " +
288
- dest +
289
- " " +
290
- version +
291
- " " +
292
- sessionKey +
293
- " " +
294
- checksum
295
- );
296
- if (dest == null) {
348
+ try {
349
+ if (!isManifest) {
350
+ final File unzipped = this.unzip(id, downloaded, this.randomString());
351
+ this.notifyDownload(id, 91);
352
+ final String idName = bundleDirectory + "/" + id;
353
+ this.flattenAssets(unzipped, idName);
354
+ } else {
355
+ this.notifyDownload(id, 91);
356
+ final String idName = bundleDirectory + "/" + id;
357
+ this.flattenAssets(downloaded, idName);
358
+ downloaded.delete();
359
+ }
360
+ this.notifyDownload(id, 100);
361
+ this.saveBundleInfo(id, null);
362
+ BundleInfo next = new BundleInfo(id, version, BundleStatus.PENDING, new Date(System.currentTimeMillis()), checksum);
363
+ this.saveBundleInfo(id, next);
364
+
297
365
  final JSObject ret = new JSObject();
298
- ret.put(
299
- "version",
300
- CapacitorUpdater.this.getCurrentBundle().getVersionName()
301
- );
366
+ ret.put("bundle", next.toJSON());
367
+ CapacitorUpdater.this.notifyListeners("updateAvailable", ret);
368
+ if (setNext) {
369
+ if (this.directUpdate) {
370
+ CapacitorUpdater.this.directUpdateFinish(next);
371
+ this.directUpdate = false;
372
+ } else {
373
+ this.setNextBundle(next.getId());
374
+ }
375
+ }
376
+ } catch (IOException e) {
377
+ e.printStackTrace();
378
+ final JSObject ret = new JSObject();
379
+ ret.put("version", CapacitorUpdater.this.getCurrentBundle().getVersionName());
302
380
  CapacitorUpdater.this.notifyListeners("downloadFailed", ret);
303
- CapacitorUpdater.this.sendStats(
304
- "download_fail",
305
- CapacitorUpdater.this.getCurrentBundle().getVersionName()
306
- );
307
- return;
308
- }
309
- CapacitorUpdater.this.finishDownload(
310
- id,
311
- dest,
312
- version,
313
- sessionKey,
314
- checksum,
315
- true
316
- );
317
- } else {
318
- Log.i(TAG, "Unknown action " + action);
381
+ CapacitorUpdater.this.sendStats("download_fail");
382
+ return false;
319
383
  }
320
- }
384
+ return true;
321
385
  }
322
- };
323
-
324
- public void finishDownload(
325
- String id,
326
- String dest,
327
- String version,
328
- String sessionKey,
329
- String checksumRes,
330
- Boolean setNext
331
- ) {
332
- try {
333
- final File downloaded = new File(this.documentsDir, dest);
334
- this.decryptFile(downloaded, sessionKey, version);
335
- final String checksum;
336
- checksum = this.getChecksum(downloaded);
337
- this.notifyDownload(id, 71);
338
- final File unzipped = this.unzip(id, downloaded, this.randomString(10));
339
- downloaded.delete();
340
- this.notifyDownload(id, 91);
341
- final String idName = bundleDirectory + "/" + id;
342
- this.flattenAssets(unzipped, idName);
343
- this.notifyDownload(id, 100);
344
- this.saveBundleInfo(id, null);
345
- BundleInfo next = new BundleInfo(
346
- id,
347
- version,
348
- BundleStatus.PENDING,
349
- new Date(System.currentTimeMillis()),
350
- checksum
351
- );
352
- this.saveBundleInfo(id, next);
353
- if (
354
- checksumRes != null &&
355
- !checksumRes.isEmpty() &&
356
- !checksumRes.equals(checksum)
357
- ) {
358
- Log.e(
359
- CapacitorUpdater.TAG,
360
- "Error checksum " + next.getChecksum() + " " + checksum
361
- );
362
- this.sendStats("checksum_fail", getCurrentBundle().getVersionName());
363
- final Boolean res = this.delete(id);
364
- if (res) {
365
- Log.i(
366
- CapacitorUpdater.TAG,
367
- "Failed bundle deleted: " + next.getVersionName()
368
- );
386
+
387
+ private void deleteDirectory(final File file) throws IOException {
388
+ if (file.isDirectory()) {
389
+ final File[] entries = file.listFiles();
390
+ if (entries != null) {
391
+ for (final File entry : entries) {
392
+ this.deleteDirectory(entry);
393
+ }
394
+ }
395
+ }
396
+ if (!file.delete()) {
397
+ throw new IOException("Failed to delete: " + file);
369
398
  }
370
- throw new IOException("Checksum failed: " + id);
371
- }
372
- final JSObject ret = new JSObject();
373
- ret.put("bundle", next.toJSON());
374
- CapacitorUpdater.this.notifyListeners("updateAvailable", ret);
375
- if (setNext) {
376
- this.setNextBundle(next.getId());
377
- }
378
- } catch (IOException e) {
379
- e.printStackTrace();
380
- final JSObject ret = new JSObject();
381
- ret.put(
382
- "version",
383
- CapacitorUpdater.this.getCurrentBundle().getVersionName()
384
- );
385
- CapacitorUpdater.this.notifyListeners("downloadFailed", ret);
386
- CapacitorUpdater.this.sendStats(
387
- "download_fail",
388
- CapacitorUpdater.this.getCurrentBundle().getVersionName()
389
- );
390
399
  }
391
- }
392
-
393
- private void downloadFileBackground(
394
- final String id,
395
- final String url,
396
- final String version,
397
- final String sessionKey,
398
- final String checksum,
399
- final String dest
400
- ) {
401
- Intent intent = new Intent(this.activity, DownloadService.class);
402
- intent.putExtra(DownloadService.URL, url);
403
- intent.putExtra(DownloadService.FILEDEST, dest);
404
- intent.putExtra(
405
- DownloadService.DOCDIR,
406
- this.documentsDir.getAbsolutePath()
407
- );
408
- intent.putExtra(DownloadService.ID, id);
409
- intent.putExtra(DownloadService.VERSION, version);
410
- intent.putExtra(DownloadService.SESSIONKEY, sessionKey);
411
- intent.putExtra(DownloadService.CHECKSUM, checksum);
412
- this.activity.startService(intent);
413
- }
414
-
415
- private File downloadFile(
416
- final String id,
417
- final String url,
418
- final String dest
419
- ) throws IOException {
420
- final URL u = new URL(url);
421
- final URLConnection connection = u.openConnection();
422
- final InputStream is = u.openStream();
423
- final DataInputStream dis = new DataInputStream(is);
424
-
425
- final File target = new File(this.documentsDir, dest);
426
- target.getParentFile().mkdirs();
427
- target.createNewFile();
428
- final FileOutputStream fos = new FileOutputStream(target);
429
-
430
- final long totalLength = connection.getContentLength();
431
- final int bufferSize = 1024;
432
- final byte[] buffer = new byte[bufferSize];
433
- int length;
434
-
435
- int bytesRead = bufferSize;
436
- int percent = 0;
437
- this.notifyDownload(id, 10);
438
- while ((length = dis.read(buffer)) > 0) {
439
- fos.write(buffer, 0, length);
440
- final int newPercent = (int) ((bytesRead / (float) totalLength) * 100);
441
- if (totalLength > 1 && newPercent != percent) {
442
- percent = newPercent;
443
- this.notifyDownload(id, this.calcTotalPercent(percent, 10, 70));
444
- }
445
- bytesRead += length;
400
+
401
+ private void setCurrentBundle(final File bundle) {
402
+ this.editor.putString(WebView.CAP_SERVER_PATH, bundle.getPath());
403
+ Log.i(TAG, "Current bundle set to: " + bundle);
404
+ this.editor.commit();
446
405
  }
447
- return target;
448
- }
449
-
450
- private void deleteDirectory(final File file) throws IOException {
451
- if (file.isDirectory()) {
452
- final File[] entries = file.listFiles();
453
- if (entries != null) {
454
- for (final File entry : entries) {
455
- this.deleteDirectory(entry);
406
+
407
+ public void downloadBackground(
408
+ final String url,
409
+ final String version,
410
+ final String sessionKey,
411
+ final String checksum,
412
+ final JSONArray manifest
413
+ ) {
414
+ final String id = this.randomString();
415
+
416
+ // Check if version is already downloading
417
+ if (this.activity != null && DownloadWorkerManager.isVersionDownloading(version)) {
418
+ Log.i(TAG, "Version already downloading: " + version);
419
+ return;
456
420
  }
457
- }
421
+
422
+ this.saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
423
+ this.notifyDownload(id, 0);
424
+ this.notifyDownload(id, 5);
425
+
426
+ this.download(id, url, this.randomString(), version, sessionKey, checksum, manifest);
458
427
  }
459
- if (!file.delete()) {
460
- throw new IOException("Failed to delete: " + file);
428
+
429
+ public BundleInfo download(final String url, final String version, final String sessionKey, final String checksum) throws IOException {
430
+ final String id = this.randomString();
431
+ this.saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
432
+ this.notifyDownload(id, 0);
433
+ this.notifyDownload(id, 5);
434
+ final String dest = this.randomString();
435
+
436
+ // Use the new WorkManager-based download
437
+ this.download(id, url, dest, version, sessionKey, checksum, null);
438
+
439
+ // Wait for completion
440
+ try {
441
+ ListenableFuture<List<WorkInfo>> future = WorkManager.getInstance(activity).getWorkInfosByTag(id);
442
+
443
+ List<WorkInfo> workInfos = Futures.getChecked(future, IOException.class);
444
+
445
+ if (workInfos != null && !workInfos.isEmpty()) {
446
+ WorkInfo workInfo = workInfos.get(0);
447
+ while (!workInfo.getState().isFinished()) {
448
+ Thread.sleep(100);
449
+ workInfos = Futures.getChecked(WorkManager.getInstance(activity).getWorkInfosByTag(id), IOException.class);
450
+ if (workInfos != null && !workInfos.isEmpty()) {
451
+ workInfo = workInfos.get(0);
452
+ }
453
+ }
454
+
455
+ if (workInfo.getState() != WorkInfo.State.SUCCEEDED) {
456
+ Data outputData = workInfo.getOutputData();
457
+ String error = outputData.getString(DownloadService.ERROR);
458
+ throw new IOException(error != null ? error : "Download failed: " + workInfo.getState());
459
+ }
460
+ }
461
+ return getBundleInfo(id);
462
+ } catch (Exception e) {
463
+ Log.e(TAG, "Error waiting for download", e);
464
+ saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), ""));
465
+ throw new IOException("Error waiting for download: " + e.getMessage());
466
+ }
461
467
  }
462
- }
463
-
464
- private void setCurrentBundle(final File bundle) {
465
- this.editor.putString(WebView.CAP_SERVER_PATH, bundle.getPath());
466
- Log.i(TAG, "Current bundle set to: " + bundle);
467
- this.editor.commit();
468
- }
469
-
470
- private String getChecksum(File file) throws IOException {
471
- byte[] bytes = new byte[(int) file.length()];
472
- try (FileInputStream fis = new FileInputStream(file)) {
473
- fis.read(bytes);
468
+
469
+ public List<BundleInfo> list(boolean rawList) {
470
+ if (!rawList) {
471
+ final List<BundleInfo> res = new ArrayList<>();
472
+ final File destHot = new File(this.documentsDir, bundleDirectory);
473
+ Log.d(TAG, "list File : " + destHot.getPath());
474
+ if (destHot.exists()) {
475
+ for (final File i : Objects.requireNonNull(destHot.listFiles())) {
476
+ final String id = i.getName();
477
+ res.add(this.getBundleInfo(id));
478
+ }
479
+ } else {
480
+ Log.i(TAG, "No versions available to list" + destHot);
481
+ }
482
+ return res;
483
+ } else {
484
+ final List<BundleInfo> res = new ArrayList<>();
485
+ for (String value : this.prefs.getAll().keySet()) {
486
+ if (!value.matches("^[0-9A-Za-z]{10}_info$")) {
487
+ continue;
488
+ }
489
+
490
+ res.add(this.getBundleInfo(value.split("_")[0]));
491
+ }
492
+ return res;
493
+ }
474
494
  }
475
- CRC32 crc = new CRC32();
476
- crc.update(bytes);
477
- String enc = String.format("%08X", crc.getValue());
478
- return enc.toLowerCase();
479
- }
480
-
481
- private void decryptFile(
482
- final File file,
483
- final String ivSessionKey,
484
- final String version
485
- ) throws IOException {
486
- // (str != null && !str.isEmpty())
487
- if (
488
- this.privateKey == null ||
489
- this.privateKey.isEmpty() ||
490
- ivSessionKey == null ||
491
- ivSessionKey.isEmpty()
492
- ) {
493
- return;
495
+
496
+ public Boolean delete(final String id, final Boolean removeInfo) throws IOException {
497
+ final BundleInfo deleted = this.getBundleInfo(id);
498
+ if (deleted.isBuiltin() || this.getCurrentBundleId().equals(id)) {
499
+ Log.e(TAG, "Cannot delete " + id);
500
+ return false;
501
+ }
502
+ final BundleInfo next = this.getNextBundle();
503
+ if (next != null && !next.isDeleted() && !next.isErrorStatus() && next.getId().equals(id)) {
504
+ Log.e(TAG, "Cannot delete the next bundle" + id);
505
+ return false;
506
+ }
507
+ // Cancel download for this version if active
508
+ if (this.activity != null) {
509
+ DownloadWorkerManager.cancelVersionDownload(this.activity, deleted.getVersionName());
510
+ }
511
+ final File bundle = new File(this.documentsDir, bundleDirectory + "/" + id);
512
+ if (bundle.exists()) {
513
+ this.deleteDirectory(bundle);
514
+ if (!removeInfo) {
515
+ this.saveBundleInfo(id, deleted.setStatus(BundleStatus.DELETED));
516
+ } else {
517
+ this.removeBundleInfo(id);
518
+ }
519
+ return true;
520
+ }
521
+ Log.e(TAG, "bundle removed: " + deleted.getVersionName());
522
+ // perhaps we did not find the bundle in the files, but if the user requested a delete, we delete
523
+ if (removeInfo) {
524
+ this.removeBundleInfo(id);
525
+ }
526
+ this.sendStats("delete", deleted.getVersionName());
527
+ return false;
494
528
  }
495
- try {
496
- String ivB64 = ivSessionKey.split(":")[0];
497
- String sessionKeyB64 = ivSessionKey.split(":")[1];
498
- byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
499
- byte[] sessionKey = Base64.decode(
500
- sessionKeyB64.getBytes(),
501
- Base64.DEFAULT
502
- );
503
- PrivateKey pKey = CryptoCipher.stringToPrivateKey(this.privateKey);
504
- byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
505
- SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
506
- byte[] content = new byte[(int) file.length()];
507
- BufferedInputStream bis = new BufferedInputStream(
508
- new FileInputStream(file)
509
- );
510
- DataInputStream dis = new DataInputStream(bis);
511
- dis.readFully(content);
512
- dis.close();
513
- byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
514
- // write the decrypted string to the file
515
- FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
516
- fos.write(decrypted);
517
- fos.close();
518
- } catch (GeneralSecurityException e) {
519
- Log.i(TAG, "decryptFile fail");
520
- this.sendStats("decrypt_fail", version);
521
- e.printStackTrace();
522
- throw new IOException("GeneralSecurityException");
529
+
530
+ public Boolean delete(final String id) {
531
+ try {
532
+ return this.delete(id, true);
533
+ } catch (IOException e) {
534
+ e.printStackTrace();
535
+ Log.i(CapacitorUpdater.TAG, "Failed to delete bundle (" + id + ")" + "\nError:\n" + e.toString());
536
+ return false;
537
+ }
523
538
  }
524
- }
525
-
526
- public void downloadBackground(
527
- final String url,
528
- final String version,
529
- final String sessionKey,
530
- final String checksum
531
- ) {
532
- final String id = this.randomString(10);
533
- this.saveBundleInfo(
534
- id,
535
- new BundleInfo(
536
- id,
537
- version,
538
- BundleStatus.DOWNLOADING,
539
- new Date(System.currentTimeMillis()),
540
- ""
541
- )
542
- );
543
- this.notifyDownload(id, 0);
544
- this.notifyDownload(id, 5);
545
- this.downloadFileBackground(
546
- id,
547
- url,
548
- version,
549
- sessionKey,
550
- checksum,
551
- this.randomString(10)
552
- );
553
- }
554
-
555
- public BundleInfo download(
556
- final String url,
557
- final String version,
558
- final String sessionKey,
559
- final String checksum
560
- ) throws IOException {
561
- final String id = this.randomString(10);
562
- this.saveBundleInfo(
563
- id,
564
- new BundleInfo(
565
- id,
566
- version,
567
- BundleStatus.DOWNLOADING,
568
- new Date(System.currentTimeMillis()),
569
- ""
570
- )
571
- );
572
- this.notifyDownload(id, 0);
573
- final String idName = bundleDirectory + "/" + id;
574
- this.notifyDownload(id, 5);
575
- final String dest = this.randomString(10);
576
- final File downloaded = this.downloadFile(id, url, dest);
577
- this.finishDownload(id, dest, version, sessionKey, checksum, false);
578
- BundleInfo info = new BundleInfo(
579
- id,
580
- version,
581
- BundleStatus.PENDING,
582
- new Date(System.currentTimeMillis()),
583
- checksum
584
- );
585
- this.saveBundleInfo(id, info);
586
- return info;
587
- }
588
-
589
- public List<BundleInfo> list() {
590
- final List<BundleInfo> res = new ArrayList<>();
591
- final File destHot = new File(this.documentsDir, bundleDirectory);
592
- Log.d(TAG, "list File : " + destHot.getPath());
593
- if (destHot.exists()) {
594
- for (final File i : destHot.listFiles()) {
595
- final String id = i.getName();
596
- res.add(this.getBundleInfo(id));
597
- }
598
- } else {
599
- Log.i(TAG, "No versions available to list" + destHot);
539
+
540
+ private File getBundleDirectory(final String id) {
541
+ return new File(this.documentsDir, bundleDirectory + "/" + id);
600
542
  }
601
- return res;
602
- }
603
-
604
- public Boolean delete(final String id, final Boolean removeInfo)
605
- throws IOException {
606
- final BundleInfo deleted = this.getBundleInfo(id);
607
- if (deleted.isBuiltin() || this.getCurrentBundleId().equals(id)) {
608
- Log.e(TAG, "Cannot delete " + id);
609
- return false;
543
+
544
+ private boolean bundleExists(final String id) {
545
+ final File bundle = this.getBundleDirectory(id);
546
+ final BundleInfo bundleInfo = this.getBundleInfo(id);
547
+ return (bundle.isDirectory() && bundle.exists() && new File(bundle.getPath(), "/index.html").exists() && !bundleInfo.isDeleted());
610
548
  }
611
- final File bundle = new File(this.documentsDir, bundleDirectory + "/" + id);
612
- if (bundle.exists()) {
613
- this.deleteDirectory(bundle);
614
- if (removeInfo) {
615
- this.removeBundleInfo(id);
616
- } else {
617
- this.saveBundleInfo(id, deleted.setStatus(BundleStatus.DELETED));
618
- }
619
- return true;
549
+
550
+ public Boolean set(final BundleInfo bundle) {
551
+ return this.set(bundle.getId());
620
552
  }
621
- Log.e(TAG, "bundle removed: " + deleted.getVersionName());
622
- this.sendStats("delete", deleted.getVersionName());
623
- return false;
624
- }
625
-
626
- public Boolean delete(final String id) throws IOException {
627
- return this.delete(id, true);
628
- }
629
-
630
- private File getBundleDirectory(final String id) {
631
- return new File(this.documentsDir, bundleDirectory + "/" + id);
632
- }
633
-
634
- private boolean bundleExists(final String id) {
635
- final File bundle = this.getBundleDirectory(id);
636
- final BundleInfo bundleInfo = this.getBundleInfo(id);
637
- if (bundle == null || !bundle.exists() || bundleInfo.isDeleted()) {
638
- return false;
553
+
554
+ public Boolean set(final String id) {
555
+ final BundleInfo newBundle = this.getBundleInfo(id);
556
+ if (newBundle.isBuiltin()) {
557
+ this.reset();
558
+ return true;
559
+ }
560
+ final File bundle = this.getBundleDirectory(id);
561
+ Log.i(TAG, "Setting next active bundle: " + id);
562
+ if (this.bundleExists(id)) {
563
+ var currentBundleName = this.getCurrentBundle().getVersionName();
564
+ this.setCurrentBundle(bundle);
565
+ this.setBundleStatus(id, BundleStatus.PENDING);
566
+ this.sendStats("set", newBundle.getVersionName(), currentBundleName);
567
+ return true;
568
+ }
569
+ this.setBundleStatus(id, BundleStatus.ERROR);
570
+ this.sendStats("set_fail", newBundle.getVersionName());
571
+ return false;
639
572
  }
640
- return new File(bundle.getPath(), "/index.html").exists();
641
- }
642
-
643
- public Boolean set(final BundleInfo bundle) {
644
- return this.set(bundle.getId());
645
- }
646
-
647
- public Boolean set(final String id) {
648
- final BundleInfo newBundle = this.getBundleInfo(id);
649
- if (newBundle.isBuiltin()) {
650
- this.reset();
651
- return true;
573
+
574
+ public void autoReset() {
575
+ final BundleInfo currentBundle = this.getCurrentBundle();
576
+ if (!currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())) {
577
+ Log.i(TAG, "Folder at bundle path does not exist. Triggering reset.");
578
+ this.reset();
579
+ }
652
580
  }
653
- final File bundle = this.getBundleDirectory(id);
654
- Log.i(TAG, "Setting next active bundle: " + id);
655
- if (this.bundleExists(id)) {
656
- this.setCurrentBundle(bundle);
657
- this.setBundleStatus(id, BundleStatus.PENDING);
658
- this.sendStats("set", newBundle.getVersionName());
659
- return true;
581
+
582
+ public void reset() {
583
+ this.reset(false);
660
584
  }
661
- this.setBundleStatus(id, BundleStatus.ERROR);
662
- this.sendStats("set_fail", newBundle.getVersionName());
663
- return false;
664
- }
665
-
666
- public void reset() {
667
- this.reset(false);
668
- }
669
-
670
- public void setSuccess(final BundleInfo bundle, Boolean autoDeletePrevious) {
671
- this.setBundleStatus(bundle.getId(), BundleStatus.SUCCESS);
672
- final BundleInfo fallback = this.getFallbackBundle();
673
- Log.d(CapacitorUpdater.TAG, "Fallback bundle is: " + fallback);
674
- Log.i(
675
- CapacitorUpdater.TAG,
676
- "Version successfully loaded: " + bundle.getVersionName()
677
- );
678
- if (autoDeletePrevious && !fallback.isBuiltin()) {
679
- try {
680
- final Boolean res = this.delete(fallback.getId());
681
- if (res) {
682
- Log.i(
683
- CapacitorUpdater.TAG,
684
- "Deleted previous bundle: " + fallback.getVersionName()
685
- );
585
+
586
+ public void setSuccess(final BundleInfo bundle, Boolean autoDeletePrevious) {
587
+ this.setBundleStatus(bundle.getId(), BundleStatus.SUCCESS);
588
+ final BundleInfo fallback = this.getFallbackBundle();
589
+ Log.d(CapacitorUpdater.TAG, "Fallback bundle is: " + fallback);
590
+ Log.i(CapacitorUpdater.TAG, "Version successfully loaded: " + bundle.getVersionName());
591
+ if (autoDeletePrevious && !fallback.isBuiltin()) {
592
+ final Boolean res = this.delete(fallback.getId());
593
+ if (res) {
594
+ Log.i(CapacitorUpdater.TAG, "Deleted previous bundle: " + fallback.getVersionName());
595
+ }
686
596
  }
687
- } catch (final IOException e) {
688
- Log.e(
689
- CapacitorUpdater.TAG,
690
- "Failed to delete previous bundle: " + fallback.getVersionName(),
691
- e
692
- );
693
- }
597
+ this.setFallbackBundle(bundle);
694
598
  }
695
- this.setFallbackBundle(bundle);
696
- }
697
-
698
- public void setError(final BundleInfo bundle) {
699
- this.setBundleStatus(bundle.getId(), BundleStatus.ERROR);
700
- }
701
-
702
- public void reset(final boolean internal) {
703
- Log.d(CapacitorUpdater.TAG, "reset: " + internal);
704
- this.setCurrentBundle(new File("public"));
705
- this.setFallbackBundle(null);
706
- this.setNextBundle(null);
707
- if (!internal) {
708
- this.sendStats("reset", this.getCurrentBundle().getVersionName());
599
+
600
+ public void setError(final BundleInfo bundle) {
601
+ this.setBundleStatus(bundle.getId(), BundleStatus.ERROR);
709
602
  }
710
- }
711
-
712
- private JSONObject createInfoObject() throws JSONException {
713
- JSONObject json = new JSONObject();
714
- json.put("platform", "android");
715
- json.put("device_id", this.deviceID);
716
- json.put("app_id", this.appId);
717
- json.put("custom_id", this.customId);
718
- json.put("version_build", this.versionBuild);
719
- json.put("version_code", this.versionCode);
720
- json.put("version_os", this.versionOs);
721
- json.put("version_name", this.getCurrentBundle().getVersionName());
722
- json.put("plugin_version", this.PLUGIN_VERSION);
723
- json.put("is_emulator", this.isEmulator());
724
- json.put("is_prod", this.isProd());
725
- return json;
726
- }
727
-
728
- private JSObject createError(String message, VolleyError error) {
729
- NetworkResponse response = error.networkResponse;
730
- final JSObject retError = new JSObject();
731
- retError.put("error", "response_error");
732
- if (response != null) {
733
- try {
734
- String json = new String(
735
- response.data,
736
- HttpHeaderParser.parseCharset(response.headers)
737
- );
738
- retError.put("message", message + ": " + json);
739
- } catch (UnsupportedEncodingException e) {
740
- retError.put("message", message + ": " + e.toString());
741
- }
742
- } else {
743
- retError.put("message", message + ": " + error.toString());
603
+
604
+ public void reset(final boolean internal) {
605
+ Log.d(CapacitorUpdater.TAG, "reset: " + internal);
606
+ var currentBundleName = this.getCurrentBundle().getVersionName();
607
+ this.setCurrentBundle(new File("public"));
608
+ this.setFallbackBundle(null);
609
+ this.setNextBundle(null);
610
+ // Cancel any ongoing downloads
611
+ if (this.activity != null) {
612
+ DownloadWorkerManager.cancelAllDownloads(this.activity);
613
+ }
614
+ if (!internal) {
615
+ this.sendStats("reset", this.getCurrentBundle().getVersionName(), currentBundleName);
616
+ }
744
617
  }
745
- Log.e(TAG, message + ": " + retError);
746
- return retError;
747
- }
748
-
749
- public void getLatest(final String updateUrl, final Callback callback) {
750
- JSONObject json = null;
751
- try {
752
- json = this.createInfoObject();
753
- } catch (JSONException e) {
754
- Log.e(TAG, "Error getLatest JSONException", e);
755
- e.printStackTrace();
756
- final JSObject retError = new JSObject();
757
- retError.put("message", "Cannot get info: " + e.toString());
758
- retError.put("error", "json_error");
759
- callback.callback(retError);
760
- return;
618
+
619
+ private JSONObject createInfoObject() throws JSONException {
620
+ JSONObject json = new JSONObject();
621
+ json.put("platform", "android");
622
+ json.put("device_id", this.deviceID);
623
+ json.put("app_id", this.appId);
624
+ json.put("custom_id", this.customId);
625
+ json.put("version_build", this.versionBuild);
626
+ json.put("version_code", this.versionCode);
627
+ json.put("version_os", this.versionOs);
628
+ json.put("version_name", this.getCurrentBundle().getVersionName());
629
+ json.put("plugin_version", this.PLUGIN_VERSION);
630
+ json.put("is_emulator", this.isEmulator());
631
+ json.put("is_prod", this.isProd());
632
+ json.put("defaultChannel", this.defaultChannel);
633
+ return json;
761
634
  }
762
635
 
763
- Log.i(CapacitorUpdater.TAG, "Auto-update parameters: " + json);
764
- // Building a request
765
- JsonObjectRequest request = new JsonObjectRequest(
766
- Request.Method.POST,
767
- updateUrl,
768
- json,
769
- new Response.Listener<JSONObject>() {
770
- @Override
771
- public void onResponse(JSONObject res) {
772
- final JSObject ret = new JSObject();
773
- Iterator<String> keys = res.keys();
774
- while (keys.hasNext()) {
775
- String key = keys.next();
776
- if (res.has(key)) {
777
- try {
778
- if ("session_key".equals(key)) {
779
- ret.put("sessionKey", res.get(key));
780
- } else {
781
- ret.put(key, res.get(key));
636
+ private void makeJsonRequest(String url, JSONObject jsonBody, Callback callback) {
637
+ MediaType JSON = MediaType.get("application/json; charset=utf-8");
638
+ RequestBody body = RequestBody.create(jsonBody.toString(), JSON);
639
+
640
+ Request request = new Request.Builder().url(url).post(body).build();
641
+
642
+ client
643
+ .newCall(request)
644
+ .enqueue(
645
+ new okhttp3.Callback() {
646
+ @Override
647
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
648
+ JSObject retError = new JSObject();
649
+ retError.put("message", "Request failed: " + e.getMessage());
650
+ retError.put("error", "network_error");
651
+ callback.callback(retError);
652
+ }
653
+
654
+ @Override
655
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
656
+ try (ResponseBody responseBody = response.body()) {
657
+ if (!response.isSuccessful()) {
658
+ JSObject retError = new JSObject();
659
+ retError.put("message", "Server error: " + response.code());
660
+ retError.put("error", "response_error");
661
+ callback.callback(retError);
662
+ return;
663
+ }
664
+
665
+ assert responseBody != null;
666
+ String responseData = responseBody.string();
667
+ JSONObject jsonResponse = new JSONObject(responseData);
668
+ JSObject ret = new JSObject();
669
+
670
+ Iterator<String> keys = jsonResponse.keys();
671
+ while (keys.hasNext()) {
672
+ String key = keys.next();
673
+ if (jsonResponse.has(key)) {
674
+ if ("session_key".equals(key)) {
675
+ ret.put("sessionKey", jsonResponse.get(key));
676
+ } else {
677
+ ret.put(key, jsonResponse.get(key));
678
+ }
679
+ }
680
+ }
681
+ callback.callback(ret);
682
+ } catch (JSONException e) {
683
+ JSObject retError = new JSObject();
684
+ retError.put("message", "JSON parse error: " + e.getMessage());
685
+ retError.put("error", "parse_error");
686
+ callback.callback(retError);
687
+ }
688
+ }
782
689
  }
783
- } catch (JSONException e) {
784
- e.printStackTrace();
785
- final JSObject retError = new JSObject();
786
- retError.put("message", "Cannot set info: " + e.toString());
787
- retError.put("error", "response_error");
788
- callback.callback(retError);
789
- }
690
+ );
691
+ }
692
+
693
+ public void getLatest(final String updateUrl, final String channel, final Callback callback) {
694
+ JSONObject json;
695
+ try {
696
+ json = this.createInfoObject();
697
+ if (channel != null && json != null) {
698
+ json.put("defaultChannel", channel);
790
699
  }
791
- }
792
- callback.callback(ret);
700
+ } catch (JSONException e) {
701
+ Log.e(TAG, "Error getLatest JSONException", e);
702
+ final JSObject retError = new JSObject();
703
+ retError.put("message", "Cannot get info: " + e);
704
+ retError.put("error", "json_error");
705
+ callback.callback(retError);
706
+ return;
793
707
  }
794
- },
795
- new Response.ErrorListener() {
796
- @Override
797
- public void onErrorResponse(VolleyError error) {
798
- callback.callback(
799
- CapacitorUpdater.this.createError("Error get latest", error)
800
- );
708
+
709
+ Log.i(CapacitorUpdater.TAG, "Auto-update parameters: " + json);
710
+
711
+ makeJsonRequest(updateUrl, json, callback);
712
+ }
713
+
714
+ public void unsetChannel(final Callback callback) {
715
+ String channelUrl = this.channelUrl;
716
+ if (channelUrl == null || channelUrl.isEmpty()) {
717
+ Log.e(TAG, "Channel URL is not set");
718
+ final JSObject retError = new JSObject();
719
+ retError.put("message", "channelUrl missing");
720
+ retError.put("error", "missing_config");
721
+ callback.callback(retError);
722
+ return;
801
723
  }
802
- }
803
- );
804
- request.setRetryPolicy(
805
- new DefaultRetryPolicy(
806
- this.timeout,
807
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
808
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
809
- )
810
- );
811
- this.requestQueue.add(request);
812
- }
813
-
814
- public void setChannel(final String channel, final Callback callback) {
815
- String channelUrl = this.channelUrl;
816
- if (
817
- channelUrl == null || "".equals(channelUrl) || channelUrl.length() == 0
818
- ) {
819
- Log.e(TAG, "Channel URL is not set");
820
- final JSObject retError = new JSObject();
821
- retError.put("message", "channelUrl missing");
822
- retError.put("error", "missing_config");
823
- callback.callback(retError);
824
- return;
724
+ JSONObject json;
725
+ try {
726
+ json = this.createInfoObject();
727
+ } catch (JSONException e) {
728
+ Log.e(TAG, "Error unsetChannel JSONException", e);
729
+ final JSObject retError = new JSObject();
730
+ retError.put("message", "Cannot get info: " + e);
731
+ retError.put("error", "json_error");
732
+ callback.callback(retError);
733
+ return;
734
+ }
735
+
736
+ Request request = new Request.Builder()
737
+ .url(channelUrl)
738
+ .delete(RequestBody.create(json.toString(), MediaType.get("application/json")))
739
+ .build();
740
+
741
+ client
742
+ .newCall(request)
743
+ .enqueue(
744
+ new okhttp3.Callback() {
745
+ @Override
746
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
747
+ JSObject retError = new JSObject();
748
+ retError.put("message", "Request failed: " + e.getMessage());
749
+ retError.put("error", "network_error");
750
+ callback.callback(retError);
751
+ }
752
+
753
+ @Override
754
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
755
+ try (ResponseBody responseBody = response.body()) {
756
+ if (!response.isSuccessful()) {
757
+ JSObject retError = new JSObject();
758
+ retError.put("message", "Server error: " + response.code());
759
+ retError.put("error", "response_error");
760
+ callback.callback(retError);
761
+ return;
762
+ }
763
+
764
+ assert responseBody != null;
765
+ String responseData = responseBody.string();
766
+ JSONObject jsonResponse = new JSONObject(responseData);
767
+ JSObject ret = new JSObject();
768
+
769
+ Iterator<String> keys = jsonResponse.keys();
770
+ while (keys.hasNext()) {
771
+ String key = keys.next();
772
+ if (jsonResponse.has(key)) {
773
+ ret.put(key, jsonResponse.get(key));
774
+ }
775
+ }
776
+ Log.i(TAG, "Channel unset");
777
+ callback.callback(ret);
778
+ } catch (JSONException e) {
779
+ JSObject retError = new JSObject();
780
+ retError.put("message", "JSON parse error: " + e.getMessage());
781
+ retError.put("error", "parse_error");
782
+ callback.callback(retError);
783
+ }
784
+ }
785
+ }
786
+ );
825
787
  }
826
- JSONObject json = null;
827
- try {
828
- json = this.createInfoObject();
829
- json.put("channel", channel);
830
- } catch (JSONException e) {
831
- Log.e(TAG, "Error setChannel JSONException", e);
832
- e.printStackTrace();
833
- final JSObject retError = new JSObject();
834
- retError.put("message", "Cannot get info: " + e.toString());
835
- retError.put("error", "json_error");
836
- callback.callback(retError);
837
- return;
788
+
789
+ public void setChannel(final String channel, final Callback callback) {
790
+ String channelUrl = this.channelUrl;
791
+ if (channelUrl == null || channelUrl.isEmpty()) {
792
+ Log.e(TAG, "Channel URL is not set");
793
+ final JSObject retError = new JSObject();
794
+ retError.put("message", "channelUrl missing");
795
+ retError.put("error", "missing_config");
796
+ callback.callback(retError);
797
+ return;
798
+ }
799
+ JSONObject json;
800
+ try {
801
+ json = this.createInfoObject();
802
+ json.put("channel", channel);
803
+ } catch (JSONException e) {
804
+ Log.e(TAG, "Error setChannel JSONException", e);
805
+ final JSObject retError = new JSObject();
806
+ retError.put("message", "Cannot get info: " + e);
807
+ retError.put("error", "json_error");
808
+ callback.callback(retError);
809
+ return;
810
+ }
811
+
812
+ makeJsonRequest(channelUrl, json, callback);
838
813
  }
839
- // Building a request
840
- JsonObjectRequest request = new JsonObjectRequest(
841
- Request.Method.POST,
842
- channelUrl,
843
- json,
844
- new Response.Listener<JSONObject>() {
845
- @Override
846
- public void onResponse(JSONObject res) {
847
- final JSObject ret = new JSObject();
848
- Iterator<String> keys = res.keys();
849
- while (keys.hasNext()) {
850
- String key = keys.next();
851
- if (res.has(key)) {
852
- try {
853
- ret.put(key, res.get(key));
854
- } catch (JSONException e) {
855
- e.printStackTrace();
856
- final JSObject retError = new JSObject();
857
- retError.put("message", "Cannot set channel: " + e.toString());
858
- retError.put("error", "response_error");
859
- callback.callback(ret);
860
- }
861
- }
862
- }
863
- Log.i(TAG, "Channel set to \"" + channel);
864
- callback.callback(ret);
814
+
815
+ public void getChannel(final Callback callback) {
816
+ String channelUrl = this.channelUrl;
817
+ if (channelUrl == null || channelUrl.isEmpty()) {
818
+ Log.e(TAG, "Channel URL is not set");
819
+ final JSObject retError = new JSObject();
820
+ retError.put("message", "Channel URL is not set");
821
+ retError.put("error", "missing_config");
822
+ callback.callback(retError);
823
+ return;
865
824
  }
866
- },
867
- new Response.ErrorListener() {
868
- @Override
869
- public void onErrorResponse(VolleyError error) {
870
- callback.callback(
871
- CapacitorUpdater.this.createError("Error set channel", error)
872
- );
825
+ JSONObject json;
826
+ try {
827
+ json = this.createInfoObject();
828
+ } catch (JSONException e) {
829
+ Log.e(TAG, "Error getChannel JSONException", e);
830
+ final JSObject retError = new JSObject();
831
+ retError.put("message", "Cannot get info: " + e);
832
+ retError.put("error", "json_error");
833
+ callback.callback(retError);
834
+ return;
873
835
  }
874
- }
875
- );
876
- request.setRetryPolicy(
877
- new DefaultRetryPolicy(
878
- this.timeout,
879
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
880
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
881
- )
882
- );
883
- this.requestQueue.add(request);
884
- }
885
-
886
- public void getChannel(final Callback callback) {
887
- String channelUrl = this.channelUrl;
888
- if (
889
- channelUrl == null || "".equals(channelUrl) || channelUrl.length() == 0
890
- ) {
891
- Log.e(TAG, "Channel URL is not set");
892
- final JSObject retError = new JSObject();
893
- retError.put("message", "Channel URL is not set");
894
- retError.put("error", "missing_config");
895
- callback.callback(retError);
896
- return;
836
+
837
+ Request request = new Request.Builder()
838
+ .url(channelUrl)
839
+ .put(RequestBody.create(json.toString(), MediaType.get("application/json")))
840
+ .build();
841
+
842
+ client
843
+ .newCall(request)
844
+ .enqueue(
845
+ new okhttp3.Callback() {
846
+ @Override
847
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
848
+ JSObject retError = new JSObject();
849
+ retError.put("message", "Request failed: " + e.getMessage());
850
+ retError.put("error", "network_error");
851
+ callback.callback(retError);
852
+ }
853
+
854
+ @Override
855
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
856
+ try (ResponseBody responseBody = response.body()) {
857
+ if (response.code() == 400) {
858
+ assert responseBody != null;
859
+ String data = responseBody.string();
860
+ if (data.contains("channel_not_found") && !defaultChannel.isEmpty()) {
861
+ JSObject ret = new JSObject();
862
+ ret.put("channel", defaultChannel);
863
+ ret.put("status", "default");
864
+ Log.i(TAG, "Channel get to \"" + ret);
865
+ callback.callback(ret);
866
+ return;
867
+ }
868
+ }
869
+
870
+ if (!response.isSuccessful()) {
871
+ JSObject retError = new JSObject();
872
+ retError.put("message", "Server error: " + response.code());
873
+ retError.put("error", "response_error");
874
+ callback.callback(retError);
875
+ return;
876
+ }
877
+
878
+ assert responseBody != null;
879
+ String responseData = responseBody.string();
880
+ JSONObject jsonResponse = new JSONObject(responseData);
881
+ JSObject ret = new JSObject();
882
+
883
+ Iterator<String> keys = jsonResponse.keys();
884
+ while (keys.hasNext()) {
885
+ String key = keys.next();
886
+ if (jsonResponse.has(key)) {
887
+ ret.put(key, jsonResponse.get(key));
888
+ }
889
+ }
890
+ Log.i(TAG, "Channel get to \"" + ret);
891
+ callback.callback(ret);
892
+ } catch (JSONException e) {
893
+ JSObject retError = new JSObject();
894
+ retError.put("message", "JSON parse error: " + e.getMessage());
895
+ retError.put("error", "parse_error");
896
+ callback.callback(retError);
897
+ }
898
+ }
899
+ }
900
+ );
901
+ }
902
+
903
+ public void sendStats(final String action) {
904
+ this.sendStats(action, this.getCurrentBundle().getVersionName());
897
905
  }
898
- JSONObject json = null;
899
- try {
900
- json = this.createInfoObject();
901
- } catch (JSONException e) {
902
- Log.e(TAG, "Error getChannel JSONException", e);
903
- e.printStackTrace();
904
- final JSObject retError = new JSObject();
905
- retError.put("message", "Cannot get info: " + e.toString());
906
- retError.put("error", "json_error");
907
- callback.callback(retError);
908
- return;
906
+
907
+ public void sendStats(final String action, final String versionName) {
908
+ this.sendStats(action, versionName, "");
909
909
  }
910
- // Building a request
911
- JsonObjectRequest request = new JsonObjectRequest(
912
- Request.Method.PUT,
913
- channelUrl,
914
- json,
915
- new Response.Listener<JSONObject>() {
916
- @Override
917
- public void onResponse(JSONObject res) {
918
- final JSObject ret = new JSObject();
919
- Iterator<String> keys = res.keys();
920
- while (keys.hasNext()) {
921
- String key = keys.next();
922
- if (res.has(key)) {
923
- try {
924
- ret.put(key, res.get(key));
925
- } catch (JSONException e) {
926
- e.printStackTrace();
927
- }
928
- }
929
- }
930
- Log.i(TAG, "Channel get to \"" + ret);
931
- callback.callback(ret);
910
+
911
+ public void sendStats(final String action, final String versionName, final String oldVersionName) {
912
+ String statsUrl = this.statsUrl;
913
+ if (statsUrl == null || statsUrl.isEmpty()) {
914
+ return;
932
915
  }
933
- },
934
- new Response.ErrorListener() {
935
- @Override
936
- public void onErrorResponse(VolleyError error) {
937
- callback.callback(
938
- CapacitorUpdater.this.createError("Error get channel", error)
939
- );
916
+ JSONObject json;
917
+ try {
918
+ json = this.createInfoObject();
919
+ json.put("version_name", versionName);
920
+ json.put("old_version_name", oldVersionName);
921
+ json.put("action", action);
922
+ } catch (JSONException e) {
923
+ Log.e(TAG, "Error sendStats JSONException", e);
924
+ return;
940
925
  }
941
- }
942
- );
943
- request.setRetryPolicy(
944
- new DefaultRetryPolicy(
945
- this.timeout,
946
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
947
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
948
- )
949
- );
950
- this.requestQueue.add(request);
951
- }
952
-
953
- public void sendStats(final String action, final String versionName) {
954
- String statsUrl = this.statsUrl;
955
- if (statsUrl == null || "".equals(statsUrl) || statsUrl.length() == 0) {
956
- return;
957
- }
958
- JSONObject json = null;
959
- try {
960
- json = this.createInfoObject();
961
- json.put("action", action);
962
- } catch (JSONException e) {
963
- Log.e(TAG, "Error sendStats JSONException", e);
964
- e.printStackTrace();
965
- return;
926
+
927
+ Request request = new Request.Builder()
928
+ .url(statsUrl)
929
+ .post(RequestBody.create(json.toString(), MediaType.get("application/json")))
930
+ .build();
931
+
932
+ client
933
+ .newCall(request)
934
+ .enqueue(
935
+ new okhttp3.Callback() {
936
+ @Override
937
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
938
+ Log.e(TAG, "Failed to send stats: " + e.getMessage());
939
+ }
940
+
941
+ @Override
942
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
943
+ if (response.isSuccessful()) {
944
+ Log.i(TAG, "Stats send for \"" + action + "\", version " + versionName);
945
+ } else {
946
+ Log.e(TAG, "Error sending stats: " + response.code());
947
+ }
948
+ }
949
+ }
950
+ );
966
951
  }
967
- // Building a request
968
- JsonObjectRequest request = new JsonObjectRequest(
969
- Request.Method.POST,
970
- statsUrl,
971
- json,
972
- new Response.Listener<JSONObject>() {
973
- @Override
974
- public void onResponse(JSONObject response) {
975
- Log.i(
976
- TAG,
977
- "Stats send for \"" + action + "\", version " + versionName
978
- );
952
+
953
+ public BundleInfo getBundleInfo(final String id) {
954
+ String trueId = BundleInfo.VERSION_UNKNOWN;
955
+ if (id != null) {
956
+ trueId = id;
979
957
  }
980
- },
981
- new Response.ErrorListener() {
982
- @Override
983
- public void onErrorResponse(VolleyError error) {
984
- CapacitorUpdater.this.createError("Error send stats", error);
958
+ BundleInfo result;
959
+ if (BundleInfo.ID_BUILTIN.equals(trueId)) {
960
+ result = new BundleInfo(trueId, null, BundleStatus.SUCCESS, "", "");
961
+ } else if (BundleInfo.VERSION_UNKNOWN.equals(trueId)) {
962
+ result = new BundleInfo(trueId, null, BundleStatus.ERROR, "", "");
963
+ } else {
964
+ try {
965
+ String stored = this.prefs.getString(trueId + INFO_SUFFIX, "");
966
+ result = BundleInfo.fromJSON(stored);
967
+ } catch (JSONException e) {
968
+ Log.e(TAG, "Failed to parse info for bundle [" + trueId + "] ", e);
969
+ result = new BundleInfo(trueId, null, BundleStatus.PENDING, "", "");
970
+ }
985
971
  }
986
- }
987
- );
988
- request.setRetryPolicy(
989
- new DefaultRetryPolicy(
990
- this.timeout,
991
- DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
992
- DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
993
- )
994
- );
995
- this.requestQueue.add(request);
996
- }
997
-
998
- public BundleInfo getBundleInfo(final String id) {
999
- String trueId = BundleInfo.VERSION_UNKNOWN;
1000
- if (id != null) {
1001
- trueId = id;
972
+ // Log.d(TAG, "Returning info [" + trueId + "] " + result);
973
+ return result;
1002
974
  }
1003
- Log.d(TAG, "Getting info for bundle [" + trueId + "]");
1004
- BundleInfo result;
1005
- if (BundleInfo.ID_BUILTIN.equals(trueId)) {
1006
- result = new BundleInfo(trueId, null, BundleStatus.SUCCESS, "", "");
1007
- } else if (BundleInfo.VERSION_UNKNOWN.equals(trueId)) {
1008
- result = new BundleInfo(trueId, null, BundleStatus.ERROR, "", "");
1009
- } else {
1010
- try {
1011
- String stored = this.prefs.getString(trueId + INFO_SUFFIX, "");
1012
- result = BundleInfo.fromJSON(stored);
1013
- } catch (JSONException e) {
1014
- Log.e(TAG, "Failed to parse info for bundle [" + trueId + "] ", e);
1015
- result = new BundleInfo(trueId, null, BundleStatus.PENDING, "", "");
1016
- }
975
+
976
+ public BundleInfo getBundleInfoByName(final String versionName) {
977
+ final List<BundleInfo> installed = this.list(false);
978
+ for (final BundleInfo i : installed) {
979
+ if (i.getVersionName().equals(versionName)) {
980
+ return i;
981
+ }
982
+ }
983
+ return null;
1017
984
  }
1018
- // Log.d(TAG, "Returning info [" + trueId + "] " + result);
1019
- return result;
1020
- }
1021
-
1022
- public BundleInfo getBundleInfoByName(final String versionName) {
1023
- final List<BundleInfo> installed = this.list();
1024
- for (final BundleInfo i : installed) {
1025
- if (i.getVersionName().equals(versionName)) {
1026
- return i;
1027
- }
985
+
986
+ private void removeBundleInfo(final String id) {
987
+ this.saveBundleInfo(id, null);
1028
988
  }
1029
- return null;
1030
- }
1031
989
 
1032
- private void removeBundleInfo(final String id) {
1033
- this.saveBundleInfo(id, null);
1034
- }
990
+ public void saveBundleInfo(final String id, final BundleInfo info) {
991
+ if (id == null || (info != null && (info.isBuiltin() || info.isUnknown()))) {
992
+ Log.d(TAG, "Not saving info for bundle: [" + id + "] " + info);
993
+ return;
994
+ }
1035
995
 
1036
- private void saveBundleInfo(final String id, final BundleInfo info) {
1037
- if (
1038
- id == null || (info != null && (info.isBuiltin() || info.isUnknown()))
1039
- ) {
1040
- Log.d(TAG, "Not saving info for bundle: [" + id + "] " + info);
1041
- return;
996
+ if (info == null) {
997
+ Log.d(TAG, "Removing info for bundle [" + id + "]");
998
+ this.editor.remove(id + INFO_SUFFIX);
999
+ } else {
1000
+ final BundleInfo update = info.setId(id);
1001
+ Log.d(TAG, "Storing info for bundle [" + id + "] " + update.toString());
1002
+ this.editor.putString(id + INFO_SUFFIX, update.toString());
1003
+ }
1004
+ this.editor.commit();
1042
1005
  }
1043
1006
 
1044
- if (info == null) {
1045
- Log.d(TAG, "Removing info for bundle [" + id + "]");
1046
- this.editor.remove(id + INFO_SUFFIX);
1047
- } else {
1048
- final BundleInfo update = info.setId(id);
1049
- Log.d(TAG, "Storing info for bundle [" + id + "] " + update.toString());
1050
- this.editor.putString(id + INFO_SUFFIX, update.toString());
1007
+ private void setBundleStatus(final String id, final BundleStatus status) {
1008
+ if (id != null && status != null) {
1009
+ BundleInfo info = this.getBundleInfo(id);
1010
+ Log.d(TAG, "Setting status for bundle [" + id + "] to " + status);
1011
+ this.saveBundleInfo(id, info.setStatus(status));
1012
+ }
1051
1013
  }
1052
- this.editor.commit();
1053
- }
1054
-
1055
- public void setVersionName(final String id, final String name) {
1056
- if (id != null) {
1057
- Log.d(TAG, "Setting name for bundle [" + id + "] to " + name);
1058
- BundleInfo info = this.getBundleInfo(id);
1059
- this.saveBundleInfo(id, info.setVersionName(name));
1014
+
1015
+ private String getCurrentBundleId() {
1016
+ if (this.isUsingBuiltin()) {
1017
+ return BundleInfo.ID_BUILTIN;
1018
+ } else {
1019
+ final String path = this.getCurrentBundlePath();
1020
+ return path.substring(path.lastIndexOf('/') + 1);
1021
+ }
1060
1022
  }
1061
- }
1062
1023
 
1063
- private void setBundleStatus(final String id, final BundleStatus status) {
1064
- if (id != null && status != null) {
1065
- BundleInfo info = this.getBundleInfo(id);
1066
- Log.d(TAG, "Setting status for bundle [" + id + "] to " + status);
1067
- this.saveBundleInfo(id, info.setStatus(status));
1024
+ public BundleInfo getCurrentBundle() {
1025
+ return this.getBundleInfo(this.getCurrentBundleId());
1068
1026
  }
1069
- }
1070
-
1071
- private String getCurrentBundleId() {
1072
- if (this.isUsingBuiltin()) {
1073
- return BundleInfo.ID_BUILTIN;
1074
- } else {
1075
- final String path = this.getCurrentBundlePath();
1076
- return path.substring(path.lastIndexOf('/') + 1);
1027
+
1028
+ public String getCurrentBundlePath() {
1029
+ String path = this.prefs.getString(WebView.CAP_SERVER_PATH, "public");
1030
+ if (path.trim().isEmpty()) {
1031
+ return "public";
1032
+ }
1033
+ return path;
1077
1034
  }
1078
- }
1079
1035
 
1080
- public BundleInfo getCurrentBundle() {
1081
- return this.getBundleInfo(this.getCurrentBundleId());
1082
- }
1036
+ public Boolean isUsingBuiltin() {
1037
+ return this.getCurrentBundlePath().equals("public");
1038
+ }
1083
1039
 
1084
- public String getCurrentBundlePath() {
1085
- String path = this.prefs.getString(WebView.CAP_SERVER_PATH, "public");
1086
- if ("".equals(path.trim())) {
1087
- return "public";
1040
+ public BundleInfo getFallbackBundle() {
1041
+ final String id = this.prefs.getString(FALLBACK_VERSION, BundleInfo.ID_BUILTIN);
1042
+ return this.getBundleInfo(id);
1088
1043
  }
1089
- return path;
1090
- }
1091
-
1092
- public Boolean isUsingBuiltin() {
1093
- return this.getCurrentBundlePath().equals("public");
1094
- }
1095
-
1096
- public BundleInfo getFallbackBundle() {
1097
- final String id =
1098
- this.prefs.getString(FALLBACK_VERSION, BundleInfo.ID_BUILTIN);
1099
- return this.getBundleInfo(id);
1100
- }
1101
-
1102
- private void setFallbackBundle(final BundleInfo fallback) {
1103
- this.editor.putString(
1104
- FALLBACK_VERSION,
1105
- fallback == null ? BundleInfo.ID_BUILTIN : fallback.getId()
1106
- );
1107
- this.editor.commit();
1108
- }
1109
-
1110
- public BundleInfo getNextBundle() {
1111
- final String id = this.prefs.getString(NEXT_VERSION, null);
1112
- if (id == null) return null;
1113
- return this.getBundleInfo(id);
1114
- }
1115
-
1116
- public boolean setNextBundle(final String next) {
1117
- if (next == null) {
1118
- this.editor.remove(NEXT_VERSION);
1119
- } else {
1120
- final BundleInfo newBundle = this.getBundleInfo(next);
1121
- if (!newBundle.isBuiltin() && !this.bundleExists(next)) {
1122
- return false;
1123
- }
1124
- this.editor.putString(NEXT_VERSION, next);
1125
- this.setBundleStatus(next, BundleStatus.PENDING);
1044
+
1045
+ private void setFallbackBundle(final BundleInfo fallback) {
1046
+ this.editor.putString(FALLBACK_VERSION, fallback == null ? BundleInfo.ID_BUILTIN : fallback.getId());
1047
+ this.editor.commit();
1048
+ }
1049
+
1050
+ public BundleInfo getNextBundle() {
1051
+ final String id = this.prefs.getString(NEXT_VERSION, null);
1052
+ if (id == null) return null;
1053
+ return this.getBundleInfo(id);
1054
+ }
1055
+
1056
+ public boolean setNextBundle(final String next) {
1057
+ if (next == null) {
1058
+ this.editor.remove(NEXT_VERSION);
1059
+ } else {
1060
+ final BundleInfo newBundle = this.getBundleInfo(next);
1061
+ if (!newBundle.isBuiltin() && !this.bundleExists(next)) {
1062
+ return false;
1063
+ }
1064
+ this.editor.putString(NEXT_VERSION, next);
1065
+ this.setBundleStatus(next, BundleStatus.PENDING);
1066
+ }
1067
+ this.editor.commit();
1068
+ return true;
1126
1069
  }
1127
- this.editor.commit();
1128
- return true;
1129
- }
1130
1070
  }