@capgo/capacitor-updater 3.2.1 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +145 -145
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +42 -41
- package/ios/Plugin/CapacitorUpdater.swift +63 -26
- package/ios/Plugin/CapacitorUpdaterPlugin.m +1 -0
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +13 -9
- package/package.json +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterEvents.java +0 -11
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Create account in [capgo.app](https://capgo.app) and get your [API key](https://
|
|
|
43
43
|
```
|
|
44
44
|
- Add to your main code
|
|
45
45
|
```javascript
|
|
46
|
-
import { CapacitorUpdater } from 'capacitor-updater'
|
|
46
|
+
import { CapacitorUpdater } from '@capgo/capacitor-updater'
|
|
47
47
|
CapacitorUpdater.notifyAppReady()
|
|
48
48
|
// To let auto update know you app boot well.
|
|
49
49
|
```
|
|
@@ -64,7 +64,7 @@ install it when user background the app.
|
|
|
64
64
|
In your main code :
|
|
65
65
|
|
|
66
66
|
```javascript
|
|
67
|
-
import { CapacitorUpdater } from 'capacitor-updater'
|
|
67
|
+
import { CapacitorUpdater } from '@capgo/capacitor-updater'
|
|
68
68
|
import { SplashScreen } from '@capacitor/splash-screen'
|
|
69
69
|
import { App } from '@capacitor/app'
|
|
70
70
|
|
|
@@ -436,7 +436,7 @@ removeAllListeners() => Promise<void>
|
|
|
436
436
|
### Listen to download events
|
|
437
437
|
|
|
438
438
|
```javascript
|
|
439
|
-
import { CapacitorUpdater } from 'capacitor-updater';
|
|
439
|
+
import { CapacitorUpdater } from '@capgo/capacitor-updater';
|
|
440
440
|
|
|
441
441
|
CapacitorUpdater.addListener('download', (info: any) => {
|
|
442
442
|
console.log('download was fired', info.percent);
|
|
@@ -45,11 +45,32 @@ interface Callback {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
public class CapacitorUpdater {
|
|
48
|
-
|
|
48
|
+
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
49
|
+
static SecureRandom rnd = new SecureRandom();
|
|
50
|
+
private final String TAG = "Capacitor-updater";
|
|
51
|
+
private final Context context;
|
|
52
|
+
private final String basePathHot = "versions";
|
|
53
|
+
private final SharedPreferences prefs;
|
|
54
|
+
private final SharedPreferences.Editor editor;
|
|
55
|
+
private String versionBuild = "";
|
|
56
|
+
private String versionCode = "";
|
|
57
|
+
private String versionOs = "";
|
|
58
|
+
|
|
49
59
|
public String appId = "";
|
|
50
60
|
public String deviceID = "";
|
|
51
|
-
|
|
61
|
+
public final String pluginVersion = "3.3.2";
|
|
62
|
+
public String statsUrl = "";
|
|
52
63
|
|
|
64
|
+
public CapacitorUpdater (final Context context) throws PackageManager.NameNotFoundException {
|
|
65
|
+
this.context = context;
|
|
66
|
+
this.prefs = this.context.getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
|
|
67
|
+
this.editor = this.prefs.edit();
|
|
68
|
+
this.versionOs = Build.VERSION.RELEASE;
|
|
69
|
+
this.deviceID = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
|
|
70
|
+
final PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
|
71
|
+
this.versionBuild = pInfo.versionName;
|
|
72
|
+
this.versionCode = Integer.toString(pInfo.versionCode);
|
|
73
|
+
}
|
|
53
74
|
|
|
54
75
|
private final FilenameFilter filter = new FilenameFilter() {
|
|
55
76
|
@Override
|
|
@@ -58,163 +79,126 @@ public class CapacitorUpdater {
|
|
|
58
79
|
return !name.startsWith("__MACOSX") && !name.startsWith(".") && !name.startsWith(".DS_Store");
|
|
59
80
|
}
|
|
60
81
|
};
|
|
61
|
-
private final Context context;
|
|
62
|
-
private final CapacitorUpdaterEvents events;
|
|
63
|
-
|
|
64
|
-
private String versionBuild = "";
|
|
65
|
-
private String versionCode = "";
|
|
66
|
-
private String versionOs = "";
|
|
67
|
-
private final String TAG = "Capacitor-updater";
|
|
68
|
-
private final String basePathHot = "versions";
|
|
69
|
-
private final SharedPreferences prefs;
|
|
70
|
-
private final SharedPreferences.Editor editor;
|
|
71
|
-
|
|
72
|
-
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
73
|
-
static SecureRandom rnd = new SecureRandom();
|
|
74
82
|
|
|
75
83
|
private int calcTotalPercent(final int percent, final int min, final int max) {
|
|
76
84
|
return (percent * (max - min)) / 100 + min;
|
|
77
85
|
}
|
|
78
86
|
|
|
87
|
+
void notifyDownload(final int percent) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
private String randomString(final int len){
|
|
80
92
|
final StringBuilder sb = new StringBuilder(len);
|
|
81
93
|
for(int i = 0; i < len; i++)
|
|
82
94
|
sb.append(AB.charAt(rnd.nextInt(AB.length())));
|
|
83
95
|
return sb.toString();
|
|
84
96
|
}
|
|
85
|
-
|
|
86
|
-
public CapacitorUpdater(final Context context) throws PackageManager.NameNotFoundException {
|
|
87
|
-
this(context, new CapacitorUpdaterEvents() {});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public CapacitorUpdater (final Context context, final CapacitorUpdaterEvents events) throws PackageManager.NameNotFoundException {
|
|
91
|
-
this.context = context;
|
|
92
|
-
this.events = events;
|
|
93
|
-
|
|
94
|
-
this.prefs = this.context.getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
|
|
95
|
-
this.editor = this.prefs.edit();
|
|
96
|
-
this.versionOs = Build.VERSION.RELEASE;
|
|
97
|
-
this.deviceID = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
|
|
98
|
-
|
|
99
|
-
final PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
|
100
|
-
this.versionBuild = pInfo.versionName;
|
|
101
|
-
this.versionCode = Integer.toString(pInfo.versionCode);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private Boolean unzip(final String source, final String dest) {
|
|
105
|
-
final File zipFile = new File(this.context.getFilesDir() + "/" + source);
|
|
97
|
+
private File unzip(final File zipFile, final String dest) throws IOException {
|
|
106
98
|
final File targetDirectory = new File(this.context.getFilesDir() + "/" + dest);
|
|
107
|
-
ZipInputStream zis =
|
|
108
|
-
try {
|
|
109
|
-
zis = new ZipInputStream(
|
|
110
|
-
new BufferedInputStream(new FileInputStream(zipFile)));
|
|
111
|
-
} catch (final FileNotFoundException e) {
|
|
112
|
-
e.printStackTrace();
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
99
|
+
final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile)));
|
|
115
100
|
try {
|
|
116
|
-
ZipEntry ze;
|
|
117
101
|
int count;
|
|
118
|
-
final int
|
|
119
|
-
final byte[] buffer = new byte[
|
|
120
|
-
final long
|
|
121
|
-
long
|
|
102
|
+
final int bufferSize = 8192;
|
|
103
|
+
final byte[] buffer = new byte[bufferSize];
|
|
104
|
+
final long lengthTotal = zipFile.length();
|
|
105
|
+
long lengthRead = bufferSize;
|
|
122
106
|
int percent = 0;
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
107
|
+
this.notifyDownload(75);
|
|
108
|
+
|
|
109
|
+
ZipEntry entry;
|
|
110
|
+
while ((entry = zis.getNextEntry()) != null) {
|
|
111
|
+
final File file = new File(targetDirectory, entry.getName());
|
|
126
112
|
final String canonicalPath = file.getCanonicalPath();
|
|
127
113
|
final String canonicalDir = (new File(String.valueOf(targetDirectory))).getCanonicalPath();
|
|
128
|
-
final File dir =
|
|
114
|
+
final File dir = entry.isDirectory() ? file : file.getParentFile();
|
|
115
|
+
|
|
129
116
|
if (!canonicalPath.startsWith(canonicalDir)) {
|
|
130
117
|
throw new FileNotFoundException("SecurityException, Failed to ensure directory is the start path : " +
|
|
131
118
|
canonicalDir + " of " + canonicalPath);
|
|
132
119
|
}
|
|
133
|
-
|
|
120
|
+
|
|
121
|
+
if (!dir.isDirectory() && !dir.mkdirs()) {
|
|
134
122
|
throw new FileNotFoundException("Failed to ensure directory: " +
|
|
135
123
|
dir.getAbsolutePath());
|
|
136
|
-
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (entry.isDirectory()) {
|
|
137
127
|
continue;
|
|
138
|
-
|
|
139
|
-
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try(final FileOutputStream outputStream = new FileOutputStream(file)) {
|
|
140
131
|
while ((count = zis.read(buffer)) != -1)
|
|
141
|
-
|
|
142
|
-
} finally {
|
|
143
|
-
fileOut.close();
|
|
132
|
+
outputStream.write(buffer, 0, count);
|
|
144
133
|
}
|
|
145
|
-
|
|
146
|
-
|
|
134
|
+
|
|
135
|
+
final int newPercent = (int)((lengthRead * 100) / lengthTotal);
|
|
136
|
+
if (lengthTotal > 1 && newPercent != percent) {
|
|
147
137
|
percent = newPercent;
|
|
148
|
-
this.
|
|
138
|
+
this.notifyDownload(this.calcTotalPercent(percent, 75, 90));
|
|
149
139
|
}
|
|
150
|
-
|
|
140
|
+
|
|
141
|
+
lengthRead += entry.getCompressedSize();
|
|
151
142
|
}
|
|
152
|
-
|
|
153
|
-
Log.i(this.TAG, "unzip error", e);
|
|
154
|
-
return false;
|
|
143
|
+
return targetDirectory;
|
|
155
144
|
} finally {
|
|
156
145
|
try {
|
|
157
146
|
zis.close();
|
|
158
147
|
} catch (final IOException e) {
|
|
159
|
-
e.
|
|
160
|
-
return false;
|
|
148
|
+
Log.e(this.TAG, "Failed to close zip input stream", e);
|
|
161
149
|
}
|
|
162
|
-
return true;
|
|
163
150
|
}
|
|
164
151
|
}
|
|
165
152
|
|
|
166
|
-
private
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return false;
|
|
153
|
+
private void flattenAssets(final File sourceFile, final String dest) throws IOException {
|
|
154
|
+
if (!sourceFile.exists()) {
|
|
155
|
+
throw new FileNotFoundException("Source file not found: " + sourceFile.getPath());
|
|
170
156
|
}
|
|
171
|
-
final File
|
|
172
|
-
|
|
173
|
-
final String[]
|
|
174
|
-
if (
|
|
175
|
-
|
|
157
|
+
final File destinationFile = new File(this.context.getFilesDir() + "/" + dest);
|
|
158
|
+
destinationFile.getParentFile().mkdirs();
|
|
159
|
+
final String[] entries = sourceFile.list(this.filter);
|
|
160
|
+
if (entries == null || entries.length == 0) {
|
|
161
|
+
throw new IOException("Source file was not a directory or was empty: " + sourceFile.getPath());
|
|
176
162
|
}
|
|
177
|
-
if (
|
|
178
|
-
final File
|
|
179
|
-
|
|
163
|
+
if (entries.length == 1 && !entries[0].equals("index.html")) {
|
|
164
|
+
final File child = new File(sourceFile.getPath() + "/" + entries[0]);
|
|
165
|
+
child.renameTo(destinationFile);
|
|
180
166
|
} else {
|
|
181
|
-
|
|
167
|
+
sourceFile.renameTo(destinationFile);
|
|
182
168
|
}
|
|
183
|
-
|
|
184
|
-
return true;
|
|
169
|
+
sourceFile.delete();
|
|
185
170
|
}
|
|
186
171
|
|
|
187
|
-
private
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
172
|
+
private File downloadFile(final String url, final String dest) throws IOException {
|
|
173
|
+
|
|
174
|
+
final URL u = new URL(url);
|
|
175
|
+
final URLConnection connection = u.openConnection();
|
|
176
|
+
final InputStream is = u.openStream();
|
|
177
|
+
final DataInputStream dis = new DataInputStream(is);
|
|
178
|
+
|
|
179
|
+
final File target = new File(this.context.getFilesDir() + "/" + dest);
|
|
180
|
+
target.getParentFile().mkdirs();
|
|
181
|
+
target.createNewFile();
|
|
182
|
+
final FileOutputStream fos = new FileOutputStream(target);
|
|
183
|
+
|
|
184
|
+
final long totalLength = connection.getContentLength();
|
|
185
|
+
final int bufferSize = 1024;
|
|
186
|
+
final byte[] buffer = new byte[bufferSize];
|
|
187
|
+
int length;
|
|
188
|
+
|
|
189
|
+
int bytesRead = bufferSize;
|
|
190
|
+
int percent = 0;
|
|
191
|
+
this.notifyDownload(10);
|
|
192
|
+
while ((length = dis.read(buffer))>0) {
|
|
193
|
+
fos.write(buffer, 0, length);
|
|
194
|
+
final int newPercent = (int)((bytesRead * 100) / totalLength);
|
|
195
|
+
if (totalLength > 1 && newPercent != percent) {
|
|
196
|
+
percent = newPercent;
|
|
197
|
+
this.notifyDownload(this.calcTotalPercent(percent, 10, 70));
|
|
212
198
|
}
|
|
213
|
-
|
|
214
|
-
Log.e(this.TAG, "downloadFile error", e);
|
|
215
|
-
return false;
|
|
199
|
+
bytesRead += length;
|
|
216
200
|
}
|
|
217
|
-
return
|
|
201
|
+
return target;
|
|
218
202
|
}
|
|
219
203
|
|
|
220
204
|
private void deleteDirectory(final File file) throws IOException {
|
|
@@ -231,30 +215,22 @@ public class CapacitorUpdater {
|
|
|
231
215
|
}
|
|
232
216
|
}
|
|
233
217
|
|
|
234
|
-
public String download(final String url) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
final Boolean flatt = this.flattenAssets(folderNameUnZip, folderName);
|
|
251
|
-
if(!flatt) return "";
|
|
252
|
-
this.events.notifyDownload(100);
|
|
253
|
-
return version;
|
|
254
|
-
} catch (final Exception e) {
|
|
255
|
-
Log.e(this.TAG, "updateApp error", e);
|
|
256
|
-
return "";
|
|
257
|
-
}
|
|
218
|
+
public String download(final String url) throws IOException {
|
|
219
|
+
this.notifyDownload(0);
|
|
220
|
+
final String path = this.randomString(10);
|
|
221
|
+
final File zipFile = new File(this.context.getFilesDir() + "/" + path);
|
|
222
|
+
final String folderNameUnZip = this.randomString(10);
|
|
223
|
+
final String version = this.randomString(10);
|
|
224
|
+
final String folderName = this.basePathHot + "/" + version;
|
|
225
|
+
this.notifyDownload(5);
|
|
226
|
+
final File downloaded = this.downloadFile(url, path);
|
|
227
|
+
this.notifyDownload(71);
|
|
228
|
+
final File unzipped = this.unzip(downloaded, folderNameUnZip);
|
|
229
|
+
zipFile.delete();
|
|
230
|
+
this.notifyDownload(91);
|
|
231
|
+
this.flattenAssets(unzipped, folderName);
|
|
232
|
+
this.notifyDownload(100);
|
|
233
|
+
return version;
|
|
258
234
|
}
|
|
259
235
|
|
|
260
236
|
public ArrayList<String> list() {
|
|
@@ -298,8 +274,8 @@ public class CapacitorUpdater {
|
|
|
298
274
|
}
|
|
299
275
|
|
|
300
276
|
public void getLatest(final String url, final Callback callback) {
|
|
301
|
-
final String deviceID = this.
|
|
302
|
-
final String appId = this.
|
|
277
|
+
final String deviceID = this.getDeviceID();
|
|
278
|
+
final String appId = this.getAppId();
|
|
303
279
|
final String versionBuild = this.versionBuild;
|
|
304
280
|
final String versionCode = this.versionCode;
|
|
305
281
|
final String versionOs = this.versionOs;
|
|
@@ -313,7 +289,7 @@ public class CapacitorUpdater {
|
|
|
313
289
|
final JSONObject jsonObject = new JSONObject(response);
|
|
314
290
|
callback.callback(jsonObject);
|
|
315
291
|
} catch (final JSONException e) {
|
|
316
|
-
e.
|
|
292
|
+
Log.e(CapacitorUpdater.this.TAG, "Error parsing JSON", e);
|
|
317
293
|
}
|
|
318
294
|
}
|
|
319
295
|
}, new Response.ErrorListener() {
|
|
@@ -358,21 +334,21 @@ public class CapacitorUpdater {
|
|
|
358
334
|
}
|
|
359
335
|
|
|
360
336
|
public void sendStats(final String action, final String version) {
|
|
361
|
-
if (this.
|
|
337
|
+
if (this.getStatsUrl() == "") { return; }
|
|
362
338
|
final URL url;
|
|
363
339
|
final JSONObject json = new JSONObject();
|
|
364
340
|
final String jsonString;
|
|
365
341
|
try {
|
|
366
|
-
url = new URL(this.
|
|
342
|
+
url = new URL(this.getStatsUrl());
|
|
367
343
|
json.put("platform", "android");
|
|
368
344
|
json.put("action", action);
|
|
369
345
|
json.put("version_name", version);
|
|
370
|
-
json.put("device_id", this.
|
|
346
|
+
json.put("device_id", this.getDeviceID());
|
|
371
347
|
json.put("version_build", this.versionBuild);
|
|
372
348
|
json.put("version_code", this.versionCode);
|
|
373
349
|
json.put("version_os", this.versionOs);
|
|
374
350
|
json.put("plugin_version", this.pluginVersion);
|
|
375
|
-
json.put("app_id", this.
|
|
351
|
+
json.put("app_id", this.getAppId());
|
|
376
352
|
jsonString = json.toString();
|
|
377
353
|
} catch (final Exception ex) {
|
|
378
354
|
Log.e(this.TAG, "Error get stats", ex);
|
|
@@ -409,4 +385,28 @@ public class CapacitorUpdater {
|
|
|
409
385
|
}
|
|
410
386
|
}).start();
|
|
411
387
|
}
|
|
388
|
+
|
|
389
|
+
public String getStatsUrl() {
|
|
390
|
+
return this.statsUrl;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
public void setStatsUrl(final String statsUrl) {
|
|
394
|
+
this.statsUrl = statsUrl;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
public String getAppId() {
|
|
398
|
+
return this.appId;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
public void setAppId(final String appId) {
|
|
402
|
+
this.appId = appId;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
public String getDeviceID() {
|
|
406
|
+
return this.deviceID;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
public void setDeviceID(final String deviceID) {
|
|
410
|
+
this.deviceID = deviceID;
|
|
411
|
+
}
|
|
412
412
|
}
|
|
@@ -5,13 +5,11 @@ import android.app.Application;
|
|
|
5
5
|
import android.content.SharedPreferences;
|
|
6
6
|
import android.content.pm.PackageInfo;
|
|
7
7
|
import android.content.pm.PackageManager;
|
|
8
|
-
import android.os.Build;
|
|
9
8
|
import android.os.Bundle;
|
|
10
9
|
import android.util.Log;
|
|
11
10
|
|
|
12
11
|
import androidx.annotation.NonNull;
|
|
13
12
|
import androidx.annotation.Nullable;
|
|
14
|
-
import androidx.annotation.RequiresApi;
|
|
15
13
|
|
|
16
14
|
import com.getcapacitor.CapConfig;
|
|
17
15
|
import com.getcapacitor.JSArray;
|
|
@@ -29,42 +27,43 @@ import java.util.ArrayList;
|
|
|
29
27
|
|
|
30
28
|
@CapacitorPlugin(name = "CapacitorUpdater")
|
|
31
29
|
public class CapacitorUpdaterPlugin extends Plugin implements Application.ActivityLifecycleCallbacks {
|
|
30
|
+
private static final String autoUpdateUrlDefault = "https://capgo.app/api/auto_update";
|
|
31
|
+
private static final String statsUrlDefault = "https://capgo.app/api/stats";
|
|
32
32
|
private final String TAG = "Capacitor-updater";
|
|
33
33
|
private CapacitorUpdater implementation;
|
|
34
|
+
|
|
34
35
|
private SharedPreferences prefs;
|
|
35
36
|
private SharedPreferences.Editor editor;
|
|
36
|
-
|
|
37
|
-
private static final String statsUrlDefault = "https://capgo.app/api/stats";
|
|
37
|
+
|
|
38
38
|
private String autoUpdateUrl = "";
|
|
39
39
|
private Version currentVersionNative;
|
|
40
40
|
private Boolean autoUpdate = false;
|
|
41
41
|
private Boolean resetWhenUpdate = true;
|
|
42
42
|
|
|
43
|
-
|
|
44
43
|
@Override
|
|
45
44
|
public void load() {
|
|
46
45
|
super.load();
|
|
47
46
|
this.prefs = this.getContext().getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
|
|
48
47
|
this.editor = this.prefs.edit();
|
|
49
48
|
try {
|
|
50
|
-
this.implementation = new CapacitorUpdater(this.getContext()
|
|
49
|
+
this.implementation = new CapacitorUpdater(this.getContext()) {
|
|
51
50
|
@Override
|
|
52
51
|
public void notifyDownload(final int percent) {
|
|
53
|
-
|
|
52
|
+
this.notifyDownload(percent);
|
|
54
53
|
}
|
|
55
|
-
}
|
|
54
|
+
};
|
|
56
55
|
final PackageInfo pInfo = this.getContext().getPackageManager().getPackageInfo(this.getContext().getPackageName(), 0);
|
|
57
56
|
this.currentVersionNative = new Version(pInfo.versionName);
|
|
58
57
|
} catch (final PackageManager.NameNotFoundException e) {
|
|
59
|
-
e.
|
|
58
|
+
Log.e(this.TAG, "Error instantiating implementation", e);
|
|
60
59
|
return;
|
|
61
60
|
} catch (final Exception ex) {
|
|
62
|
-
Log.e(this.TAG, "Error
|
|
61
|
+
Log.e(this.TAG, "Error getting current native app version", ex);
|
|
63
62
|
return;
|
|
64
63
|
}
|
|
65
64
|
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
66
|
-
this.implementation.
|
|
67
|
-
this.implementation.
|
|
65
|
+
this.implementation.setAppId(config.getString("appId", ""));
|
|
66
|
+
this.implementation.setStatsUrl(this.getConfig().getString("statsUrl", statsUrlDefault));
|
|
68
67
|
this.autoUpdateUrl = this.getConfig().getString("autoUpdateUrl", autoUpdateUrlDefault);
|
|
69
68
|
this.autoUpdate = this.getConfig().getBoolean("autoUpdate", false);
|
|
70
69
|
this.resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
|
|
@@ -78,16 +77,18 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
78
77
|
final ArrayList<String> res = this.implementation.list();
|
|
79
78
|
for (int i = 0; i < res.size(); i++) {
|
|
80
79
|
try {
|
|
81
|
-
|
|
80
|
+
final String version = res.get(i);
|
|
81
|
+
this.implementation.delete(version, "");
|
|
82
|
+
Log.i(this.TAG, "Deleted obsolete version: " + version);
|
|
82
83
|
} catch (final IOException e) {
|
|
83
|
-
e.
|
|
84
|
+
Log.e(CapacitorUpdaterPlugin.this.TAG, "error deleting version", e);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
this.editor.putString("LatestVersionNative", this.currentVersionNative.toString());
|
|
88
89
|
this.editor.commit();
|
|
89
90
|
} catch (final Exception ex) {
|
|
90
|
-
Log.e(
|
|
91
|
+
Log.e(this.TAG, "Cannot get the current version " + ex.getMessage());
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
if (!this.autoUpdate || this.autoUpdateUrl.equals("")) return;
|
|
@@ -105,7 +106,14 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
105
106
|
@PluginMethod
|
|
106
107
|
public void getId(final PluginCall call) {
|
|
107
108
|
final JSObject ret = new JSObject();
|
|
108
|
-
ret.put("id", this.implementation.
|
|
109
|
+
ret.put("id", this.implementation.getDeviceID());
|
|
110
|
+
call.resolve(ret);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@PluginMethod
|
|
114
|
+
public void getPluginVersion(final PluginCall call) {
|
|
115
|
+
final JSObject ret = new JSObject();
|
|
116
|
+
ret.put("version", this.implementation.pluginVersion);
|
|
109
117
|
call.resolve(ret);
|
|
110
118
|
}
|
|
111
119
|
|
|
@@ -114,14 +122,15 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
114
122
|
new Thread(new Runnable(){
|
|
115
123
|
@Override
|
|
116
124
|
public void run() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
try {
|
|
126
|
+
final String url = call.getString("url");
|
|
127
|
+
final String version = CapacitorUpdaterPlugin.this.implementation.download(url);
|
|
120
128
|
final JSObject ret = new JSObject();
|
|
121
|
-
ret.put("version",
|
|
129
|
+
ret.put("version", version);
|
|
122
130
|
call.resolve(ret);
|
|
123
|
-
}
|
|
124
|
-
|
|
131
|
+
} catch (final IOException e) {
|
|
132
|
+
Log.e(CapacitorUpdaterPlugin.this.TAG, "download failed", e);
|
|
133
|
+
call.reject("download failed", e);
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
}).start();
|
|
@@ -132,7 +141,7 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
132
141
|
this.bridge.setServerBasePath(pathHot);
|
|
133
142
|
return true;
|
|
134
143
|
}
|
|
135
|
-
|
|
144
|
+
|
|
136
145
|
@PluginMethod
|
|
137
146
|
public void reload(final PluginCall call) {
|
|
138
147
|
if (this._reload()) {
|
|
@@ -166,7 +175,7 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
166
175
|
call.reject("Delete failed, version " + version + " doesn't exist");
|
|
167
176
|
}
|
|
168
177
|
} catch(final IOException ex) {
|
|
169
|
-
Log.e(
|
|
178
|
+
Log.e(this.TAG, "An unexpected error occurred during deletion of folder. Message: " + ex.getMessage());
|
|
170
179
|
call.reject("An unexpected error occurred during deletion of folder.");
|
|
171
180
|
}
|
|
172
181
|
}
|
|
@@ -247,17 +256,8 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
247
256
|
@Override
|
|
248
257
|
public void onActivityStarted(@NonNull final Activity activity) {
|
|
249
258
|
// disableRevert disableBreaking
|
|
250
|
-
String currentVersionNative = "";
|
|
251
|
-
try {
|
|
252
|
-
final PackageInfo pInfo = this.getContext().getPackageManager().getPackageInfo(this.getContext().getPackageName(), 0);
|
|
253
|
-
currentVersionNative = pInfo.versionName;
|
|
254
|
-
} catch (final Exception ex) {
|
|
255
|
-
Log.e(this.TAG, "Error get stats", ex);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
259
|
Log.i(this.TAG, "Check for update in the server");
|
|
259
260
|
if (this.autoUpdateUrl.equals("")) return;
|
|
260
|
-
final String finalCurrentVersionNative = currentVersionNative;
|
|
261
261
|
new Thread(new Runnable(){
|
|
262
262
|
@Override
|
|
263
263
|
public void run() {
|
|
@@ -282,7 +282,8 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
282
282
|
@Override
|
|
283
283
|
public void run() {
|
|
284
284
|
try {
|
|
285
|
-
final String
|
|
285
|
+
final String url = (String) res.get("url");
|
|
286
|
+
final String dl = CapacitorUpdaterPlugin.this.implementation.download(url);
|
|
286
287
|
if (dl.equals("")) {
|
|
287
288
|
Log.i(CapacitorUpdaterPlugin.this.TAG, "Download version: " + newVersion + " failed");
|
|
288
289
|
return;
|
|
@@ -292,8 +293,8 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
292
293
|
CapacitorUpdaterPlugin.this.editor.putString("nextVersionName", (String) res.get("version"));
|
|
293
294
|
CapacitorUpdaterPlugin.this.editor.commit();
|
|
294
295
|
CapacitorUpdaterPlugin.this.notifyListeners("updateAvailable", ret);
|
|
295
|
-
} catch (final
|
|
296
|
-
e.
|
|
296
|
+
} catch (final Exception e) {
|
|
297
|
+
Log.e(CapacitorUpdaterPlugin.this.TAG, "error downloading file", e);
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
}).start();
|
|
@@ -301,7 +302,7 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
301
302
|
Log.i(CapacitorUpdaterPlugin.this.TAG, "No need to update, " + currentVersion + " is the latest");
|
|
302
303
|
}
|
|
303
304
|
} catch (final JSONException e) {
|
|
304
|
-
e.
|
|
305
|
+
Log.e(CapacitorUpdaterPlugin.this.TAG, "error parsing JSON", e);
|
|
305
306
|
}
|
|
306
307
|
});
|
|
307
308
|
}
|
|
@@ -371,20 +372,20 @@ public class CapacitorUpdaterPlugin extends Plugin implements Application.Activi
|
|
|
371
372
|
try {
|
|
372
373
|
final Boolean res = this.implementation.delete(curVersion, curVersionName);
|
|
373
374
|
if (res) {
|
|
374
|
-
Log.i(this.TAG, "
|
|
375
|
+
Log.i(this.TAG, "Deleted failing version: " + curVersionName);
|
|
375
376
|
}
|
|
376
377
|
} catch (final IOException e) {
|
|
377
|
-
e.
|
|
378
|
+
Log.e(CapacitorUpdaterPlugin.this.TAG, "error deleting version", e);
|
|
378
379
|
}
|
|
379
380
|
} else if (!pastVersion.equals("")) {
|
|
380
381
|
Log.i(this.TAG, "Validated version: " + curVersionName);
|
|
381
382
|
try {
|
|
382
383
|
final Boolean res = this.implementation.delete(pastVersion, pastVersionName);
|
|
383
384
|
if (res) {
|
|
384
|
-
Log.i(this.TAG, "
|
|
385
|
+
Log.i(this.TAG, "Deleted past version: " + pastVersionName);
|
|
385
386
|
}
|
|
386
387
|
} catch (final IOException e) {
|
|
387
|
-
e.
|
|
388
|
+
Log.e(CapacitorUpdaterPlugin.this.TAG, "error deleting version", e);
|
|
388
389
|
}
|
|
389
390
|
this.editor.putString("pastVersion", "");
|
|
390
391
|
this.editor.putString("pastVersionName", "");
|
|
@@ -36,13 +36,33 @@ extension Bundle {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
enum CustomError: Error {
|
|
40
|
+
// Throw when an unzip fail
|
|
41
|
+
case cannotUnzip
|
|
42
|
+
|
|
43
|
+
// Throw in all other cases
|
|
44
|
+
case unexpected(code: Int)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
extension CustomError: LocalizedError {
|
|
48
|
+
public var errorDescription: String? {
|
|
49
|
+
switch self {
|
|
50
|
+
case .cannotUnzip:
|
|
51
|
+
return NSLocalizedString(
|
|
52
|
+
"The file cannot be unzip",
|
|
53
|
+
comment: "Invalid zip"
|
|
54
|
+
)
|
|
55
|
+
case .unexpected(_):
|
|
56
|
+
return NSLocalizedString(
|
|
57
|
+
"An unexpected error occurred.",
|
|
58
|
+
comment: "Unexpected Error"
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
@objc public class CapacitorUpdater: NSObject {
|
|
40
65
|
|
|
41
|
-
public var statsUrl = ""
|
|
42
|
-
public var appId = ""
|
|
43
|
-
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
44
|
-
public var notifyDownload: (Int) -> Void = { _ in }
|
|
45
|
-
public var pluginVersion = "3.2.1"
|
|
46
66
|
private var versionBuild = Bundle.main.releaseVersionNumber ?? ""
|
|
47
67
|
private var versionCode = Bundle.main.buildVersionNumber ?? ""
|
|
48
68
|
private var versionOs = ProcessInfo().operatingSystemVersion.getFullVersion()
|
|
@@ -53,11 +73,17 @@ extension Bundle {
|
|
|
53
73
|
private let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
54
74
|
private let libraryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
|
|
55
75
|
|
|
56
|
-
|
|
76
|
+
public var statsUrl = ""
|
|
77
|
+
public var appId = ""
|
|
78
|
+
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
79
|
+
public var notifyDownload: (Int) -> Void = { _ in }
|
|
80
|
+
public var pluginVersion = "3.2.0"
|
|
81
|
+
|
|
82
|
+
private func calcTotalPercent(percent: Int, min: Int, max: Int) -> Int {
|
|
57
83
|
return (percent * (max - min)) / 100 + min;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
|
-
|
|
86
|
+
private func randomString(length: Int) -> String {
|
|
61
87
|
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
62
88
|
return String((0..<length).map{ _ in letters.randomElement()! })
|
|
63
89
|
}
|
|
@@ -101,17 +127,19 @@ extension Bundle {
|
|
|
101
127
|
}
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
private func saveDownloaded(sourceZip: URL, version: String, base: URL) {
|
|
130
|
+
private func saveDownloaded(sourceZip: URL, version: String, base: URL) throws {
|
|
105
131
|
prepareFolder(source: base)
|
|
106
132
|
let destHot = base.appendingPathComponent(version)
|
|
107
133
|
let destUnZip = documentsUrl.appendingPathComponent(randomString(length: 10))
|
|
108
|
-
SSZipArchive.unzipFile(atPath: sourceZip.path, toDestination: destUnZip.path)
|
|
134
|
+
if (!SSZipArchive.unzipFile(atPath: sourceZip.path, toDestination: destUnZip.path)) {
|
|
135
|
+
throw CustomError.cannotUnzip
|
|
136
|
+
}
|
|
109
137
|
if (unflatFolder(source: destUnZip, dest: destHot)) {
|
|
110
138
|
deleteFolder(source: destUnZip)
|
|
111
139
|
}
|
|
112
140
|
}
|
|
113
141
|
|
|
114
|
-
|
|
142
|
+
public func getLatest(url: URL) -> AppVersion? {
|
|
115
143
|
let semaphore = DispatchSemaphore(value: 0)
|
|
116
144
|
let latest = AppVersion()
|
|
117
145
|
let headers: HTTPHeaders = [
|
|
@@ -150,9 +178,10 @@ extension Bundle {
|
|
|
150
178
|
return latest.url != "" ? latest : nil
|
|
151
179
|
}
|
|
152
180
|
|
|
153
|
-
|
|
181
|
+
public func download(url: URL) throws -> String {
|
|
154
182
|
let semaphore = DispatchSemaphore(value: 0)
|
|
155
|
-
var version: String
|
|
183
|
+
var version: String = ""
|
|
184
|
+
var mainError: NSError? = nil
|
|
156
185
|
let destination: DownloadRequest.Destination = { _, _ in
|
|
157
186
|
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
158
187
|
let fileURL = documentsURL.appendingPathComponent(self.randomString(length: 10))
|
|
@@ -171,24 +200,32 @@ extension Bundle {
|
|
|
171
200
|
case .success:
|
|
172
201
|
self.notifyDownload(71);
|
|
173
202
|
version = self.randomString(length: 10)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
203
|
+
do {
|
|
204
|
+
try self.saveDownloaded(sourceZip: fileURL, version: version, base: self.documentsUrl.appendingPathComponent(self.basePathHot))
|
|
205
|
+
self.notifyDownload(85);
|
|
206
|
+
try self.saveDownloaded(sourceZip: fileURL, version: version, base: self.libraryUrl.appendingPathComponent(self.basePathPersist))
|
|
207
|
+
self.notifyDownload(100);
|
|
208
|
+
self.deleteFolder(source: fileURL)
|
|
209
|
+
} catch {
|
|
210
|
+
print("✨ Capacitor-updater: download unzip error", error)
|
|
211
|
+
mainError = error as NSError
|
|
212
|
+
}
|
|
179
213
|
case let .failure(error):
|
|
180
214
|
print("✨ Capacitor-updater: download error", error)
|
|
181
|
-
|
|
215
|
+
mainError = error as NSError
|
|
182
216
|
}
|
|
183
217
|
}
|
|
184
218
|
semaphore.signal()
|
|
185
219
|
}
|
|
186
220
|
self.notifyDownload(0);
|
|
187
221
|
semaphore.wait()
|
|
222
|
+
if (mainError != nil) {
|
|
223
|
+
throw mainError!
|
|
224
|
+
}
|
|
188
225
|
return version
|
|
189
226
|
}
|
|
190
227
|
|
|
191
|
-
|
|
228
|
+
public func list() -> [String] {
|
|
192
229
|
let dest = documentsUrl.appendingPathComponent(basePathHot)
|
|
193
230
|
do {
|
|
194
231
|
let files = try FileManager.default.contentsOfDirectory(atPath: dest.path)
|
|
@@ -199,7 +236,7 @@ extension Bundle {
|
|
|
199
236
|
}
|
|
200
237
|
}
|
|
201
238
|
|
|
202
|
-
|
|
239
|
+
public func delete(version: String, versionName: String) -> Bool {
|
|
203
240
|
let destHot = documentsUrl.appendingPathComponent(basePathHot).appendingPathComponent(version)
|
|
204
241
|
let destPersist = libraryUrl.appendingPathComponent(basePathPersist).appendingPathComponent(version)
|
|
205
242
|
do {
|
|
@@ -217,7 +254,7 @@ extension Bundle {
|
|
|
217
254
|
return true
|
|
218
255
|
}
|
|
219
256
|
|
|
220
|
-
|
|
257
|
+
public func set(version: String, versionName: String) -> Bool {
|
|
221
258
|
let destHot = documentsUrl.appendingPathComponent(basePathHot).appendingPathComponent(version)
|
|
222
259
|
let indexHot = destHot.appendingPathComponent("index.html")
|
|
223
260
|
let destHotPersist = libraryUrl.appendingPathComponent(basePathPersist).appendingPathComponent(version)
|
|
@@ -233,19 +270,19 @@ extension Bundle {
|
|
|
233
270
|
return false
|
|
234
271
|
}
|
|
235
272
|
|
|
236
|
-
|
|
273
|
+
public func getLastPathHot() -> String {
|
|
237
274
|
return UserDefaults.standard.string(forKey: "lastPathHot") ?? ""
|
|
238
275
|
}
|
|
239
276
|
|
|
240
|
-
|
|
277
|
+
public func getVersionName() -> String {
|
|
241
278
|
return UserDefaults.standard.string(forKey: "versionName") ?? ""
|
|
242
279
|
}
|
|
243
280
|
|
|
244
|
-
|
|
281
|
+
public func getLastPathPersist() -> String {
|
|
245
282
|
return UserDefaults.standard.string(forKey: "lastPathPersist") ?? ""
|
|
246
283
|
}
|
|
247
284
|
|
|
248
|
-
|
|
285
|
+
public func reset() {
|
|
249
286
|
let version = UserDefaults.standard.string(forKey: "versionName") ?? ""
|
|
250
287
|
sendStats(action: "reset", version: version)
|
|
251
288
|
UserDefaults.standard.set("", forKey: "lastPathHot")
|
|
@@ -254,7 +291,7 @@ extension Bundle {
|
|
|
254
291
|
UserDefaults.standard.synchronize()
|
|
255
292
|
}
|
|
256
293
|
|
|
257
|
-
|
|
294
|
+
func sendStats(action: String, version: String) {
|
|
258
295
|
if (statsUrl == "") { return }
|
|
259
296
|
let parameters: [String: String] = [
|
|
260
297
|
"platform": "ios",
|
|
@@ -15,4 +15,5 @@ CAP_PLUGIN(CapacitorUpdaterPlugin, "CapacitorUpdater",
|
|
|
15
15
|
CAP_PLUGIN_METHOD(notifyAppReady, CAPPluginReturnPromise);
|
|
16
16
|
CAP_PLUGIN_METHOD(delayUpdate, CAPPluginReturnPromise);
|
|
17
17
|
CAP_PLUGIN_METHOD(getId, CAPPluginReturnPromise);
|
|
18
|
+
CAP_PLUGIN_METHOD(getPluginVersion, CAPPluginReturnPromise);
|
|
18
19
|
)
|
|
@@ -65,16 +65,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
65
65
|
@objc func getId(_ call: CAPPluginCall) {
|
|
66
66
|
call.resolve(["id": implementation.deviceID])
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
70
|
+
call.resolve(["version": implementation.pluginVersion])
|
|
71
|
+
}
|
|
68
72
|
|
|
69
73
|
@objc func download(_ call: CAPPluginCall) {
|
|
70
74
|
let url = URL(string: call.getString("url") ?? "")
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
do {
|
|
76
|
+
let res = try implementation.download(url: url!)
|
|
73
77
|
call.resolve([
|
|
74
|
-
"version": res
|
|
78
|
+
"version": res
|
|
75
79
|
])
|
|
76
|
-
}
|
|
77
|
-
call.reject("download failed")
|
|
80
|
+
} catch {
|
|
81
|
+
call.reject("download failed", error.localizedDescription)
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -221,14 +225,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
221
225
|
print("✨ Capacitor-updater: Cannot get version \(failingVersion) \(newVersion)")
|
|
222
226
|
}
|
|
223
227
|
if (newVersion != "0.0.0" && newVersion != failingVersion) {
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
do {
|
|
229
|
+
let dl = try self.implementation.download(url: downloadUrl)
|
|
226
230
|
print("✨ Capacitor-updater: New version: \(newVersion) found. Current is \(currentVersion == "" ? "builtin" : currentVersion), next backgrounding will trigger update")
|
|
227
231
|
UserDefaults.standard.set(dl, forKey: "nextVersion")
|
|
228
232
|
UserDefaults.standard.set(newVersion.description, forKey: "nextVersionName")
|
|
229
233
|
self.notifyListeners("updateAvailable", data: ["version": newVersion])
|
|
230
|
-
}
|
|
231
|
-
print("✨ Capacitor-updater: Download version \(newVersion) fail")
|
|
234
|
+
} catch {
|
|
235
|
+
print("✨ Capacitor-updater: Download version \(newVersion) fail", error.localizedDescription)
|
|
232
236
|
}
|
|
233
237
|
} else {
|
|
234
238
|
print("✨ Capacitor-updater: No need to update, \(currentVersion) is the latest")
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
package ee.forgr.capacitor_updater;
|
|
2
|
-
|
|
3
|
-
public interface CapacitorUpdaterEvents {
|
|
4
|
-
/**
|
|
5
|
-
* Notify listeners of download progress.
|
|
6
|
-
* @param percent Current percentage as an integer (e.g.: N out of 100)
|
|
7
|
-
*/
|
|
8
|
-
default void notifyDownload(final int percent) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
}
|