@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.
- package/CapgoCapacitorUpdater.podspec +2 -2
- package/Package.swift +35 -0
- package/README.md +667 -206
- package/android/build.gradle +16 -11
- package/android/proguard-rules.pro +28 -0
- package/android/src/main/AndroidManifest.xml +0 -1
- 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 +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +967 -1027
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1283 -1180
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +276 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +45 -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 +440 -113
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +101 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
- package/dist/docs.json +1316 -473
- package/dist/esm/definitions.d.ts +518 -248
- 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 +25 -41
- package/dist/esm/web.js +67 -35
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +67 -35
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +67 -35
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +736 -361
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +436 -136
- package/ios/Plugin/CryptoCipherV2.swift +310 -0
- package/ios/Plugin/InternalUtils.swift +258 -0
- package/package.json +33 -29
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +0 -153
- package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
- package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
- 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
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
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.
|
|
35
|
+
import java.util.Objects;
|
|
50
36
|
import java.util.zip.ZipEntry;
|
|
51
37
|
import java.util.zip.ZipInputStream;
|
|
52
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
"
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
460
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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
|
-
|
|
1004
|
-
BundleInfo
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
1033
|
-
|
|
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
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1036
|
+
public Boolean isUsingBuiltin() {
|
|
1037
|
+
return this.getCurrentBundlePath().equals("public");
|
|
1038
|
+
}
|
|
1083
1039
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
}
|