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