mirror of
https://github.com/zoriya/react-native-background-downloader.git
synced 2026-06-05 10:59:51 +00:00
Sync issue fix, added error code, handled enque failure
Sync issue fix, added error code, handled enque failure
This commit is contained in:
@@ -18,5 +18,5 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
//noinspection GradleDynamicVersion
|
//noinspection GradleDynamicVersion
|
||||||
implementation 'com.facebook.react:react-native:+'
|
implementation 'com.facebook.react:react-native:+'
|
||||||
implementation "com.tonyodev.fetch2:fetch2:3.0.3"
|
implementation "androidx.tonyodev.fetch2:xfetch2:3.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
private static final int TASK_CANCELING = 2;
|
private static final int TASK_CANCELING = 2;
|
||||||
private static final int TASK_COMPLETED = 3;
|
private static final int TASK_COMPLETED = 3;
|
||||||
|
|
||||||
|
private static final int ERR_STORAGE_FULL = 0;
|
||||||
|
private static final int ERR_NO_INTERNET = 1;
|
||||||
|
private static final int ERR_NO_WRITE_PERMISSION = 2;
|
||||||
|
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>() {{
|
private static Map<Status, Integer> stateMap = new HashMap<Status, Integer>() {{
|
||||||
put(Status.DOWNLOADING, TASK_RUNNING);
|
put(Status.DOWNLOADING, TASK_RUNNING);
|
||||||
put(Status.COMPLETED, TASK_COMPLETED);
|
put(Status.COMPLETED, TASK_COMPLETED);
|
||||||
@@ -66,6 +72,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||||
private Date lastProgressReport = new Date();
|
private Date lastProgressReport = new Date();
|
||||||
private HashMap<String, WritableMap> progressReports = new HashMap<>();
|
private HashMap<String, WritableMap> progressReports = new HashMap<>();
|
||||||
|
private static Object sharedLock = new Object();
|
||||||
|
|
||||||
public RNBackgroundDownloaderModule(ReactApplicationContext reactContext) {
|
public RNBackgroundDownloaderModule(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
@@ -116,24 +123,28 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromMaps(int requestId) {
|
private void removeFromMaps(int requestId) {
|
||||||
RNBGDTaskConfig config = requestIdToConfig.get(requestId);
|
synchronized(sharedLock) {
|
||||||
if (config != null) {
|
RNBGDTaskConfig config = requestIdToConfig.get(requestId);
|
||||||
idToRequestId.remove(config.id);
|
if (config != null) {
|
||||||
requestIdToConfig.remove(requestId);
|
idToRequestId.remove(config.id);
|
||||||
|
requestIdToConfig.remove(requestId);
|
||||||
|
|
||||||
saveConfigMap();
|
saveConfigMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveConfigMap() {
|
private void saveConfigMap() {
|
||||||
File file = new File(this.getReactApplicationContext().getFilesDir(), "RNFileBackgroundDownload_configMap");
|
synchronized(sharedLock) {
|
||||||
try {
|
File file = new File(this.getReactApplicationContext().getFilesDir(), "RNFileBackgroundDownload_configMap");
|
||||||
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
|
try {
|
||||||
outputStream.writeObject(requestIdToConfig);
|
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
|
||||||
outputStream.flush();
|
outputStream.writeObject(requestIdToConfig);
|
||||||
outputStream.close();
|
outputStream.flush();
|
||||||
} catch (IOException e) {
|
outputStream.close();
|
||||||
e.printStackTrace();
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,11 +157,27 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
e.printStackTrace();
|
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
|
// JS Methods
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void download(ReadableMap options) {
|
public void download(ReadableMap options) {
|
||||||
String id = options.getString("id");
|
final String id = options.getString("id");
|
||||||
String url = options.getString("url");
|
String url = options.getString("url");
|
||||||
String destination = options.getString("destination");
|
String destination = options.getString("destination");
|
||||||
ReadableMap headers = options.getMap("headers");
|
ReadableMap headers = options.getMap("headers");
|
||||||
@@ -161,7 +188,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
RNBGDTaskConfig config = new RNBGDTaskConfig(id);
|
RNBGDTaskConfig config = new RNBGDTaskConfig(id);
|
||||||
Request request = new Request(url, destination);
|
final Request request = new Request(url, destination);
|
||||||
if (headers != null) {
|
if (headers != null) {
|
||||||
ReadableMapKeySetIterator it = headers.keySetIterator();
|
ReadableMapKeySetIterator it = headers.keySetIterator();
|
||||||
while (it.hasNextKey()) {
|
while (it.hasNextKey()) {
|
||||||
@@ -171,34 +198,66 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
}
|
}
|
||||||
request.setPriority(options.hasKey("priority") ? Priority.valueOf(options.getInt("priority")) : Priority.NORMAL);
|
request.setPriority(options.hasKey("priority") ? Priority.valueOf(options.getInt("priority")) : Priority.NORMAL);
|
||||||
request.setNetworkType(options.hasKey("network") ? NetworkType.valueOf(options.getInt("network")) : NetworkType.ALL);
|
request.setNetworkType(options.hasKey("network") ? NetworkType.valueOf(options.getInt("network")) : NetworkType.ALL);
|
||||||
fetch.enqueue(request, null, null);
|
|
||||||
|
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());
|
||||||
|
|
||||||
idToRequestId.put(id, request.getId());
|
int convertedErrCode = convertErrorCode(error);
|
||||||
requestIdToConfig.put(request.getId(), config);
|
params.putInt("errorcode", convertedErrCode);
|
||||||
saveConfigMap();
|
ee.emit("downloadFailed", params);
|
||||||
|
|
||||||
|
removeFromMaps(request.getId());
|
||||||
|
fetch.remove(request.getId());
|
||||||
|
|
||||||
|
Log.e(getName(), "Error in enqueue: " + error.toString() + ":" + error.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
synchronized(sharedLock) {
|
||||||
|
idToRequestId.put(id, request.getId());
|
||||||
|
requestIdToConfig.put(request.getId(), config);
|
||||||
|
saveConfigMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void pauseTask(String identifier) {
|
public void pauseTask(String identifier) {
|
||||||
Integer requestId = idToRequestId.get(identifier);
|
synchronized(sharedLock) {
|
||||||
if (requestId != null) {
|
Integer requestId = idToRequestId.get(identifier);
|
||||||
fetch.pause(requestId);
|
if (requestId != null) {
|
||||||
|
fetch.pause(requestId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void resumeTask(String identifier) {
|
public void resumeTask(String identifier) {
|
||||||
Integer requestId = idToRequestId.get(identifier);
|
synchronized(sharedLock) {
|
||||||
if (requestId != null) {
|
Integer requestId = idToRequestId.get(identifier);
|
||||||
fetch.resume(requestId);
|
if (requestId != null) {
|
||||||
|
fetch.resume(requestId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void stopTask(String identifier) {
|
public void stopTask(String identifier) {
|
||||||
Integer requestId = idToRequestId.get(identifier);
|
synchronized(sharedLock) {
|
||||||
if (requestId != null) {
|
Integer requestId = idToRequestId.get(identifier);
|
||||||
fetch.cancel(requestId);
|
if (requestId != null) {
|
||||||
|
fetch.cancel(requestId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,22 +268,24 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
public void call(@NotNull List<Download> downloads) {
|
public void call(@NotNull List<Download> downloads) {
|
||||||
WritableArray foundIds = Arguments.createArray();
|
WritableArray foundIds = Arguments.createArray();
|
||||||
|
|
||||||
for (Download download : downloads) {
|
synchronized(sharedLock) {
|
||||||
if (requestIdToConfig.containsKey(download.getId())) {
|
for (Download download : downloads) {
|
||||||
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
if (requestIdToConfig.containsKey(download.getId())) {
|
||||||
WritableMap params = Arguments.createMap();
|
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||||
params.putString("id", config.id);
|
WritableMap params = Arguments.createMap();
|
||||||
params.putInt("state", stateMap.get(download.getStatus()));
|
params.putString("id", config.id);
|
||||||
params.putInt("bytesWritten", (int)download.getDownloaded());
|
params.putInt("state", stateMap.get(download.getStatus()));
|
||||||
params.putInt("totalBytes", (int)download.getTotal());
|
params.putInt("bytesWritten", (int)download.getDownloaded());
|
||||||
params.putDouble("percent", ((double)download.getProgress()) / 100);
|
params.putInt("totalBytes", (int)download.getTotal());
|
||||||
|
params.putDouble("percent", ((double)download.getProgress()) / 100);
|
||||||
|
|
||||||
foundIds.pushMap(params);
|
foundIds.pushMap(params);
|
||||||
|
|
||||||
idToRequestId.put(config.id, download.getId());
|
idToRequestId.put(config.id, download.getId());
|
||||||
config.reportedBegin = true;
|
config.reportedBegin = true;
|
||||||
} else {
|
} else {
|
||||||
fetch.delete(download.getId());
|
fetch.delete(download.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,106 +297,121 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
|||||||
// Fetch API
|
// Fetch API
|
||||||
@Override
|
@Override
|
||||||
public void onCompleted(Download download) {
|
public void onCompleted(Download download) {
|
||||||
WritableMap params = Arguments.createMap();
|
synchronized(sharedLock) {
|
||||||
params.putString("id", requestIdToConfig.get(download.getId()).id);
|
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||||
ee.emit("downloadComplete", params);
|
if (config != null) {
|
||||||
|
WritableMap params = Arguments.createMap();
|
||||||
|
params.putString("id", config.id);
|
||||||
|
ee.emit("downloadComplete", params);
|
||||||
|
}
|
||||||
|
|
||||||
removeFromMaps(download.getId());
|
removeFromMaps(download.getId());
|
||||||
fetch.remove(download.getId());
|
fetch.remove(download.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(Download download, long l, long l1) {
|
public void onProgress(Download download, long l, long l1) {
|
||||||
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
synchronized(sharedLock) {
|
||||||
WritableMap params = Arguments.createMap();
|
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||||
params.putString("id", config.id);
|
if (config == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!config.reportedBegin) {
|
WritableMap params = Arguments.createMap();
|
||||||
params.putInt("expectedBytes", (int)download.getTotal());
|
params.putString("id", config.id);
|
||||||
ee.emit("downloadBegin", params);
|
|
||||||
config.reportedBegin = true;
|
if (!config.reportedBegin) {
|
||||||
} else {
|
params.putInt("expectedBytes", (int)download.getTotal());
|
||||||
params.putInt("written", (int)download.getDownloaded());
|
ee.emit("downloadBegin", params);
|
||||||
params.putInt("total", (int)download.getTotal());
|
config.reportedBegin = true;
|
||||||
params.putDouble("percent", ((double)download.getProgress()) / 100);
|
} else {
|
||||||
progressReports.put(config.id, params);
|
params.putInt("written", (int)download.getDownloaded());
|
||||||
Date now = new Date();
|
params.putInt("total", (int)download.getTotal());
|
||||||
if (now.getTime() - lastProgressReport.getTime() > 1500) {
|
params.putDouble("percent", ((double)download.getProgress()) / 100);
|
||||||
WritableArray reportsArray = Arguments.createArray();
|
progressReports.put(config.id, params);
|
||||||
for (WritableMap report : progressReports.values()) {
|
Date now = new Date();
|
||||||
reportsArray.pushMap(report);
|
if (now.getTime() - lastProgressReport.getTime() > 1500) {
|
||||||
|
WritableArray reportsArray = Arguments.createArray();
|
||||||
|
for (WritableMap report : progressReports.values()) {
|
||||||
|
reportsArray.pushMap(report);
|
||||||
|
}
|
||||||
|
ee.emit("downloadProgress", reportsArray);
|
||||||
|
lastProgressReport = now;
|
||||||
|
progressReports.clear();
|
||||||
}
|
}
|
||||||
ee.emit("downloadProgress", reportsArray);
|
|
||||||
lastProgressReport = now;
|
|
||||||
progressReports.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPaused(Download download) {
|
public void onPaused(Download download) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResumed(Download download) {
|
public void onResumed(Download download) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelled(Download download) {
|
public void onCancelled(Download download) {
|
||||||
removeFromMaps(download.getId());
|
synchronized(sharedLock) {
|
||||||
fetch.delete(download.getId());
|
removeFromMaps(download.getId());
|
||||||
|
fetch.delete(download.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoved(Download download) {
|
public void onRemoved(Download download) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDeleted(Download download) {
|
public void onDeleted(Download download) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(Download download) {
|
public void onAdded(Download download) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onQueued(Download download, boolean b) {
|
public void onQueued(Download download, boolean b) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWaitingNetwork(Download download) {
|
public void onWaitingNetwork(Download download) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Download download, Error error, Throwable throwable) {
|
public void onError(Download download, Error error, Throwable throwable) {
|
||||||
WritableMap params = Arguments.createMap();
|
synchronized(sharedLock) {
|
||||||
params.putString("id", requestIdToConfig.get(download.getId()).id);
|
RNBGDTaskConfig config = requestIdToConfig.get(download.getId());
|
||||||
if (error == Error.UNKNOWN && throwable != null) {
|
|
||||||
params.putString("error", throwable.getLocalizedMessage());
|
|
||||||
} else {
|
|
||||||
params.putString("error", error.toString());
|
|
||||||
}
|
|
||||||
ee.emit("downloadFailed", params);
|
|
||||||
|
|
||||||
removeFromMaps(download.getId());
|
if (config != null ) {
|
||||||
fetch.remove(download.getId());
|
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
|
@Override
|
||||||
public void onDownloadBlockUpdated(Download download, DownloadBlock downloadBlock, int i) {
|
public void onDownloadBlockUpdated(Download download, DownloadBlock downloadBlock, int i) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStarted(Download download, List<? extends DownloadBlock> list, int i) {
|
public void onStarted(Download download, List<? extends DownloadBlock> list, int i) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ RNBackgroundDownloaderEmitter.addListener('downloadComplete', event => {
|
|||||||
RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
|
RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
|
||||||
let task = tasksMap.get(event.id);
|
let task = tasksMap.get(event.id);
|
||||||
if (task) {
|
if (task) {
|
||||||
task._onError(event.error);
|
task._onError(event.error, event.errorcode);
|
||||||
}
|
}
|
||||||
tasksMap.delete(event.id);
|
tasksMap.delete(event.id);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ static CompletionHandler storedCompletionHandler;
|
|||||||
NSMutableDictionary<NSString *, NSNumber *> *idToPercentMap;
|
NSMutableDictionary<NSString *, NSNumber *> *idToPercentMap;
|
||||||
NSMutableDictionary<NSString *, NSDictionary *> *progressReports;
|
NSMutableDictionary<NSString *, NSDictionary *> *progressReports;
|
||||||
NSDate *lastProgressReport;
|
NSDate *lastProgressReport;
|
||||||
|
NSNumber *sharedLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_MODULE();
|
RCT_EXPORT_MODULE();
|
||||||
@@ -66,6 +67,7 @@ RCT_EXPORT_MODULE();
|
|||||||
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessonIdentifier];
|
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessonIdentifier];
|
||||||
progressReports = [[NSMutableDictionary alloc] init];
|
progressReports = [[NSMutableDictionary alloc] init];
|
||||||
lastProgressReport = [[NSDate alloc] init];
|
lastProgressReport = [[NSDate alloc] init];
|
||||||
|
sharedLock = [NSNumber numberWithInt:1];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -77,19 +79,21 @@ RCT_EXPORT_MODULE();
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeTaskFromMap: (NSURLSessionTask *)task {
|
- (void)removeTaskFromMap: (NSURLSessionTask *)task {
|
||||||
NSNumber *taskId = @(task.taskIdentifier);
|
@synchronized (sharedLock) {
|
||||||
RNBGDTaskConfig *taskConfig = taskToConfigMap[taskId];
|
NSNumber *taskId = @(task.taskIdentifier);
|
||||||
@synchronized (taskToConfigMap) {
|
RNBGDTaskConfig *taskConfig = taskToConfigMap[taskId];
|
||||||
|
|
||||||
[taskToConfigMap removeObjectForKey:taskId];
|
[taskToConfigMap removeObjectForKey:taskId];
|
||||||
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
|
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
|
||||||
}
|
|
||||||
if (taskConfig) {
|
if (taskConfig) {
|
||||||
[idToTaskMap removeObjectForKey:taskConfig.id];
|
[idToTaskMap removeObjectForKey:taskConfig.id];
|
||||||
[idToPercentMap removeObjectForKey:taskConfig.id];
|
[idToPercentMap removeObjectForKey:taskConfig.id];
|
||||||
}
|
}
|
||||||
if (taskToConfigMap.count == 0) {
|
if (taskToConfigMap.count == 0) {
|
||||||
[urlSession invalidateAndCancel];
|
[urlSession invalidateAndCancel];
|
||||||
urlSession = nil;
|
urlSession = nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,37 +125,45 @@ RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NSURLSessionDownloadTask __strong *task = [urlSession downloadTaskWithRequest:request];
|
@synchronized (sharedLock) {
|
||||||
RNBGDTaskConfig *taskConfig = [[RNBGDTaskConfig alloc] initWithDictionary: @{@"id": identifier, @"destination": destination}];
|
NSURLSessionDownloadTask __strong *task = [urlSession downloadTaskWithRequest:request];
|
||||||
@synchronized(taskToConfigMap) {
|
RNBGDTaskConfig *taskConfig = [[RNBGDTaskConfig alloc] initWithDictionary: @{@"id": identifier, @"destination": destination}];
|
||||||
|
|
||||||
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
||||||
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
|
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
|
||||||
}
|
|
||||||
idToTaskMap[identifier] = task;
|
|
||||||
idToPercentMap[identifier] = @0.0;
|
|
||||||
|
|
||||||
[task resume];
|
|
||||||
}
|
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(pauseTask: (NSString *)identifier) {
|
idToTaskMap[identifier] = task;
|
||||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
idToPercentMap[identifier] = @0.0;
|
||||||
if (task != nil && task.state == NSURLSessionTaskStateRunning) {
|
|
||||||
[task suspend];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(resumeTask: (NSString *)identifier) {
|
|
||||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
|
||||||
if (task != nil && task.state == NSURLSessionTaskStateSuspended) {
|
|
||||||
[task resume];
|
[task resume];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(pauseTask: (NSString *)identifier) {
|
||||||
|
@synchronized (sharedLock) {
|
||||||
|
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||||
|
if (task != nil && task.state == NSURLSessionTaskStateRunning) {
|
||||||
|
[task suspend];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(resumeTask: (NSString *)identifier) {
|
||||||
|
@synchronized (sharedLock) {
|
||||||
|
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||||
|
if (task != nil && task.state == NSURLSessionTaskStateSuspended) {
|
||||||
|
[task resume];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(stopTask: (NSString *)identifier) {
|
RCT_EXPORT_METHOD(stopTask: (NSString *)identifier) {
|
||||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
@synchronized (sharedLock) {
|
||||||
if (task != nil) {
|
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||||
[task cancel];
|
if (task != nil) {
|
||||||
[self removeTaskFromMap:task];
|
[task cancel];
|
||||||
|
[self removeTaskFromMap:task];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,56 +171,60 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
|||||||
[self lazyInitSession];
|
[self lazyInitSession];
|
||||||
[urlSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
|
[urlSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
|
||||||
NSMutableArray *idsFound = [[NSMutableArray alloc] init];
|
NSMutableArray *idsFound = [[NSMutableArray alloc] init];
|
||||||
for (NSURLSessionDownloadTask *foundTask in downloadTasks) {
|
@synchronized (sharedLock) {
|
||||||
NSURLSessionDownloadTask __strong *task = foundTask;
|
for (NSURLSessionDownloadTask *foundTask in downloadTasks) {
|
||||||
RNBGDTaskConfig *taskConfig = taskToConfigMap[@(task.taskIdentifier)];
|
NSURLSessionDownloadTask __strong *task = foundTask;
|
||||||
if (taskConfig) {
|
RNBGDTaskConfig *taskConfig = taskToConfigMap[@(task.taskIdentifier)];
|
||||||
if (task.state == NSURLSessionTaskStateCompleted && task.countOfBytesReceived < task.countOfBytesExpectedToReceive) {
|
if (taskConfig) {
|
||||||
if (task.error && task.error.code == -999 && task.error.userInfo[NSURLSessionDownloadTaskResumeData] != nil) {
|
if (task.state == NSURLSessionTaskStateCompleted && task.countOfBytesReceived < task.countOfBytesExpectedToReceive) {
|
||||||
task = [urlSession downloadTaskWithResumeData:task.error.userInfo[NSURLSessionDownloadTaskResumeData]];
|
if (task.error && task.error.code == -999 && task.error.userInfo[NSURLSessionDownloadTaskResumeData] != nil) {
|
||||||
} else {
|
task = [urlSession downloadTaskWithResumeData:task.error.userInfo[NSURLSessionDownloadTaskResumeData]];
|
||||||
task = [urlSession downloadTaskWithURL:foundTask.currentRequest.URL];
|
} else {
|
||||||
|
task = [urlSession downloadTaskWithURL:foundTask.currentRequest.URL];
|
||||||
|
}
|
||||||
|
[task resume];
|
||||||
}
|
}
|
||||||
[task resume];
|
NSNumber *percent = foundTask.countOfBytesExpectedToReceive > 0 ? [NSNumber numberWithFloat:(float)task.countOfBytesReceived/(float)foundTask.countOfBytesExpectedToReceive] : @0.0;
|
||||||
|
[idsFound addObject:@{
|
||||||
|
@"id": taskConfig.id,
|
||||||
|
@"state": [NSNumber numberWithInt: task.state],
|
||||||
|
@"bytesWritten": [NSNumber numberWithLongLong:task.countOfBytesReceived],
|
||||||
|
@"totalBytes": [NSNumber numberWithLongLong:foundTask.countOfBytesExpectedToReceive],
|
||||||
|
@"percent": percent
|
||||||
|
}];
|
||||||
|
taskConfig.reportedBegin = YES;
|
||||||
|
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
||||||
|
idToTaskMap[taskConfig.id] = task;
|
||||||
|
idToPercentMap[taskConfig.id] = percent;
|
||||||
|
} else {
|
||||||
|
[task cancel];
|
||||||
}
|
}
|
||||||
NSNumber *percent = foundTask.countOfBytesExpectedToReceive > 0 ? [NSNumber numberWithFloat:(float)task.countOfBytesReceived/(float)foundTask.countOfBytesExpectedToReceive] : @0.0;
|
|
||||||
[idsFound addObject:@{
|
|
||||||
@"id": taskConfig.id,
|
|
||||||
@"state": [NSNumber numberWithInt: task.state],
|
|
||||||
@"bytesWritten": [NSNumber numberWithLongLong:task.countOfBytesReceived],
|
|
||||||
@"totalBytes": [NSNumber numberWithLongLong:foundTask.countOfBytesExpectedToReceive],
|
|
||||||
@"percent": percent
|
|
||||||
}];
|
|
||||||
taskConfig.reportedBegin = YES;
|
|
||||||
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
|
||||||
idToTaskMap[taskConfig.id] = task;
|
|
||||||
idToPercentMap[taskConfig.id] = percent;
|
|
||||||
} else {
|
|
||||||
[task cancel];
|
|
||||||
}
|
}
|
||||||
|
resolve(idsFound);
|
||||||
}
|
}
|
||||||
resolve(idsFound);
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - NSURLSessionDownloadDelegate methods
|
#pragma mark - NSURLSessionDownloadDelegate methods
|
||||||
- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
|
- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
|
||||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
@synchronized (sharedLock) {
|
||||||
if (taskCofig != nil) {
|
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
||||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
if (taskCofig != nil) {
|
||||||
NSURL *destURL = [NSURL fileURLWithPath:taskCofig.destination];
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||||
[fileManager createDirectoryAtURL:[destURL URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
NSURL *destURL = [NSURL fileURLWithPath:taskCofig.destination];
|
||||||
[fileManager removeItemAtURL:destURL error:nil];
|
[fileManager createDirectoryAtURL:[destURL URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||||
NSError *moveError;
|
[fileManager removeItemAtURL:destURL error:nil];
|
||||||
BOOL moved = [fileManager moveItemAtURL:location toURL:destURL error:&moveError];
|
NSError *moveError;
|
||||||
if (self.bridge) {
|
BOOL moved = [fileManager moveItemAtURL:location toURL:destURL error:&moveError];
|
||||||
if (moved) {
|
if (self.bridge) {
|
||||||
[self sendEventWithName:@"downloadComplete" body:@{@"id": taskCofig.id}];
|
if (moved) {
|
||||||
} else {
|
[self sendEventWithName:@"downloadComplete" body:@{@"id": taskCofig.id}];
|
||||||
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskCofig.id, @"error": [moveError localizedDescription]}];
|
} else {
|
||||||
|
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskCofig.id, @"error": [moveError localizedDescription]}];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
[self removeTaskFromMap:downloadTask];
|
||||||
}
|
}
|
||||||
[self removeTaskFromMap:downloadTask];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +232,7 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (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)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
||||||
@synchronized (self) {
|
@synchronized (sharedLock) {
|
||||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
||||||
if (taskCofig != nil) {
|
if (taskCofig != nil) {
|
||||||
if (!taskCofig.reportedBegin) {
|
if (!taskCofig.reportedBegin) {
|
||||||
@@ -244,12 +260,14 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
|
||||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(task.taskIdentifier)];
|
@synchronized (sharedLock) {
|
||||||
if (error != nil && error.code != -999 && taskCofig != nil) {
|
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(task.taskIdentifier)];
|
||||||
if (self.bridge) {
|
if (error != nil && error.code != -999 && taskCofig != nil) {
|
||||||
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskCofig.id, @"error": [error localizedDescription]}];
|
if (self.bridge) {
|
||||||
|
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskCofig.id, @"error": [error localizedDescription]}];
|
||||||
|
}
|
||||||
|
[self removeTaskFromMap:task];
|
||||||
}
|
}
|
||||||
[self removeTaskFromMap:task];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+70
-70
@@ -2,93 +2,93 @@ import { NativeModules } from 'react-native';
|
|||||||
const { RNBackgroundDownloader } = NativeModules;
|
const { RNBackgroundDownloader } = NativeModules;
|
||||||
|
|
||||||
function validateHandler(handler) {
|
function validateHandler(handler) {
|
||||||
if (!(typeof handler === 'function')) {
|
if (!(typeof handler === 'function')) {
|
||||||
throw new TypeError(`[RNBackgroundDownloader] expected argument to be a function, got: ${typeof handler}`);
|
throw new TypeError(`[RNBackgroundDownloader] expected argument to be a function, got: ${typeof handler}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default class DownloadTask {
|
export default class DownloadTask {
|
||||||
state = 'PENDING'
|
state = 'PENDING'
|
||||||
percent = 0
|
percent = 0
|
||||||
bytesWritten = 0
|
bytesWritten = 0
|
||||||
totalBytes = 0
|
totalBytes = 0
|
||||||
|
|
||||||
constructor(taskInfo) {
|
constructor(taskInfo) {
|
||||||
if (typeof taskInfo === 'string') {
|
if (typeof taskInfo === 'string') {
|
||||||
this.id = taskInfo;
|
this.id = taskInfo;
|
||||||
} else {
|
} else {
|
||||||
this.id = taskInfo.id;
|
this.id = taskInfo.id;
|
||||||
this.percent = taskInfo.percent;
|
this.percent = taskInfo.percent;
|
||||||
this.bytesWritten = taskInfo.bytesWritten;
|
this.bytesWritten = taskInfo.bytesWritten;
|
||||||
this.totalBytes = taskInfo.totalBytes;
|
this.totalBytes = taskInfo.totalBytes;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
begin(handler) {
|
begin(handler) {
|
||||||
validateHandler(handler);
|
validateHandler(handler);
|
||||||
this._beginHandler = handler;
|
this._beginHandler = handler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress(handler) {
|
progress(handler) {
|
||||||
validateHandler(handler);
|
validateHandler(handler);
|
||||||
this._progressHandler = handler;
|
this._progressHandler = handler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
done(handler) {
|
done(handler) {
|
||||||
validateHandler(handler);
|
validateHandler(handler);
|
||||||
this._doneHandler = handler;
|
this._doneHandler = handler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
error(handler) {
|
error(handler) {
|
||||||
validateHandler(handler);
|
validateHandler(handler);
|
||||||
this._errorHandler = handler;
|
this._errorHandler = handler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onBegin(expectedBytes) {
|
_onBegin(expectedBytes) {
|
||||||
this.state = 'DOWNLOADING';
|
this.state = 'DOWNLOADING';
|
||||||
if (this._beginHandler) {
|
if (this._beginHandler) {
|
||||||
this._beginHandler(expectedBytes);
|
this._beginHandler(expectedBytes);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onProgress(percent, bytesWritten, totalBytes) {
|
_onProgress(percent, bytesWritten, totalBytes) {
|
||||||
this.percent = percent;
|
this.percent = percent;
|
||||||
this.bytesWritten = bytesWritten;
|
this.bytesWritten = bytesWritten;
|
||||||
this.totalBytes = totalBytes;
|
this.totalBytes = totalBytes;
|
||||||
if (this._progressHandler) {
|
if (this._progressHandler) {
|
||||||
this._progressHandler(percent, bytesWritten, totalBytes);
|
this._progressHandler(percent, bytesWritten, totalBytes);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onDone() {
|
_onDone() {
|
||||||
this.state = 'DONE';
|
this.state = 'DONE';
|
||||||
if (this._doneHandler) {
|
if (this._doneHandler) {
|
||||||
this._doneHandler();
|
this._doneHandler();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onError(error) {
|
_onError(error, errorCode) {
|
||||||
this.state = 'FAILED';
|
this.state = 'FAILED';
|
||||||
if (this._errorHandler) {
|
if (this._errorHandler) {
|
||||||
this._errorHandler(error);
|
this._errorHandler(error, errorCode);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
this.state = 'PAUSED';
|
this.state = 'PAUSED';
|
||||||
RNBackgroundDownloader.pauseTask(this.id);
|
RNBackgroundDownloader.pauseTask(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
resume() {
|
resume() {
|
||||||
this.state = 'DOWNLOADING';
|
this.state = 'DOWNLOADING';
|
||||||
RNBackgroundDownloader.resumeTask(this.id);
|
RNBackgroundDownloader.resumeTask(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.state = 'STOPPED';
|
this.state = 'STOPPED';
|
||||||
RNBackgroundDownloader.stopTask(this.id);
|
RNBackgroundDownloader.stopTask(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ Pod::Spec.new do |s|
|
|||||||
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.
|
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.
|
||||||
DESC
|
DESC
|
||||||
s.author = 'elad@helleko.com'
|
s.author = 'elad@helleko.com'
|
||||||
s.homepage = 'https://github.com/EkoLabs/react-native-background-downloader'
|
s.homepage = 'https://github.com/learnyst/react-native-background-downloader'
|
||||||
s.license = 'MIT'
|
s.license = 'MIT'
|
||||||
s.platform = :ios, '7.0'
|
s.platform = :ios, '7.0'
|
||||||
s.source = { git: 'https://github.com/EkoLabs/react-native-background-downloader.git', tag: 'master' }
|
s.source = { git: 'https://github.com/learnyst/react-native-background-downloader.git', tag: 'master' }
|
||||||
s.source_files = 'ios/**/*.{h,m}'
|
s.source_files = 'ios/**/*.{h,m}'
|
||||||
s.requires_arc = true
|
s.requires_arc = true
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user