Sync issue fix, added error code, handled enque failure

Sync issue fix, added error code, handled enque failure
This commit is contained in:
sridhar
2019-10-12 10:51:52 +05:30
parent b7452e84a5
commit 9fef9d38c7
6 changed files with 337 additions and 243 deletions
+1 -1
View File
@@ -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) {
} }
} }
+1 -1
View File
@@ -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);
}); });
+97 -79
View File
@@ -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
View File
@@ -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);
} }
} }
+2 -2
View File
@@ -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