@kesha-antonov/react-native-background-downloader 2.10.0 → 3.0.0-alpha.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/README.md CHANGED
@@ -12,7 +12,7 @@ On iOS, if you want to download big files no matter the state of your app, wethe
12
12
 
13
13
  This API handles your downloads separately from your app and only keeps it informed using delegates (Read: [Downloading Files in the Background](https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background)).
14
14
 
15
- On Android we are simulating this process with a wonderful library called [Fetch2](https://github.com/tonyofrancis/Fetch)
15
+ On Android we are simulating this process with [DownloadManager](https://developer.android.com/reference/android/app/DownloadManager)
16
16
 
17
17
  The real challenge of using this method is making sure the app's UI is always up-to-date with the downloads that are happening in another process because your app might startup from scratch while the downloads are still running.
18
18
 
@@ -118,9 +118,8 @@ let task = download({
118
118
 
119
119
  // PROCESS YOUR STUFF
120
120
 
121
- // FINISH DOWNLOAD JOB ON IOS
122
- if (Platform.OS === 'ios')
123
- completeHandler(jobId)
121
+ // FINISH DOWNLOAD JOB
122
+ completeHandler(jobId)
124
123
  }).error(error => {
125
124
  console.log('Download canceled due to error: ', error);
126
125
  })
@@ -205,15 +204,15 @@ Download a file to destination
205
204
 
206
205
  An object containing options properties
207
206
 
208
- | Property | Type | Required | Platforms | Info |
209
- | ------------- | ------------------------------------------------ | :------: | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
210
- | `id` | String | | All | A Unique ID to provide for this download. This ID will help to identify the download task when the app re-launches |
211
- | `url` | String | | All | URL to file you want to download |
212
- | `destination` | String | | All | Where to copy the file to once the download is done |
213
- | `metadata` | Object | | All | Data to be preserved on reboot. |
214
- | `headers` | Object | | All | Costume headers to add to the download request. These are merged with the headers given in the `setHeaders` function
215
- | `priority` | [Priority (enum)](#priority-enum---android-only) | | Android | The priority of the download. On Android, downloading is limited to 4 simultaneous instances where further downloads are queued. Priority helps in deciding which download to pick next from the queue. **Default:** Priority.MEDIUM |
216
- | `network` | [Network (enum)](#network-enum---android-only) | | Android | Give your the ability to limit the download to WIFI only. **Default:** Network.ALL |
207
+ | Property | Type | Required | Platforms | Info |
208
+ | ------------- | ------------------------------------------------ | :------: | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
209
+ | `id` | String | | All | A Unique ID to provide for this download. This ID will help to identify the download task when the app re-launches |
210
+ | `url` | String | | All | URL to file you want to download |
211
+ | `destination` | String | | All | Where to copy the file to once the download is done |
212
+ | `metadata` | Object | | All | Data to be preserved on reboot. |
213
+ | `headers` | Object | | All | Costume headers to add to the download request. These are merged with the headers given in the `setHeaders` function |
214
+ | `isAllowedOverRoaming` | Boolean | | Android | whether this download may proceed over a roaming connection. By default, roaming is allowed |
215
+ | `isAllowedOverMetered` | Boolean | | Android | Whether this download may proceed over a metered network connection. By default, metered networks are allowed |
217
216
 
218
217
  **returns**
219
218
 
@@ -245,12 +244,12 @@ A class representing a download task created by `RNBackgroundDownloader.download
245
244
  | `id` | String | The id you gave the task when calling `RNBackgroundDownloader.download` |
246
245
  | `metadata` | Object | The metadata you gave the task when calling `RNBackgroundDownloader.download` |
247
246
  | `percent` | Number | The current percent of completion of the task between 0 and 1 |
248
- | `bytesWritten` | Number | The number of bytes currently written by the task |
249
- | `totalBytes` | Number | The number bytes expected to be written by this task or more plainly, the file size being downloaded |
247
+ | `bytesDownloaded` | Number | The number of bytes currently written by the task |
248
+ | `bytesTotal` | Number | The number bytes expected to be written by this task or more plainly, the file size being downloaded |
250
249
 
251
250
  ### `completeHandler(jobId)`
252
251
 
253
- Finishes download job on iOS and informs OS that app can be closed in background if needed.
252
+ Finishes download job and informs OS that app can be closed in background if needed.
254
253
  After finishing download in background you have some time to process your JS logic and finish the job.
255
254
 
256
255
  ### `ensureDownloadsAreRunning`
@@ -305,12 +304,12 @@ Use these methods to stay updated on what's happening with the task.
305
304
 
306
305
  All callback methods return the current instance of the `DownloadTask` for chaining.
307
306
 
308
- | Function | Callback Arguments | Info |
309
- | ---------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
307
+ | Function | Callback Arguments | Info |
308
+ | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
310
309
  | `begin` | { expectedBytes, headers } | Called when the first byte is received. 💡: this is good place to check if the device has enough storage space for this download |
311
- | `progress` | percent, bytesWritten, totalBytes | Called at max every 1.5s so you can update your progress bar accordingly |
312
- | `done` | | Called when the download is done, the file is at the destination you've set |
313
- | `error` | error | Called when the download stops due to an error |
310
+ | `progress` | percent, bytesDownloaded, bytesTotal | Called at max every 1.5s so you can update your progress bar accordingly |
311
+ | `done` | | Called when the download is done, the file is at the destination you've set |
312
+ | `error` | error | Called when the download stops due to an error |
314
313
 
315
314
  ### `pause()`
316
315
  Pauses the download
@@ -321,31 +320,6 @@ Resumes a pause download
321
320
  ### `stop()`
322
321
  Stops the download for good and removes the file that was written so far
323
322
 
324
- ### `initDownloader(options)`
325
-
326
- Init android downloader with options
327
-
328
- **options**
329
-
330
- An object containing options properties
331
-
332
- | Property | Type | Required | Platforms | Info |
333
- | ------------- | ------------------------------------------------ | :------: | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
334
- | `type` | String | | Android | Downloader type: 'parallel' or 'sequential'. Default: 'sequential'. 'parallel' seems to cause lots of ANRs, so be careful |
335
-
336
- **Usage**
337
-
338
- ```javascript
339
- import { initDownloader } from '@kesha-antonov/react-native-background-downloader'
340
-
341
- ...
342
- // SOMEWHERE AT APP STARTUP
343
-
344
- useEffect(() => {
345
- initDownloader({ type: 'parallel' })
346
- }, [])
347
- ```
348
-
349
323
  ## Constants
350
324
 
351
325
  ### directories
@@ -354,20 +328,6 @@ useEffect(() => {
354
328
 
355
329
  An absolute path to the app's documents directory. It is recommended that you use this path as the target of downloaded files.
356
330
 
357
- ### Priority (enum) - Android only
358
-
359
- `Priority.HIGH`
360
-
361
- `Priority.MEDIUM` - Default ✅
362
-
363
- `Priority.LOW`
364
-
365
- ### Network (enum) - Android only
366
-
367
- `Network.WIFI_ONLY`
368
-
369
- `Network.ALL` - Default ✅
370
-
371
331
  ## Authors
372
332
 
373
333
  Developed by [Elad Gil](https://github.com/ptelad) of [Eko](http://www.helloeko.com)
@@ -1,7 +1,5 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
- def useAndroidX = (project.hasProperty('android.useAndroidX')) ? project.property('android.useAndroidX') : false
4
-
5
3
  def safeExtGet(prop, fallback) {
6
4
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
7
5
  }
@@ -26,11 +24,4 @@ android {
26
24
  dependencies {
27
25
  //noinspection GradleDynamicVersion
28
26
  implementation 'com.facebook.react:react-native:+'
29
- if (useAndroidX) {
30
- api 'com.github.tonyofrancis.Fetch:xfetch2:3.1.6'
31
- implementation 'com.github.tonyofrancis.Fetch:xfetch2okhttp:3.1.6'
32
- } else {
33
- api 'com.tonyodev.fetch2:fetch2:3.0.12'
34
- implementation 'com.tonyodev.fetch2okhttp:fetch2okhttp:3.0.12'
35
- }
36
27
  }
@@ -1,4 +1,3 @@
1
-
2
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
2
  package="com.eko">
4
3
 
@@ -0,0 +1,157 @@
1
+ package com.eko;
2
+
3
+ import android.app.DownloadManager;
4
+ import android.content.Context;
5
+ import android.database.Cursor;
6
+ import android.net.Uri;
7
+ import android.os.Environment;
8
+
9
+ import com.facebook.react.bridge.Arguments;
10
+ import com.facebook.react.bridge.ReadableMap;
11
+ import com.facebook.react.bridge.ReadableMapKeySetIterator;
12
+ import com.facebook.react.bridge.WritableMap;
13
+ import com.facebook.react.bridge.WritableNativeMap;
14
+
15
+ import java.util.HashMap;
16
+ import android.util.Log;
17
+
18
+ import static android.content.Context.DOWNLOAD_SERVICE;
19
+
20
+ public class Downloader {
21
+
22
+ public DownloadManager downloadManager;
23
+ private Context context;
24
+
25
+ public Downloader(Context ctx) {
26
+ context = ctx;
27
+ downloadManager = (DownloadManager) ctx.getSystemService(DOWNLOAD_SERVICE);
28
+ }
29
+
30
+ public long queueDownload(DownloadManager.Request request) {
31
+ return downloadManager.enqueue(request);
32
+ }
33
+
34
+ public WritableMap checkDownloadStatus(long downloadId) {
35
+ DownloadManager.Query downloadQuery = new DownloadManager.Query();
36
+ downloadQuery.setFilterById(downloadId);
37
+ Cursor cursor = downloadManager.query(downloadQuery);
38
+
39
+ WritableMap result = Arguments.createMap();
40
+
41
+ if (cursor.moveToFirst()) {
42
+ result = getDownloadStatus(cursor);
43
+ } else {
44
+ result.putString("downloadId", String.valueOf(downloadId));
45
+ result.putInt("status", DownloadManager.STATUS_FAILED);
46
+ result.putInt("reason", -1);
47
+ result.putString("reasonText", "COULD_NOT_FIND");
48
+ }
49
+
50
+ return result;
51
+ }
52
+
53
+ public int cancelDownload(long downloadId) {
54
+ Log.d("RNBackgroundDownloader", "Downloader cancelDownload " + downloadId);
55
+ return downloadManager.remove(downloadId);
56
+ }
57
+
58
+ // WAITING FOR THE FIX TO BE MERGED
59
+ // https://android-review.googlesource.com/c/platform/packages/providers/DownloadProvider/+/2089866
60
+ public void pauseDownload(long downloadId) {
61
+ // ContentValues values = new ContentValues();
62
+
63
+ // values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_PAUSED);
64
+ // values.put(Downloads.Impl.COLUMN_STATUS,
65
+ // Downloads.Impl.STATUS_PAUSED_BY_APP);
66
+
67
+ // downloadManager.mResolver.update(ContentUris.withAppendedId(mBaseUri,
68
+ // ids[0]), values, null, null)
69
+ }
70
+
71
+ public void resumeDownload(long downloadId) {
72
+ // ContentValues values = new ContentValues();
73
+
74
+ // values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
75
+ // values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
76
+ }
77
+
78
+ public WritableMap getDownloadStatus(Cursor cursor) {
79
+ String downloadId = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
80
+ String localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
81
+ if (localUri != null) {
82
+ localUri = localUri.replace("file://", "");
83
+ }
84
+ String bytesDownloadedSoFar = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
85
+ String totalSizeBytes = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
86
+
87
+ int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
88
+ int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
89
+
90
+ String reasonText = "";
91
+
92
+ switch (status) {
93
+ case DownloadManager.STATUS_FAILED:
94
+ switch (reason) {
95
+ case DownloadManager.ERROR_CANNOT_RESUME:
96
+ reasonText = "ERROR_CANNOT_RESUME";
97
+ break;
98
+ case DownloadManager.ERROR_DEVICE_NOT_FOUND:
99
+ reasonText = "ERROR_DEVICE_NOT_FOUND";
100
+ break;
101
+ case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
102
+ reasonText = "ERROR_FILE_ALREADY_EXISTS";
103
+ break;
104
+ case DownloadManager.ERROR_FILE_ERROR:
105
+ reasonText = "ERROR_FILE_ERROR";
106
+ break;
107
+ case DownloadManager.ERROR_HTTP_DATA_ERROR:
108
+ reasonText = "ERROR_HTTP_DATA_ERROR";
109
+ break;
110
+ case DownloadManager.ERROR_INSUFFICIENT_SPACE:
111
+ reasonText = "ERROR_INSUFFICIENT_SPACE";
112
+ break;
113
+ case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
114
+ reasonText = "ERROR_TOO_MANY_REDIRECTS";
115
+ break;
116
+ case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
117
+ reasonText = "ERROR_UNHANDLED_HTTP_CODE";
118
+ break;
119
+ default:
120
+ reasonText = "ERROR_UNKNOWN";
121
+ break;
122
+ }
123
+ break;
124
+ case DownloadManager.STATUS_PAUSED:
125
+ switch (reason) {
126
+ case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
127
+ reasonText = "PAUSED_QUEUED_FOR_WIFI";
128
+ break;
129
+ case DownloadManager.PAUSED_UNKNOWN:
130
+ reasonText = "PAUSED_UNKNOWN";
131
+ break;
132
+ case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
133
+ reasonText = "PAUSED_WAITING_FOR_NETWORK";
134
+ break;
135
+ case DownloadManager.PAUSED_WAITING_TO_RETRY:
136
+ reasonText = "PAUSED_WAITING_TO_RETRY";
137
+ break;
138
+ default:
139
+ reasonText = "UNKNOWN";
140
+ }
141
+ break;
142
+ }
143
+
144
+ WritableMap result = Arguments.createMap();
145
+ result.putString("downloadId", downloadId);
146
+
147
+ result.putInt("status", status);
148
+ result.putInt("reason", reason);
149
+ result.putString("reasonText", reasonText);
150
+
151
+ result.putInt("bytesDownloaded", Integer.parseInt(bytesDownloadedSoFar));
152
+ result.putInt("bytesTotal", Integer.parseInt(totalSizeBytes));
153
+ result.putString("localUri", localUri);
154
+
155
+ return result;
156
+ }
157
+ }
@@ -0,0 +1,49 @@
1
+ package com.eko;
2
+
3
+ import java.net.URL;
4
+ import java.net.URLConnection;
5
+ import java.util.List;
6
+ import java.util.Map;
7
+ import java.util.Set;
8
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
9
+ import com.eko.RNBGDTaskConfig;
10
+ import com.facebook.react.bridge.WritableMap;
11
+ import com.facebook.react.bridge.Arguments;
12
+ import com.facebook.react.bridge.WritableArray;
13
+
14
+ public class OnBegin extends Thread {
15
+ private RNBGDTaskConfig config;
16
+ private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
17
+
18
+ public OnBegin(RNBGDTaskConfig config,
19
+ DeviceEventManagerModule.RCTDeviceEventEmitter ee) {
20
+ this.config = config;
21
+ this.ee = ee;
22
+ }
23
+
24
+ @Override
25
+ public void run() {
26
+ try {
27
+ WritableMap headersMap = Arguments.createMap();
28
+
29
+ URL urlC = new URL(config.url);
30
+ URLConnection con = urlC.openConnection();
31
+ Map<String, List<String>> headers = con.getHeaderFields();
32
+ Set<String> keys = headers.keySet();
33
+ for (String key : keys) {
34
+ String val = con.getHeaderField(key);
35
+ headersMap.putString(key, val);
36
+ }
37
+ con.getInputStream().close();
38
+
39
+ WritableMap params = Arguments.createMap();
40
+ params.putString("id", config.id);
41
+ params.putMap("headers", headersMap);
42
+ params.putInt("expectedBytes", Integer.valueOf(headersMap.getString("Content-Length")));
43
+
44
+ ee.emit("downloadBegin", params);
45
+ } catch (Exception e) {
46
+ e.printStackTrace();
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,132 @@
1
+ package com.eko;
2
+
3
+ import java.util.HashMap;
4
+ import android.app.DownloadManager;
5
+ import android.util.Log;
6
+
7
+ import com.eko.Downloader;
8
+ import com.eko.RNBGDTaskConfig;
9
+ import com.facebook.react.bridge.WritableMap;
10
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
11
+ import android.database.Cursor;
12
+ import com.facebook.react.bridge.Arguments;
13
+ import com.facebook.react.bridge.WritableArray;
14
+
15
+ public class OnProgress extends Thread {
16
+ private final long downloadId;
17
+ private final DownloadManager.Query query;
18
+ private final Downloader downloader;
19
+ private Cursor cursor;
20
+ private int lastBytesDownloaded;
21
+ private int bytesTotal;
22
+ private int progressInterval = 300;
23
+
24
+ private RNBGDTaskConfig config;
25
+ private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
26
+
27
+ public OnProgress(RNBGDTaskConfig config, long downloadId,
28
+ DeviceEventManagerModule.RCTDeviceEventEmitter ee, Downloader downloader, int progressInterval) {
29
+ this.config = config;
30
+
31
+ this.downloadId = downloadId;
32
+ this.query = new DownloadManager.Query();
33
+ query.setFilterById(this.downloadId);
34
+
35
+ this.ee = ee;
36
+ this.downloader = downloader;
37
+ if (progressInterval > 0) {
38
+ this.progressInterval = progressInterval;
39
+ }
40
+ }
41
+
42
+ private void handleInterrupt() {
43
+ try {
44
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress handleInterrupt. downloadId " + downloadId);
45
+ if (cursor != null) {
46
+ cursor.close();
47
+ }
48
+ } catch (Exception e) {
49
+ return;
50
+ }
51
+ this.interrupt();
52
+ }
53
+
54
+ @Override
55
+ public void run() {
56
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-1. downloadId " + downloadId);
57
+ while (downloadId > 0) {
58
+ try {
59
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2. downloadId " + downloadId);
60
+
61
+ cursor = downloader.downloadManager.query(query);
62
+
63
+ if (!cursor.moveToFirst()) {
64
+ this.handleInterrupt();
65
+ }
66
+
67
+ int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
68
+ if (status == DownloadManager.STATUS_FAILED || status == DownloadManager.STATUS_SUCCESSFUL) {
69
+ this.handleInterrupt();
70
+ }
71
+
72
+ if (status == DownloadManager.STATUS_PAUSED) {
73
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.1. downloadId " + downloadId);
74
+ Thread.sleep(5000);
75
+ } else if (status == DownloadManager.STATUS_PENDING) {
76
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.2. downloadId " + downloadId);
77
+ Thread.sleep(1000);
78
+ } else {
79
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.3. downloadId " + downloadId);
80
+ Thread.sleep(progressInterval);
81
+ }
82
+
83
+ // get total bytes of the file
84
+ if (bytesTotal <= 0) {
85
+ bytesTotal = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
86
+ }
87
+
88
+ int bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
89
+
90
+ if (bytesTotal > 0 && bytesDownloaded == bytesTotal) {
91
+ this.handleInterrupt();
92
+ } else {
93
+ lastBytesDownloaded = bytesDownloaded;
94
+ }
95
+
96
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-3. downloadId " + downloadId + " bytesDownloaded "
97
+ + bytesDownloaded + " bytesTotal " + bytesTotal);
98
+
99
+ if (lastBytesDownloaded > 0 && bytesTotal > 0) {
100
+ WritableMap params = Arguments.createMap();
101
+ params.putString("id", config.id);
102
+ params.putInt("bytesDownloaded", (int) lastBytesDownloaded);
103
+ params.putInt("bytesTotal", (int) bytesTotal);
104
+ params.putDouble("percent", ((double) lastBytesDownloaded / bytesTotal));
105
+
106
+ HashMap<String, WritableMap> progressReports = new HashMap<>();
107
+ progressReports.put(config.id, params);
108
+
109
+ WritableArray reportsArray = Arguments.createArray();
110
+ for (WritableMap report : progressReports.values()) {
111
+ reportsArray.pushMap(report);
112
+ }
113
+
114
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-4. downloadId " + downloadId);
115
+ ee.emit("downloadProgress", reportsArray);
116
+ }
117
+ } catch (Exception e) {
118
+ return;
119
+ }
120
+
121
+ try {
122
+ if (cursor != null) {
123
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-5. downloadId " + downloadId);
124
+ cursor.close();
125
+ Log.d("RNBackgroundDownloader", "RNBD: OnProgress-6. downloadId " + downloadId);
126
+ }
127
+ } catch (Exception e) {
128
+ return;
129
+ }
130
+ }
131
+ }
132
+ }
@@ -4,12 +4,14 @@ import java.io.Serializable;
4
4
 
5
5
  public class RNBGDTaskConfig implements Serializable {
6
6
  public String id;
7
+ public String url;
7
8
  public String destination;
8
9
  public String metadata = "{}";
9
10
  public boolean reportedBegin;
10
11
 
11
- public RNBGDTaskConfig(String id, String destination, String metadata) {
12
+ public RNBGDTaskConfig(String id, String url, String destination, String metadata) {
12
13
  this.id = id;
14
+ this.url = url;
13
15
  this.destination = destination;
14
16
  this.metadata = metadata;
15
17
  this.reportedBegin = false;