mirror of
https://github.com/zoriya/react-native-background-downloader.git
synced 2025-12-06 06:56:10 +00:00
rewritten android side from Fetch to DownloadManager. Everything works except manual pause/resume
This commit is contained in:
62
README.md
62
README.md
@@ -12,7 +12,7 @@ On iOS, if you want to download big files no matter the state of your app, wethe
|
||||
|
||||
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)).
|
||||
|
||||
On Android we are simulating this process with a wonderful library called [Fetch2](https://github.com/tonyofrancis/Fetch)
|
||||
On Android we are simulating this process with [DownloadManager](https://developer.android.com/reference/android/app/DownloadManager)
|
||||
|
||||
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.
|
||||
|
||||
@@ -118,8 +118,7 @@ let task = download({
|
||||
|
||||
// PROCESS YOUR STUFF
|
||||
|
||||
// FINISH DOWNLOAD JOB ON IOS
|
||||
if (Platform.OS === 'ios')
|
||||
// FINISH DOWNLOAD JOB
|
||||
completeHandler(jobId)
|
||||
}).error(error => {
|
||||
console.log('Download canceled due to error: ', error);
|
||||
@@ -206,14 +205,14 @@ Download a file to destination
|
||||
An object containing options properties
|
||||
|
||||
| Property | Type | Required | Platforms | Info |
|
||||
| ------------- | ------------------------------------------------ | :------: | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ------------- | ------------------------------------------------ | :------: | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `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 |
|
||||
| `url` | String | ✅ | All | URL to file you want to download |
|
||||
| `destination` | String | ✅ | All | Where to copy the file to once the download is done |
|
||||
| `metadata` | Object | | All | Data to be preserved on reboot. |
|
||||
| `headers` | Object | | All | Costume headers to add to the download request. These are merged with the headers given in the `setHeaders` function
|
||||
| `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 |
|
||||
| `network` | [Network (enum)](#network-enum---android-only) | | Android | Give your the ability to limit the download to WIFI only. **Default:** Network.ALL |
|
||||
| `headers` | Object | | All | Costume headers to add to the download request. These are merged with the headers given in the `setHeaders` function |
|
||||
| `isAllowedOverRoaming` | Boolean | | Android | whether this download may proceed over a roaming connection. By default, roaming is allowed |
|
||||
| `isAllowedOverMetered` | Boolean | | Android | Whether this download may proceed over a metered network connection. By default, metered networks are allowed |
|
||||
|
||||
**returns**
|
||||
|
||||
@@ -245,12 +244,12 @@ A class representing a download task created by `RNBackgroundDownloader.download
|
||||
| `id` | String | The id you gave the task when calling `RNBackgroundDownloader.download` |
|
||||
| `metadata` | Object | The metadata you gave the task when calling `RNBackgroundDownloader.download` |
|
||||
| `percent` | Number | The current percent of completion of the task between 0 and 1 |
|
||||
| `bytesWritten` | Number | The number of bytes currently written by the task |
|
||||
| `totalBytes` | Number | The number bytes expected to be written by this task or more plainly, the file size being downloaded |
|
||||
| `bytesDownloaded` | Number | The number of bytes currently written by the task |
|
||||
| `bytesTotal` | Number | The number bytes expected to be written by this task or more plainly, the file size being downloaded |
|
||||
|
||||
### `completeHandler(jobId)`
|
||||
|
||||
Finishes download job on iOS and informs OS that app can be closed in background if needed.
|
||||
Finishes download job and informs OS that app can be closed in background if needed.
|
||||
After finishing download in background you have some time to process your JS logic and finish the job.
|
||||
|
||||
### `ensureDownloadsAreRunning`
|
||||
@@ -306,9 +305,9 @@ Use these methods to stay updated on what's happening with the task.
|
||||
All callback methods return the current instance of the `DownloadTask` for chaining.
|
||||
|
||||
| Function | Callback Arguments | Info |
|
||||
| ---------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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 |
|
||||
| `progress` | percent, bytesWritten, totalBytes | Called at max every 1.5s so you can update your progress bar accordingly |
|
||||
| `progress` | percent, bytesDownloaded, bytesTotal | Called at max every 1.5s so you can update your progress bar accordingly |
|
||||
| `done` | | Called when the download is done, the file is at the destination you've set |
|
||||
| `error` | error | Called when the download stops due to an error |
|
||||
|
||||
@@ -321,31 +320,6 @@ Resumes a pause download
|
||||
### `stop()`
|
||||
Stops the download for good and removes the file that was written so far
|
||||
|
||||
### `initDownloader(options)`
|
||||
|
||||
Init android downloader with options
|
||||
|
||||
**options**
|
||||
|
||||
An object containing options properties
|
||||
|
||||
| Property | Type | Required | Platforms | Info |
|
||||
| ------------- | ------------------------------------------------ | :------: | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `type` | String | | Android | Downloader type: 'parallel' or 'sequential'. Default: 'sequential'. 'parallel' seems to cause lots of ANRs, so be careful |
|
||||
|
||||
**Usage**
|
||||
|
||||
```javascript
|
||||
import { initDownloader } from '@kesha-antonov/react-native-background-downloader'
|
||||
|
||||
...
|
||||
// SOMEWHERE AT APP STARTUP
|
||||
|
||||
useEffect(() => {
|
||||
initDownloader({ type: 'parallel' })
|
||||
}, [])
|
||||
```
|
||||
|
||||
## Constants
|
||||
|
||||
### directories
|
||||
@@ -354,20 +328,6 @@ useEffect(() => {
|
||||
|
||||
An absolute path to the app's documents directory. It is recommended that you use this path as the target of downloaded files.
|
||||
|
||||
### Priority (enum) - Android only
|
||||
|
||||
`Priority.HIGH`
|
||||
|
||||
`Priority.MEDIUM` - Default ✅
|
||||
|
||||
`Priority.LOW`
|
||||
|
||||
### Network (enum) - Android only
|
||||
|
||||
`Network.WIFI_ONLY`
|
||||
|
||||
`Network.ALL` - Default ✅
|
||||
|
||||
## Authors
|
||||
|
||||
Developed by [Elad Gil](https://github.com/ptelad) of [Eko](http://www.helloeko.com)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
def useAndroidX = (project.hasProperty('android.useAndroidX')) ? project.property('android.useAndroidX') : false
|
||||
|
||||
def safeExtGet(prop, fallback) {
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
@@ -26,11 +24,4 @@ android {
|
||||
dependencies {
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
if (useAndroidX) {
|
||||
api 'com.github.tonyofrancis.Fetch:xfetch2:3.1.6'
|
||||
implementation 'com.github.tonyofrancis.Fetch:xfetch2okhttp:3.1.6'
|
||||
} else {
|
||||
api 'com.tonyodev.fetch2:fetch2:3.0.12'
|
||||
implementation 'com.tonyodev.fetch2okhttp:fetch2okhttp:3.0.12'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.eko">
|
||||
|
||||
|
||||
157
android/src/main/java/com/eko/Downloader.java
Normal file
157
android/src/main/java/com/eko/Downloader.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package com.eko;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import android.util.Log;
|
||||
|
||||
import static android.content.Context.DOWNLOAD_SERVICE;
|
||||
|
||||
public class Downloader {
|
||||
|
||||
public DownloadManager downloadManager;
|
||||
private Context context;
|
||||
|
||||
public Downloader(Context ctx) {
|
||||
context = ctx;
|
||||
downloadManager = (DownloadManager) ctx.getSystemService(DOWNLOAD_SERVICE);
|
||||
}
|
||||
|
||||
public long queueDownload(DownloadManager.Request request) {
|
||||
return downloadManager.enqueue(request);
|
||||
}
|
||||
|
||||
public WritableMap checkDownloadStatus(long downloadId) {
|
||||
DownloadManager.Query downloadQuery = new DownloadManager.Query();
|
||||
downloadQuery.setFilterById(downloadId);
|
||||
Cursor cursor = downloadManager.query(downloadQuery);
|
||||
|
||||
WritableMap result = Arguments.createMap();
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
result = getDownloadStatus(cursor);
|
||||
} else {
|
||||
result.putString("downloadId", String.valueOf(downloadId));
|
||||
result.putInt("status", DownloadManager.STATUS_FAILED);
|
||||
result.putInt("reason", -1);
|
||||
result.putString("reasonText", "COULD_NOT_FIND");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int cancelDownload(long downloadId) {
|
||||
Log.d("RNBackgroundDownloader", "Downloader cancelDownload " + downloadId);
|
||||
return downloadManager.remove(downloadId);
|
||||
}
|
||||
|
||||
// WAITING FOR THE FIX TO BE MERGED
|
||||
// https://android-review.googlesource.com/c/platform/packages/providers/DownloadProvider/+/2089866
|
||||
public void pauseDownload(long downloadId) {
|
||||
// ContentValues values = new ContentValues();
|
||||
|
||||
// values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_PAUSED);
|
||||
// values.put(Downloads.Impl.COLUMN_STATUS,
|
||||
// Downloads.Impl.STATUS_PAUSED_BY_APP);
|
||||
|
||||
// downloadManager.mResolver.update(ContentUris.withAppendedId(mBaseUri,
|
||||
// ids[0]), values, null, null)
|
||||
}
|
||||
|
||||
public void resumeDownload(long downloadId) {
|
||||
// ContentValues values = new ContentValues();
|
||||
|
||||
// values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
|
||||
// values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
|
||||
}
|
||||
|
||||
public WritableMap getDownloadStatus(Cursor cursor) {
|
||||
String downloadId = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
|
||||
String localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
|
||||
if (localUri != null) {
|
||||
localUri = localUri.replace("file://", "");
|
||||
}
|
||||
String bytesDownloadedSoFar = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
||||
String totalSizeBytes = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
||||
|
||||
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
|
||||
int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
|
||||
|
||||
String reasonText = "";
|
||||
|
||||
switch (status) {
|
||||
case DownloadManager.STATUS_FAILED:
|
||||
switch (reason) {
|
||||
case DownloadManager.ERROR_CANNOT_RESUME:
|
||||
reasonText = "ERROR_CANNOT_RESUME";
|
||||
break;
|
||||
case DownloadManager.ERROR_DEVICE_NOT_FOUND:
|
||||
reasonText = "ERROR_DEVICE_NOT_FOUND";
|
||||
break;
|
||||
case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
|
||||
reasonText = "ERROR_FILE_ALREADY_EXISTS";
|
||||
break;
|
||||
case DownloadManager.ERROR_FILE_ERROR:
|
||||
reasonText = "ERROR_FILE_ERROR";
|
||||
break;
|
||||
case DownloadManager.ERROR_HTTP_DATA_ERROR:
|
||||
reasonText = "ERROR_HTTP_DATA_ERROR";
|
||||
break;
|
||||
case DownloadManager.ERROR_INSUFFICIENT_SPACE:
|
||||
reasonText = "ERROR_INSUFFICIENT_SPACE";
|
||||
break;
|
||||
case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
|
||||
reasonText = "ERROR_TOO_MANY_REDIRECTS";
|
||||
break;
|
||||
case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
|
||||
reasonText = "ERROR_UNHANDLED_HTTP_CODE";
|
||||
break;
|
||||
default:
|
||||
reasonText = "ERROR_UNKNOWN";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DownloadManager.STATUS_PAUSED:
|
||||
switch (reason) {
|
||||
case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
|
||||
reasonText = "PAUSED_QUEUED_FOR_WIFI";
|
||||
break;
|
||||
case DownloadManager.PAUSED_UNKNOWN:
|
||||
reasonText = "PAUSED_UNKNOWN";
|
||||
break;
|
||||
case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
|
||||
reasonText = "PAUSED_WAITING_FOR_NETWORK";
|
||||
break;
|
||||
case DownloadManager.PAUSED_WAITING_TO_RETRY:
|
||||
reasonText = "PAUSED_WAITING_TO_RETRY";
|
||||
break;
|
||||
default:
|
||||
reasonText = "UNKNOWN";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
WritableMap result = Arguments.createMap();
|
||||
result.putString("downloadId", downloadId);
|
||||
|
||||
result.putInt("status", status);
|
||||
result.putInt("reason", reason);
|
||||
result.putString("reasonText", reasonText);
|
||||
|
||||
result.putInt("bytesDownloaded", Integer.parseInt(bytesDownloadedSoFar));
|
||||
result.putInt("bytesTotal", Integer.parseInt(totalSizeBytes));
|
||||
result.putString("localUri", localUri);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
49
android/src/main/java/com/eko/OnBegin.java
Normal file
49
android/src/main/java/com/eko/OnBegin.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.eko;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.eko.RNBGDTaskConfig;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
|
||||
public class OnBegin extends Thread {
|
||||
private RNBGDTaskConfig config;
|
||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||
|
||||
public OnBegin(RNBGDTaskConfig config,
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter ee) {
|
||||
this.config = config;
|
||||
this.ee = ee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
WritableMap headersMap = Arguments.createMap();
|
||||
|
||||
URL urlC = new URL(config.url);
|
||||
URLConnection con = urlC.openConnection();
|
||||
Map<String, List<String>> headers = con.getHeaderFields();
|
||||
Set<String> keys = headers.keySet();
|
||||
for (String key : keys) {
|
||||
String val = con.getHeaderField(key);
|
||||
headersMap.putString(key, val);
|
||||
}
|
||||
con.getInputStream().close();
|
||||
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putMap("headers", headersMap);
|
||||
params.putInt("expectedBytes", Integer.valueOf(headersMap.getString("Content-Length")));
|
||||
|
||||
ee.emit("downloadBegin", params);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
android/src/main/java/com/eko/OnProgress.java
Normal file
132
android/src/main/java/com/eko/OnProgress.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package com.eko;
|
||||
|
||||
import java.util.HashMap;
|
||||
import android.app.DownloadManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.eko.Downloader;
|
||||
import com.eko.RNBGDTaskConfig;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import android.database.Cursor;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
|
||||
public class OnProgress extends Thread {
|
||||
private final long downloadId;
|
||||
private final DownloadManager.Query query;
|
||||
private final Downloader downloader;
|
||||
private Cursor cursor;
|
||||
private int lastBytesDownloaded;
|
||||
private int bytesTotal;
|
||||
private int progressInterval = 300;
|
||||
|
||||
private RNBGDTaskConfig config;
|
||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||
|
||||
public OnProgress(RNBGDTaskConfig config, long downloadId,
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter ee, Downloader downloader, int progressInterval) {
|
||||
this.config = config;
|
||||
|
||||
this.downloadId = downloadId;
|
||||
this.query = new DownloadManager.Query();
|
||||
query.setFilterById(this.downloadId);
|
||||
|
||||
this.ee = ee;
|
||||
this.downloader = downloader;
|
||||
if (progressInterval > 0) {
|
||||
this.progressInterval = progressInterval;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInterrupt() {
|
||||
try {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress handleInterrupt. downloadId " + downloadId);
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-1. downloadId " + downloadId);
|
||||
while (downloadId > 0) {
|
||||
try {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2. downloadId " + downloadId);
|
||||
|
||||
cursor = downloader.downloadManager.query(query);
|
||||
|
||||
if (!cursor.moveToFirst()) {
|
||||
this.handleInterrupt();
|
||||
}
|
||||
|
||||
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
|
||||
if (status == DownloadManager.STATUS_FAILED || status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
this.handleInterrupt();
|
||||
}
|
||||
|
||||
if (status == DownloadManager.STATUS_PAUSED) {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.1. downloadId " + downloadId);
|
||||
Thread.sleep(5000);
|
||||
} else if (status == DownloadManager.STATUS_PENDING) {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.2. downloadId " + downloadId);
|
||||
Thread.sleep(1000);
|
||||
} else {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.3. downloadId " + downloadId);
|
||||
Thread.sleep(progressInterval);
|
||||
}
|
||||
|
||||
// get total bytes of the file
|
||||
if (bytesTotal <= 0) {
|
||||
bytesTotal = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
||||
}
|
||||
|
||||
int bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
||||
|
||||
if (bytesTotal > 0 && bytesDownloaded == bytesTotal) {
|
||||
this.handleInterrupt();
|
||||
} else {
|
||||
lastBytesDownloaded = bytesDownloaded;
|
||||
}
|
||||
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-3. downloadId " + downloadId + " bytesDownloaded "
|
||||
+ bytesDownloaded + " bytesTotal " + bytesTotal);
|
||||
|
||||
if (lastBytesDownloaded > 0 && bytesTotal > 0) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putInt("bytesDownloaded", (int) lastBytesDownloaded);
|
||||
params.putInt("bytesTotal", (int) bytesTotal);
|
||||
params.putDouble("percent", ((double) lastBytesDownloaded / bytesTotal));
|
||||
|
||||
HashMap<String, WritableMap> progressReports = new HashMap<>();
|
||||
progressReports.put(config.id, params);
|
||||
|
||||
WritableArray reportsArray = Arguments.createArray();
|
||||
for (WritableMap report : progressReports.values()) {
|
||||
reportsArray.pushMap(report);
|
||||
}
|
||||
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-4. downloadId " + downloadId);
|
||||
ee.emit("downloadProgress", reportsArray);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (cursor != null) {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-5. downloadId " + downloadId);
|
||||
cursor.close();
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-6. downloadId " + downloadId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import java.io.Serializable;
|
||||
|
||||
public class RNBGDTaskConfig implements Serializable {
|
||||
public String id;
|
||||
public String url;
|
||||
public String destination;
|
||||
public String metadata = "{}";
|
||||
public boolean reportedBegin;
|
||||
|
||||
public RNBGDTaskConfig(String id, String destination, String metadata) {
|
||||
public RNBGDTaskConfig(String id, String url, String destination, String metadata) {
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
this.destination = destination;
|
||||
this.metadata = metadata;
|
||||
this.reportedBegin = false;
|
||||
|
||||
@@ -8,27 +8,14 @@ import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.tonyodev.fetch2.Download;
|
||||
import com.tonyodev.fetch2core.Downloader;
|
||||
import com.tonyodev.fetch2okhttp.OkHttpDownloader;
|
||||
import com.tonyodev.fetch2.Error;
|
||||
import com.tonyodev.fetch2.Fetch;
|
||||
import com.tonyodev.fetch2.FetchConfiguration;
|
||||
import com.tonyodev.fetch2.FetchListener;
|
||||
import com.tonyodev.fetch2.NetworkType;
|
||||
import com.tonyodev.fetch2.Priority;
|
||||
import com.tonyodev.fetch2.Request;
|
||||
import com.tonyodev.fetch2.Status;
|
||||
import com.tonyodev.fetch2core.DownloadBlock;
|
||||
import com.tonyodev.fetch2core.Func;
|
||||
import com.tonyodev.fetch2.HttpUrlConnectionDownloader;
|
||||
import com.tonyodev.fetch2core.Downloader;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.app.DownloadManager.Request;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -42,16 +29,33 @@ import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import java.util.Set;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule implements FetchListener {
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import android.net.Uri;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
|
||||
public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final int TASK_RUNNING = 0;
|
||||
private static final int TASK_SUSPENDED = 1;
|
||||
@@ -64,37 +68,117 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
private static final int ERR_FILE_NOT_FOUND = 3;
|
||||
private static final int ERR_OTHERS = 100;
|
||||
|
||||
private static Map<Status, Integer> stateMap = new HashMap<Status, Integer>() {{
|
||||
put(Status.DOWNLOADING, TASK_RUNNING);
|
||||
put(Status.COMPLETED, TASK_COMPLETED);
|
||||
put(Status.PAUSED, TASK_SUSPENDED);
|
||||
put(Status.QUEUED, TASK_RUNNING);
|
||||
put(Status.CANCELLED, TASK_CANCELING);
|
||||
put(Status.FAILED, TASK_CANCELING);
|
||||
put(Status.REMOVED, TASK_CANCELING);
|
||||
put(Status.DELETED, TASK_CANCELING);
|
||||
put(Status.NONE, TASK_CANCELING);
|
||||
}};
|
||||
private static Map<Integer, Integer> stateMap = new HashMap<Integer, Integer>() {
|
||||
{
|
||||
put(DownloadManager.STATUS_FAILED, TASK_CANCELING);
|
||||
put(DownloadManager.STATUS_PAUSED, TASK_SUSPENDED);
|
||||
put(DownloadManager.STATUS_PENDING, TASK_RUNNING);
|
||||
put(DownloadManager.STATUS_RUNNING, TASK_RUNNING);
|
||||
put(DownloadManager.STATUS_SUCCESSFUL, TASK_COMPLETED);
|
||||
}
|
||||
};
|
||||
|
||||
private Fetch fetch;
|
||||
private Map<String, Integer> idToRequestId = new HashMap<>();
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private Map<Integer, RNBGDTaskConfig> requestIdToConfig = new HashMap<>();
|
||||
private Downloader downloader;
|
||||
|
||||
private Map<String, Long> condigIdToDownloadId = new HashMap<>();
|
||||
private Map<Long, RNBGDTaskConfig> downloadIdToConfig = new HashMap<>();
|
||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||
private Date lastProgressReport = new Date();
|
||||
private HashMap<String, WritableMap> progressReports = new HashMap<>();
|
||||
private Map<String, OnProgress> onProgressThreads = new HashMap<>();
|
||||
|
||||
private static Object sharedLock = new Object();
|
||||
|
||||
BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(getName(), "RNBD: onReceive-1");
|
||||
try {
|
||||
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
|
||||
Log.d(getName(), "RNBD: onReceive-2 " + downloadId);
|
||||
|
||||
RNBGDTaskConfig config = downloadIdToConfig.get(downloadId);
|
||||
|
||||
if (config != null) {
|
||||
Log.d(getName(), "RNBD: onReceive-3");
|
||||
WritableMap downloadStatus = downloader.checkDownloadStatus(downloadId);
|
||||
int status = downloadStatus.getInt("status");
|
||||
|
||||
Log.d(getName(), "RNBD: onReceive: status - " + status);
|
||||
|
||||
stopTrackingProgress(config.id);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
switch (status) {
|
||||
case DownloadManager.STATUS_SUCCESSFUL: {
|
||||
// MOVES FILE TO DESTINATION
|
||||
String localUri = downloadStatus.getString("localUri");
|
||||
Log.d(getName(), "RNBD: onReceive: localUri " + localUri + " destination " + config.destination);
|
||||
File file = new File(localUri);
|
||||
Log.d(getName(), "RNBD: onReceive: file exists " + file.exists());
|
||||
File dest = new File(config.destination);
|
||||
Log.d(getName(), "RNBD: onReceive: dest exists " + dest.exists());
|
||||
Files.move(file.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putString("location", config.destination);
|
||||
|
||||
ee.emit("downloadComplete", params);
|
||||
break;
|
||||
}
|
||||
case DownloadManager.STATUS_FAILED: {
|
||||
Log.e(getName(), "Error in enqueue: " + downloadStatus.getInt("status") + ":"
|
||||
+ downloadStatus.getInt("reason") + ":" + downloadStatus.getString("reasonText"));
|
||||
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putInt("errorCode", downloadStatus.getInt("reason"));
|
||||
params.putString("error", downloadStatus.getString("reasonText"));
|
||||
ee.emit("downloadFailed", params);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(getName(), "downloadReceiver: onReceive. " + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public RNBackgroundDownloaderModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
ReadableMap emptyMap = Arguments.createMap();
|
||||
this.initDownloader(emptyMap);
|
||||
|
||||
downloader = new Downloader(reactContext);
|
||||
|
||||
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
|
||||
compatRegisterReceiver(reactContext, downloadReceiver, filter, true);
|
||||
}
|
||||
|
||||
// TAKEN FROM
|
||||
// https://github.com/facebook/react-native/pull/38256/files#diff-d5e21477eeadeb0c536d5870f487a8528f9a16ae928c397fec7b255805cc8ad3
|
||||
private void compatRegisterReceiver(Context context, BroadcastReceiver receiver, IntentFilter filter,
|
||||
boolean exported) {
|
||||
if (Build.VERSION.SDK_INT >= 34 && context.getApplicationInfo().targetSdkVersion >= 34) {
|
||||
context.registerReceiver(
|
||||
receiver, filter, exported ? Context.RECEIVER_EXPORTED : Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
context.registerReceiver(receiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopTrackingProgress(String configId) {
|
||||
OnProgress onProgressTh = onProgressThreads.get(configId);
|
||||
if (onProgressTh != null) {
|
||||
onProgressTh.interrupt();
|
||||
onProgressThreads.remove(configId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
fetch.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,46 +216,27 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
constants.put("TaskSuspended", TASK_SUSPENDED);
|
||||
constants.put("TaskCanceling", TASK_CANCELING);
|
||||
constants.put("TaskCompleted", TASK_COMPLETED);
|
||||
constants.put("PriorityHigh", Priority.HIGH.getValue());
|
||||
constants.put("PriorityNormal", Priority.NORMAL.getValue());
|
||||
constants.put("PriorityLow", Priority.LOW.getValue());
|
||||
constants.put("OnlyWifi", NetworkType.WIFI_ONLY.getValue());
|
||||
constants.put("AllNetworks", NetworkType.ALL.getValue());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void initDownloader(ReadableMap options) {
|
||||
if (fetch != null) {
|
||||
fetch.close();
|
||||
fetch = null;
|
||||
}
|
||||
|
||||
Downloader.FileDownloaderType downloaderType = options.getString("type") == "parallel"
|
||||
? Downloader.FileDownloaderType.PARALLEL
|
||||
: Downloader.FileDownloaderType.SEQUENTIAL;
|
||||
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
|
||||
final Downloader okHttpDownloader = new OkHttpDownloader(okHttpClient,
|
||||
downloaderType);
|
||||
Log.d(getName(), "RNBD: initDownloader");
|
||||
|
||||
loadConfigMap();
|
||||
FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(this.getReactApplicationContext())
|
||||
.setDownloadConcurrentLimit(4)
|
||||
.setHttpDownloader(okHttpDownloader)
|
||||
.enableRetryOnNetworkGain(true)
|
||||
.setHttpDownloader(new HttpUrlConnectionDownloader(downloaderType))
|
||||
.build();
|
||||
fetch = Fetch.Impl.getInstance(fetchConfiguration);
|
||||
fetch.addListener(this);
|
||||
|
||||
// TODO. MAYBE REINIT DOWNLOADER
|
||||
}
|
||||
|
||||
private void removeFromMaps(int requestId) {
|
||||
private void removeFromMaps(long downloadId) {
|
||||
Log.d(getName(), "RNBD: removeFromMaps");
|
||||
|
||||
synchronized (sharedLock) {
|
||||
RNBGDTaskConfig config = requestIdToConfig.get(requestId);
|
||||
RNBGDTaskConfig config = downloadIdToConfig.get(downloadId);
|
||||
if (config != null) {
|
||||
idToRequestId.remove(config.id);
|
||||
requestIdToConfig.remove(requestId);
|
||||
condigIdToDownloadId.remove(config.id);
|
||||
downloadIdToConfig.remove(downloadId);
|
||||
|
||||
saveConfigMap();
|
||||
}
|
||||
@@ -179,11 +244,13 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
}
|
||||
|
||||
private void saveConfigMap() {
|
||||
Log.d(getName(), "RNBD: saveConfigMap");
|
||||
|
||||
synchronized (sharedLock) {
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), "RNFileBackgroundDownload_configMap");
|
||||
try {
|
||||
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
|
||||
outputStream.writeObject(requestIdToConfig);
|
||||
outputStream.writeObject(downloadIdToConfig);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
@@ -193,31 +260,17 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
}
|
||||
|
||||
private void loadConfigMap() {
|
||||
Log.d(getName(), "RNBD: loadConfigMap");
|
||||
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), "RNFileBackgroundDownload_configMap");
|
||||
try {
|
||||
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
|
||||
requestIdToConfig = (Map<Integer, RNBGDTaskConfig>) inputStream.readObject();
|
||||
downloadIdToConfig = (Map<Long, RNBGDTaskConfig>) inputStream.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private int convertErrorCode(Error error) {
|
||||
if ((error == Error.FILE_NOT_CREATED)
|
||||
|| (error == Error.WRITE_PERMISSION_DENIED)) {
|
||||
return ERR_NO_WRITE_PERMISSION;
|
||||
} else if ((error == Error.CONNECTION_TIMED_OUT)
|
||||
|| (error == Error.NO_NETWORK_CONNECTION)) {
|
||||
return ERR_NO_INTERNET;
|
||||
} else if (error == Error.NO_STORAGE_SPACE) {
|
||||
return ERR_STORAGE_FULL;
|
||||
} else if (error == Error.FILE_NOT_FOUND) {
|
||||
return ERR_FILE_NOT_FOUND;
|
||||
} else {
|
||||
return ERR_OTHERS;
|
||||
}
|
||||
}
|
||||
|
||||
// JS Methods
|
||||
@ReactMethod
|
||||
public void download(ReadableMap options) {
|
||||
@@ -226,277 +279,220 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
String destination = options.getString("destination");
|
||||
ReadableMap headers = options.getMap("headers");
|
||||
String metadata = options.getString("metadata");
|
||||
int progressInterval = options.getInt("progressInterval");
|
||||
|
||||
boolean isAllowedOverRoaming = options.getBoolean("isAllowedOverRoaming");
|
||||
boolean isAllowedOverMetered = options.getBoolean("isAllowedOverMetered");
|
||||
|
||||
Log.d(getName(), "RNBD: download " + id + " " + url + " " + destination + " " + metadata);
|
||||
|
||||
if (id == null || url == null || destination == null) {
|
||||
Log.e(getName(), "id, url and destination must be set");
|
||||
return;
|
||||
}
|
||||
|
||||
RNBGDTaskConfig config = new RNBGDTaskConfig(id, destination, metadata);
|
||||
final Request request = new Request(url, destination);
|
||||
RNBGDTaskConfig config = new RNBGDTaskConfig(id, url, destination, metadata);
|
||||
final Request request = new Request(Uri.parse(url));
|
||||
request.setAllowedOverRoaming(isAllowedOverRoaming);
|
||||
request.setAllowedOverMetered(isAllowedOverMetered);
|
||||
request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
|
||||
request.setRequiresCharging(false);
|
||||
|
||||
int uuid = (int) (System.currentTimeMillis() & 0xfffffff);
|
||||
// GETS THE FILE EXTENSION FROM PATH
|
||||
String extension = MimeTypeMap.getFileExtensionFromUrl(destination);
|
||||
|
||||
String fileName = uuid + "." + extension;
|
||||
request.setDestinationInExternalFilesDir(this.getReactApplicationContext(), null, fileName);
|
||||
|
||||
// TOREMOVE
|
||||
// request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS.toString(), fileName);
|
||||
// request.setDestinationUri(Uri.parse(destination));
|
||||
|
||||
if (headers != null) {
|
||||
ReadableMapKeySetIterator it = headers.keySetIterator();
|
||||
while (it.hasNextKey()) {
|
||||
String headerKey = it.nextKey();
|
||||
request.addHeader(headerKey, headers.getString(headerKey));
|
||||
request.addRequestHeader(headerKey, headers.getString(headerKey));
|
||||
}
|
||||
}
|
||||
request.setPriority(options.hasKey("priority") ? Priority.valueOf(options.getInt("priority")) : Priority.NORMAL);
|
||||
request.setNetworkType(options.hasKey("network") ? NetworkType.valueOf(options.getInt("network")) : NetworkType.ALL);
|
||||
|
||||
fetch.enqueue(request, new Func<Request>() {
|
||||
@Override
|
||||
public void call(Request download) {
|
||||
}
|
||||
}, new Func<Error>() {
|
||||
@Override
|
||||
public void call(Error error) {
|
||||
//An error occurred when enqueuing a request.
|
||||
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", id);
|
||||
params.putString("error", error.toString());
|
||||
|
||||
int convertedErrCode = convertErrorCode(error);
|
||||
params.putInt("errorcode", convertedErrCode);
|
||||
ee.emit("downloadFailed", params);
|
||||
|
||||
removeFromMaps(request.getId());
|
||||
fetch.remove(request.getId());
|
||||
|
||||
Log.e(getName(), "Error in enqueue: " + error.toString() + ":" + error.getValue());
|
||||
}
|
||||
}
|
||||
);
|
||||
long downloadId = downloader.queueDownload(request);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
lastProgressReport = new Date();
|
||||
idToRequestId.put(id, request.getId());
|
||||
requestIdToConfig.put(request.getId(), config);
|
||||
condigIdToDownloadId.put(id, downloadId);
|
||||
downloadIdToConfig.put(downloadId, config);
|
||||
saveConfigMap();
|
||||
|
||||
WritableMap downloadStatus = downloader.checkDownloadStatus(downloadId);
|
||||
int status = downloadStatus.getInt("status");
|
||||
|
||||
Log.d(getName(), "RNBD: download-1. status: " + status + " downloadId: " + downloadId);
|
||||
|
||||
if (config.reportedBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
config.reportedBegin = true;
|
||||
saveConfigMap();
|
||||
|
||||
Log.d(getName(), "RNBD: download-2 downloadId: " + downloadId);
|
||||
// report begin & progress
|
||||
//
|
||||
// overlaped with thread to not block main thread
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Log.d(getName(), "RNBD: download-3 downloadId: " + downloadId);
|
||||
|
||||
while (true) {
|
||||
WritableMap downloadStatus = downloader.checkDownloadStatus(downloadId);
|
||||
int status = downloadStatus.getInt("status");
|
||||
|
||||
Log.d(getName(), "RNBD: download-3.1 " + status + " downloadId: " + downloadId);
|
||||
|
||||
if (status == DownloadManager.STATUS_RUNNING) {
|
||||
break;
|
||||
}
|
||||
if (status == DownloadManager.STATUS_FAILED || status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
Log.d(getName(), "RNBD: download-3.2 " + status + " downloadId: " + downloadId);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
// EMIT BEGIN
|
||||
OnBegin onBeginTh = new OnBegin(config, ee);
|
||||
onBeginTh.start();
|
||||
// wait for onBeginTh to finish
|
||||
onBeginTh.join();
|
||||
|
||||
Log.d(getName(), "RNBD: download-4 downloadId: " + downloadId);
|
||||
OnProgress onProgressTh = new OnProgress(config, downloadId, ee, downloader, progressInterval);
|
||||
onProgressThreads.put(config.id, onProgressTh);
|
||||
onProgressTh.start();
|
||||
Log.d(getName(), "RNBD: download-5 downloadId: " + downloadId);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
Log.d(getName(), "RNBD: download-6 downloadId: " + downloadId);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: NOT WORKING WITH DownloadManager FOR NOW
|
||||
@ReactMethod
|
||||
public void pauseTask(String identifier) {
|
||||
public void pauseTask(String configId) {
|
||||
Log.d(getName(), "RNBD: pauseTask " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Integer requestId = idToRequestId.get(identifier);
|
||||
if (requestId != null) {
|
||||
fetch.pause(requestId);
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
if (downloadId != null) {
|
||||
downloader.pauseDownload(downloadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: NOT WORKING WITH DownloadManager FOR NOW
|
||||
@ReactMethod
|
||||
public void resumeTask(String configId) {
|
||||
Log.d(getName(), "RNBD: resumeTask " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
if (downloadId != null) {
|
||||
downloader.resumeDownload(downloadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void resumeTask(String identifier) {
|
||||
public void stopTask(String configId) {
|
||||
Log.d(getName(), "RNBD: stopTask-1 " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Integer requestId = idToRequestId.get(identifier);
|
||||
if (requestId != null) {
|
||||
fetch.resume(requestId);
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
Log.d(getName(), "RNBD: stopTask-2 " + configId + " downloadId " + downloadId);
|
||||
if (downloadId != null) {
|
||||
// DELETES CONFIG HERE SO receiver WILL NOT THROW ERROR DOWNLOAD_FAILED TO THE
|
||||
// USER
|
||||
removeFromMaps(downloadId);
|
||||
stopTrackingProgress(configId);
|
||||
|
||||
downloader.cancelDownload(downloadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stopTask(String identifier) {
|
||||
public void completeHandler(String configId, final Promise promise) {
|
||||
Log.d(getName(), "RNBD: completeHandler-1 " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Integer requestId = idToRequestId.get(identifier);
|
||||
if (requestId != null) {
|
||||
fetch.cancel(requestId);
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
Log.d(getName(), "RNBD: completeHandler-2 " + configId + " downloadId " + downloadId);
|
||||
if (downloadId != null) {
|
||||
removeFromMaps(downloadId);
|
||||
// REMOVES DOWNLOAD FROM DownloadManager SO IT WOULD NOT BE RETURNED IN checkForExistingDownloads
|
||||
downloader.cancelDownload(downloadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void checkForExistingDownloads(final Promise promise) {
|
||||
fetch.getDownloads(new Func<List<Download>>() {
|
||||
@Override
|
||||
public void call(@NotNull List<Download> downloads) {
|
||||
Log.d(getName(), "RNBD: checkForExistingDownloads-1");
|
||||
|
||||
WritableArray foundIds = Arguments.createArray();
|
||||
|
||||
synchronized (sharedLock) {
|
||||
for (Download download : downloads) {
|
||||
if (requestIdToConfig.containsKey(download.getId())) {
|
||||
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||
try {
|
||||
DownloadManager.Query downloadQuery = new DownloadManager.Query();
|
||||
Cursor cursor = downloader.downloadManager.query(downloadQuery);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
WritableMap result = downloader.getDownloadStatus(cursor);
|
||||
Long downloadId = Long.parseLong(result.getString("downloadId"));
|
||||
|
||||
if (downloadIdToConfig.containsKey(downloadId)) {
|
||||
Log.d(getName(), "RNBD: checkForExistingDownloads-2");
|
||||
RNBGDTaskConfig config = downloadIdToConfig.get(downloadId);
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putString("metadata", config.metadata);
|
||||
params.putInt("state", stateMap.get(download.getStatus()));
|
||||
params.putInt("bytesWritten", (int)download.getDownloaded());
|
||||
params.putInt("totalBytes", (int)download.getTotal());
|
||||
params.putDouble("percent", ((double)download.getProgress()) / 100);
|
||||
params.putInt("state", stateMap.get(result.getInt("status")));
|
||||
|
||||
int bytesDownloaded = result.getInt("bytesDownloaded");
|
||||
params.putInt("bytesDownloaded", bytesDownloaded);
|
||||
|
||||
int bytesTotal = result.getInt("bytesTotal");
|
||||
params.putInt("bytesTotal", bytesTotal);
|
||||
|
||||
params.putDouble("percent", ((double) bytesDownloaded / bytesTotal));
|
||||
|
||||
foundIds.pushMap(params);
|
||||
|
||||
// TODO: MAYBE ADD headers
|
||||
|
||||
idToRequestId.put(config.id, download.getId());
|
||||
config.reportedBegin = true;
|
||||
condigIdToDownloadId.put(config.id, downloadId);
|
||||
|
||||
// TOREMOVE
|
||||
// config.reportedBegin = true;
|
||||
} else {
|
||||
fetch.delete(download.getId());
|
||||
Log.d(getName(), "RNBD: checkForExistingDownloads-3");
|
||||
downloader.cancelDownload(downloadId);
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(getName(), "Error in checkForExistingDownloads: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
promise.resolve(foundIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch API
|
||||
@Override
|
||||
public void onCompleted(Download download) {
|
||||
synchronized(sharedLock) {
|
||||
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||
if (config != null) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putString("location", config.destination);
|
||||
|
||||
ee.emit("downloadComplete", params);
|
||||
}
|
||||
|
||||
removeFromMaps(download.getId());
|
||||
if (!fetch.isClosed()) {
|
||||
fetch.remove(download.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(Download download, long l, long l1) {
|
||||
synchronized(sharedLock) {
|
||||
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
|
||||
if (!config.reportedBegin) {
|
||||
config.reportedBegin = true;
|
||||
|
||||
params.putInt("expectedBytes", (int)download.getTotal());
|
||||
|
||||
// TODO: MAKE IT IN CUSTOM DOWNLOADER
|
||||
// https://github.com/tonyofrancis/Fetch/issues/347#issuecomment-478349299
|
||||
Thread th = new Thread(() -> {
|
||||
try {
|
||||
WritableMap headersMap = Arguments.createMap();
|
||||
|
||||
URL urlC = new URL(download.getUrl());
|
||||
URLConnection con = urlC.openConnection();
|
||||
Map<String,List<String>> headers = con.getHeaderFields();
|
||||
Set<String> keys = headers.keySet();
|
||||
for (String key : keys) {
|
||||
String val = con.getHeaderField(key);
|
||||
headersMap.putString(key, val);
|
||||
}
|
||||
params.putMap("headers", headersMap);
|
||||
|
||||
ee.emit("downloadBegin", params);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
th.start();
|
||||
th.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
params.putInt("written", (int)download.getDownloaded());
|
||||
params.putInt("total", (int)download.getTotal());
|
||||
params.putDouble("percent", ((double)download.getProgress()) / 100);
|
||||
progressReports.put(config.id, params);
|
||||
Date now = new Date();
|
||||
if (now.getTime() - lastProgressReport.getTime() > 250) {
|
||||
WritableArray reportsArray = Arguments.createArray();
|
||||
for (WritableMap report : progressReports.values()) {
|
||||
reportsArray.pushMap(report);
|
||||
}
|
||||
ee.emit("downloadProgress", reportsArray);
|
||||
lastProgressReport = now;
|
||||
progressReports.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused(Download download) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResumed(Download download) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(Download download) {
|
||||
synchronized(sharedLock) {
|
||||
removeFromMaps(download.getId());
|
||||
fetch.delete(download.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(Download download) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleted(Download download) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded(Download download) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueued(Download download, boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWaitingNetwork(Download download) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Download download, Error error, Throwable throwable) {
|
||||
synchronized(sharedLock) {
|
||||
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||
|
||||
if (config != null ) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
|
||||
int convertedErrCode = convertErrorCode(error);
|
||||
params.putInt("errorcode", convertedErrCode);
|
||||
|
||||
if (error == Error.UNKNOWN && throwable != null) {
|
||||
params.putString("error", throwable.getLocalizedMessage());
|
||||
Log.e(getName(), "UNKNOWN Error in download: " + throwable.getLocalizedMessage());
|
||||
} else {
|
||||
params.putString("error", error.toString());
|
||||
Log.e(getName(), "Error in download: " + error.toString() + ":" + error.getValue());
|
||||
}
|
||||
ee.emit("downloadFailed", params);
|
||||
}
|
||||
|
||||
removeFromMaps(download.getId());
|
||||
fetch.remove(download.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadBlockUpdated(Download download, DownloadBlock downloadBlock, int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStarted(Download download, List<? extends DownloadBlock> list, int i) {
|
||||
}
|
||||
}
|
||||
|
||||
30
index.d.ts
vendored
30
index.d.ts
vendored
@@ -16,8 +16,8 @@ export interface TaskInfoObject {
|
||||
metadata: object | string;
|
||||
|
||||
percent?: number;
|
||||
bytesWritten?: number;
|
||||
totalBytes?: number;
|
||||
bytesDownloaded?: number;
|
||||
bytesTotal?: number;
|
||||
|
||||
beginHandler?: Function;
|
||||
progressHandler?: Function;
|
||||
@@ -37,8 +37,8 @@ export type BeginHandler = ({
|
||||
}: BeginHandlerObject) => any;
|
||||
export type ProgressHandler = (
|
||||
percent: number,
|
||||
bytesWritten: number,
|
||||
totalBytes: number
|
||||
bytesDownloaded: number,
|
||||
bytesTotal: number
|
||||
) => any;
|
||||
export type DoneHandler = () => any;
|
||||
export type ErrorHandler = (error: any, errorCode: any) => any;
|
||||
@@ -55,8 +55,8 @@ export interface DownloadTask {
|
||||
id: string;
|
||||
state: DownloadTaskState;
|
||||
percent: number;
|
||||
bytesWritten: number;
|
||||
totalBytes: number;
|
||||
bytesDownloaded: number;
|
||||
bytesTotal: number;
|
||||
|
||||
begin: (handler: BeginHandler) => DownloadTask;
|
||||
progress: (handler: ProgressHandler) => DownloadTask;
|
||||
@@ -77,7 +77,6 @@ export type CheckForExistingDownloads = () => Promise<DownloadTask[]>;
|
||||
export type EnsureDownloadsAreRunning = () => Promise<void>;
|
||||
|
||||
export interface InitDownloaderOptions {
|
||||
type?: 'parallel' | 'sequential' | null;
|
||||
}
|
||||
export type InitDownloader = (options: InitDownloaderOptions) => undefined;
|
||||
|
||||
@@ -87,6 +86,8 @@ export interface DownloadOption {
|
||||
destination: string;
|
||||
headers?: DownloadHeaders | undefined;
|
||||
metadata?: object;
|
||||
isAllowedOverRoaming?: boolean;
|
||||
isAllowedOverMetered?: boolean;
|
||||
}
|
||||
|
||||
export type Download = (options: DownloadOption) => DownloadTask;
|
||||
@@ -96,17 +97,6 @@ export interface Directories {
|
||||
documents: string;
|
||||
}
|
||||
|
||||
export interface Network {
|
||||
WIFI_ONLY: string;
|
||||
ALL: string;
|
||||
}
|
||||
|
||||
export interface Priority {
|
||||
HIGH: string;
|
||||
MEDIUM: string;
|
||||
LOW: string;
|
||||
}
|
||||
|
||||
export const setHeaders: SetHeaders;
|
||||
export const checkForExistingDownloads: CheckForExistingDownloads;
|
||||
export const ensureDownloadsAreRunning: EnsureDownloadsAreRunning;
|
||||
@@ -114,8 +104,6 @@ export const initDownloader: InitDownloader;
|
||||
export const download: Download;
|
||||
export const completeHandler: CompleteHandler;
|
||||
export const directories: Directories;
|
||||
export const Network: Network;
|
||||
export const Priority: Priority;
|
||||
|
||||
export interface RNBackgroundDownloader {
|
||||
setHeaders: SetHeaders;
|
||||
@@ -125,8 +113,6 @@ export interface RNBackgroundDownloader {
|
||||
download: Download;
|
||||
completeHandler: CompleteHandler;
|
||||
directories: Directories;
|
||||
Network: Network;
|
||||
Priority: Priority;
|
||||
}
|
||||
|
||||
declare const RNBackgroundDownloader: RNBackgroundDownloader;
|
||||
|
||||
55
index.ts
55
index.ts
@@ -7,14 +7,23 @@ const RNBackgroundDownloaderEmitter = new NativeEventEmitter(RNBackgroundDownloa
|
||||
const tasksMap = new Map()
|
||||
let headers = {}
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, expectedBytes, headers }) => {
|
||||
console.log('[RNBackgroundDownloader] downloadBegin', id, expectedBytes, headers)
|
||||
const task = tasksMap.get(id)
|
||||
task?.onBegin({ expectedBytes, headers })
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadProgress', events => {
|
||||
// console.log('[RNBackgroundDownloader] downloadProgress-1', events, tasksMap)
|
||||
for (const event of events) {
|
||||
const task = tasksMap.get(event.id)
|
||||
task?.onProgress(event.percent, event.written, event.total)
|
||||
// console.log('[RNBackgroundDownloader] downloadProgress-2', event.id, task)
|
||||
task?.onProgress(event.percent, event.bytesDownloaded, event.bytesTotal)
|
||||
}
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, location }) => {
|
||||
console.log('[RNBackgroundDownloader] downloadComplete', id, location)
|
||||
const task = tasksMap.get(id)
|
||||
task?.onDone({ location })
|
||||
|
||||
@@ -22,17 +31,13 @@ RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, location })
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
|
||||
console.log('[RNBackgroundDownloader] downloadFailed', event)
|
||||
const task = tasksMap.get(event.id)
|
||||
task?.onError(event.error, event.errorcode)
|
||||
task?.onError(event.error, event.errorCode)
|
||||
|
||||
tasksMap.delete(event.id)
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, expectedBytes, headers }) => {
|
||||
const task = tasksMap.get(id)
|
||||
task?.onBegin({ expectedBytes, headers })
|
||||
})
|
||||
|
||||
export function setHeaders(h = {}) {
|
||||
if (typeof h !== 'object')
|
||||
throw new Error('[RNBackgroundDownloader] headers must be an object')
|
||||
@@ -46,11 +51,14 @@ export function initDownloader (options = {}) {
|
||||
}
|
||||
|
||||
export function checkForExistingDownloads() {
|
||||
console.log('[RNBackgroundDownloader] checkForExistingDownloads-1')
|
||||
return RNBackgroundDownloader.checkForExistingDownloads()
|
||||
.then(foundTasks => {
|
||||
console.log('[RNBackgroundDownloader] checkForExistingDownloads-2', foundTasks)
|
||||
return foundTasks.map(taskInfo => {
|
||||
// SECOND ARGUMENT RE-ASSIGNS EVENT HANDLERS
|
||||
const task = new DownloadTask(taskInfo, tasksMap.get(taskInfo.id))
|
||||
console.log('[RNBackgroundDownloader] checkForExistingDownloads-3', taskInfo)
|
||||
|
||||
if (taskInfo.state === RNBackgroundDownloader.TaskRunning) {
|
||||
task.state = 'DOWNLOADING'
|
||||
@@ -60,7 +68,7 @@ export function checkForExistingDownloads () {
|
||||
task.stop()
|
||||
return null
|
||||
} else if (taskInfo.state === RNBackgroundDownloader.TaskCompleted) {
|
||||
if (taskInfo.bytesWritten === taskInfo.totalBytes)
|
||||
if (taskInfo.bytesDownloaded === taskInfo.bytesTotal)
|
||||
task.state = 'DONE'
|
||||
else
|
||||
// IOS completed the download but it was not done.
|
||||
@@ -68,11 +76,12 @@ export function checkForExistingDownloads () {
|
||||
}
|
||||
tasksMap.set(taskInfo.id, task)
|
||||
return task
|
||||
}).filter(task => task !== null)
|
||||
}).filter(task => !!task)
|
||||
})
|
||||
}
|
||||
|
||||
export function ensureDownloadsAreRunning() {
|
||||
console.log('[RNBackgroundDownloader] ensureDownloadsAreRunning')
|
||||
return checkForExistingDownloads()
|
||||
.then(tasks => {
|
||||
for (const task of tasks)
|
||||
@@ -83,7 +92,12 @@ export function ensureDownloadsAreRunning () {
|
||||
})
|
||||
}
|
||||
|
||||
export function completeHandler (jobId) {
|
||||
export function completeHandler(jobId: string) {
|
||||
if (jobId == null) {
|
||||
console.warn('[RNBackgroundDownloader] completeHandler: jobId is empty')
|
||||
return
|
||||
}
|
||||
|
||||
return RNBackgroundDownloader.completeHandler(jobId)
|
||||
}
|
||||
|
||||
@@ -93,9 +107,12 @@ type DownloadOptions = {
|
||||
destination: string,
|
||||
headers?: object,
|
||||
metadata?: object,
|
||||
isAllowedOverRoaming?: boolean,
|
||||
isAllowedOverMetered?: boolean,
|
||||
}
|
||||
|
||||
export function download(options: DownloadOptions) {
|
||||
console.log('[RNBackgroundDownloader] download', options)
|
||||
if (!options.id || !options.url || !options.destination)
|
||||
throw new Error('[RNBackgroundDownloader] id, url and destination are required')
|
||||
|
||||
@@ -104,6 +121,11 @@ export function download (options : DownloadOptions) {
|
||||
if (!(options.metadata && typeof options.metadata === 'object'))
|
||||
options.metadata = {}
|
||||
|
||||
options.destination = options.destination.replace('file://', '')
|
||||
|
||||
if (options.isAllowedOverRoaming == null) options.isAllowedOverRoaming = true
|
||||
if (options.isAllowedOverMetered == null) options.isAllowedOverMetered = true
|
||||
|
||||
const task = new DownloadTask({
|
||||
id: options.id,
|
||||
metadata: options.metadata,
|
||||
@@ -122,17 +144,6 @@ export const directories = {
|
||||
documents: RNBackgroundDownloader.documents,
|
||||
}
|
||||
|
||||
export const Network = {
|
||||
WIFI_ONLY: RNBackgroundDownloader.OnlyWifi,
|
||||
ALL: RNBackgroundDownloader.AllNetworks,
|
||||
}
|
||||
|
||||
export const Priority = {
|
||||
HIGH: RNBackgroundDownloader.PriorityHigh,
|
||||
MEDIUM: RNBackgroundDownloader.PriorityNormal,
|
||||
LOW: RNBackgroundDownloader.PriorityLow,
|
||||
}
|
||||
|
||||
export default {
|
||||
initDownloader,
|
||||
download,
|
||||
@@ -141,6 +152,4 @@ export default {
|
||||
completeHandler,
|
||||
setHeaders,
|
||||
directories,
|
||||
Network,
|
||||
Priority,
|
||||
}
|
||||
|
||||
@@ -286,8 +286,8 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
@"id": taskConfig.id,
|
||||
@"metadata": taskConfig.metadata,
|
||||
@"state": [NSNumber numberWithInt: task.state],
|
||||
@"bytesWritten": [NSNumber numberWithLongLong:task.countOfBytesReceived],
|
||||
@"totalBytes": [NSNumber numberWithLongLong:task.countOfBytesExpectedToReceive],
|
||||
@"bytesDownloaded": [NSNumber numberWithLongLong:task.countOfBytesReceived],
|
||||
@"bytesTotal": [NSNumber numberWithLongLong:task.countOfBytesExpectedToReceive],
|
||||
@"percent": percent
|
||||
}];
|
||||
taskConfig.reportedBegin = YES;
|
||||
@@ -341,11 +341,11 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedbytesTotal:(int64_t)expectedbytesTotal {
|
||||
NSLog(@"[RNBackgroundDownloader] - [didResumeAtOffset]");
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesDownloaded bytesTotalWritten:(int64_t)bytesTotalWritten bytesTotalExpectedToWrite:(int64_t)bytesTotalExpectedToWrite {
|
||||
NSLog(@"[RNBackgroundDownloader] - [didWriteData]");
|
||||
@synchronized (sharedLock) {
|
||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
||||
@@ -356,7 +356,7 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
|
||||
if (self.bridge) {
|
||||
[self sendEventWithName:@"downloadBegin" body:@{
|
||||
@"id": taskCofig.id,
|
||||
@"expectedBytes": [NSNumber numberWithLongLong: totalBytesExpectedToWrite],
|
||||
@"expectedBytes": [NSNumber numberWithLongLong: bytesTotalExpectedToWrite],
|
||||
@"headers": responseHeaders
|
||||
}];
|
||||
}
|
||||
@@ -364,9 +364,9 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
|
||||
}
|
||||
|
||||
NSNumber *prevPercent = idToPercentMap[taskCofig.id];
|
||||
NSNumber *percent = [NSNumber numberWithFloat:(float)totalBytesWritten/(float)totalBytesExpectedToWrite];
|
||||
NSNumber *percent = [NSNumber numberWithFloat:(float)bytesTotalWritten/(float)bytesTotalExpectedToWrite];
|
||||
if ([percent floatValue] - [prevPercent floatValue] > 0.01f) {
|
||||
progressReports[taskCofig.id] = @{@"id": taskCofig.id, @"written": [NSNumber numberWithLongLong: totalBytesWritten], @"total": [NSNumber numberWithLongLong: totalBytesExpectedToWrite], @"percent": percent};
|
||||
progressReports[taskCofig.id] = @{@"id": taskCofig.id, @"bytesDownloaded": [NSNumber numberWithLongLong: bytesTotalWritten], @"bytesTotal": [NSNumber numberWithLongLong: bytesTotalExpectedToWrite], @"percent": percent};
|
||||
idToPercentMap[taskCofig.id] = percent;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ export default class DownloadTask {
|
||||
metadata = {}
|
||||
|
||||
percent = 0
|
||||
bytesWritten = 0
|
||||
totalBytes = 0
|
||||
bytesDownloaded = 0
|
||||
bytesTotal = 0
|
||||
|
||||
constructor (taskInfo: TaskInfo, originalTask?: TaskInfo) {
|
||||
this.id = taskInfo.id
|
||||
this.percent = taskInfo.percent ?? 0
|
||||
this.bytesWritten = taskInfo.bytesWritten ?? 0
|
||||
this.totalBytes = taskInfo.totalBytes ?? 0
|
||||
this.bytesDownloaded = taskInfo.bytesDownloaded ?? 0
|
||||
this.bytesTotal = taskInfo.bytesTotal ?? 0
|
||||
|
||||
const metadata = this.tryParseJson(taskInfo.metadata)
|
||||
if (metadata)
|
||||
@@ -66,11 +66,11 @@ export default class DownloadTask {
|
||||
this.beginHandler?.({ expectedBytes, headers })
|
||||
}
|
||||
|
||||
onProgress (percent, bytesWritten, totalBytes) {
|
||||
onProgress (percent, bytesDownloaded, bytesTotal) {
|
||||
this.percent = percent
|
||||
this.bytesWritten = bytesWritten
|
||||
this.totalBytes = totalBytes
|
||||
this.progressHandler?.(percent, bytesWritten, totalBytes)
|
||||
this.bytesDownloaded = bytesDownloaded
|
||||
this.bytesTotal = bytesTotal
|
||||
this.progressHandler?.(percent, bytesDownloaded, bytesTotal)
|
||||
}
|
||||
|
||||
onDone ({ location }) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kesha-antonov/react-native-background-downloader",
|
||||
"version": "2.10.0",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"description": "A library for React-Native to help you download large files on iOS and Android both in the foreground and most importantly in the background.",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
|
||||
Reference in New Issue
Block a user