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