mirror of
https://github.com/zoriya/react-native-background-downloader.git
synced 2026-05-24 15:08:08 +00:00
upd from local changes to work correctly
This commit is contained in:
@@ -92,13 +92,13 @@ let task = RNBackgroundDownloader.download({
|
||||
id: 'file123',
|
||||
url: 'https://link-to-very.large/file.zip'
|
||||
destination: `${RNBackgroundDownloader.directories.documents}/file.zip`
|
||||
}).begin((expectedBytes) => {
|
||||
}).begin(({ expectedBytes, headers }) => {
|
||||
console.log(`Going to download ${expectedBytes} bytes!`);
|
||||
}).progress((percent) => {
|
||||
}).progress(percent => {
|
||||
console.log(`Downloaded: ${percent * 100}%`);
|
||||
}).done(() => {
|
||||
console.log('Download is done!');
|
||||
}).error((error) => {
|
||||
}).error(error => {
|
||||
console.log('Download canceled due to error: ', error);
|
||||
});
|
||||
|
||||
@@ -126,7 +126,7 @@ import RNBackgroundDownloader from 'react-native-background-downloader';
|
||||
let lostTasks = await RNBackgroundDownloader.checkForExistingDownloads();
|
||||
for (let task of lostTasks) {
|
||||
console.log(`Task ${task.id} was found!`);
|
||||
task.progress((percent) => {
|
||||
task.progress(percent => {
|
||||
console.log(`Downloaded: ${percent * 100}%`);
|
||||
}).done(() => {
|
||||
console.log('Downlaod is done!');
|
||||
@@ -158,9 +158,9 @@ let task = RNBackgroundDownloader.download({
|
||||
headers: {
|
||||
Authorization: 'Bearer 2we$@$@Ddd223'
|
||||
}
|
||||
}).begin((expectedBytes) => {
|
||||
}).begin(({ expectedBytes, headers }) => {
|
||||
console.log(`Going to download ${expectedBytes} bytes!`);
|
||||
}).progress((percent) => {
|
||||
}).progress(percent => {
|
||||
console.log(`Downloaded: ${percent * 100}%`);
|
||||
}).done(() => {
|
||||
console.log('Download is done!');
|
||||
@@ -228,7 +228,7 @@ All callback methods return the current instance of the `DownloadTask` for chain
|
||||
|
||||
| Function | Callback Arguments | Info |
|
||||
| ---------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `begin` | expectedBytes | Called when the first byte is received. 💡: this is good place to check if the device has enough storage space for this download |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
@@ -5,11 +5,11 @@ def safeExtGet(prop, fallback) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 28)
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 30)
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet('minSdkVersion', 16)
|
||||
targetSdkVersion safeExtGet('targetSdkVersion', 28)
|
||||
minSdkVersion safeExtGet('minSdkVersion', 21)
|
||||
targetSdkVersion safeExtGet('targetSdkVersion', 30)
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
ndk {
|
||||
@@ -22,8 +22,10 @@ dependencies {
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
if (project.properties['android.useAndroidX'] == 'true' || project.properties['android.useAndroidX'] == true) {
|
||||
api "androidx.tonyodev.fetch2:xfetch2:3.1.4"
|
||||
api "androidx.tonyodev.fetch2:xfetch2:3.1.6"
|
||||
implementation "androidx.tonyodev.fetch2okhttp:xfetch2okhttp:3.1.6"
|
||||
} else {
|
||||
api "com.tonyodev.fetch2:fetch2:3.0.10"
|
||||
api "com.tonyodev.fetch2:fetch2:3.0.12"
|
||||
implementation "com.tonyodev.fetch2okhttp:fetch2okhttp:3.0.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ 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;
|
||||
@@ -24,6 +26,8 @@ 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 org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -40,6 +44,12 @@ import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import java.util.Set;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule implements FetchListener {
|
||||
|
||||
private static final int TASK_RUNNING = 0;
|
||||
@@ -72,15 +82,21 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||
private Date lastProgressReport = new Date();
|
||||
private HashMap<String, WritableMap> progressReports = new HashMap<>();
|
||||
private static Object sharedLock = new Object();
|
||||
private static Object sharedLock = new Object();
|
||||
|
||||
public RNBackgroundDownloaderModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
|
||||
final Downloader okHttpDownloader = new OkHttpDownloader(okHttpClient,
|
||||
Downloader.FileDownloaderType.PARALLEL);
|
||||
|
||||
loadConfigMap();
|
||||
FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(this.getReactApplicationContext())
|
||||
.setDownloadConcurrentLimit(4)
|
||||
.setNamespace("RNBackgroundDownloader")
|
||||
.setHttpDownloader(okHttpDownloader)
|
||||
.enableRetryOnNetworkGain(true)
|
||||
.setHttpDownloader(new HttpUrlConnectionDownloader(Downloader.FileDownloaderType.PARALLEL))
|
||||
.build();
|
||||
fetch = Fetch.Impl.getInstance(fetchConfiguration);
|
||||
fetch.addListener(this);
|
||||
@@ -164,7 +180,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int convertErrorCode(Error error) {
|
||||
if ((error == Error.FILE_NOT_CREATED)
|
||||
|| (error == Error.WRITE_PERMISSION_DENIED)) {
|
||||
@@ -205,7 +221,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
}
|
||||
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) {
|
||||
@@ -214,7 +230,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
@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());
|
||||
@@ -232,6 +248,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
);
|
||||
|
||||
synchronized(sharedLock) {
|
||||
lastProgressReport = new Date();
|
||||
idToRequestId.put(id, request.getId());
|
||||
requestIdToConfig.put(request.getId(), config);
|
||||
saveConfigMap();
|
||||
@@ -288,6 +305,8 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
|
||||
foundIds.pushMap(params);
|
||||
|
||||
// TODO: MAYBE ADD headers
|
||||
|
||||
idToRequestId.put(config.id, download.getId());
|
||||
config.reportedBegin = true;
|
||||
} else {
|
||||
@@ -309,6 +328,9 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
if (config != null) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
|
||||
// TODO: add location
|
||||
|
||||
ee.emit("downloadComplete", params);
|
||||
}
|
||||
|
||||
@@ -331,16 +353,44 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule imp
|
||||
params.putString("id", config.id);
|
||||
|
||||
if (!config.reportedBegin) {
|
||||
params.putInt("expectedBytes", (int)download.getTotal());
|
||||
ee.emit("downloadBegin", params);
|
||||
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();
|
||||
}
|
||||
});
|
||||
try {
|
||||
th.start();
|
||||
th.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} 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() > 1500) {
|
||||
if (now.getTime() - lastProgressReport.getTime() > 250) {
|
||||
WritableArray reportsArray = Arguments.createArray();
|
||||
for (WritableMap report : progressReports.values()) {
|
||||
reportsArray.pushMap(report);
|
||||
|
||||
@@ -15,12 +15,12 @@ RNBackgroundDownloaderEmitter.addListener('downloadProgress', events => {
|
||||
}
|
||||
});
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadComplete', event => {
|
||||
let task = tasksMap.get(event.id);
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, location }) => {
|
||||
let task = tasksMap.get(id);
|
||||
if (task) {
|
||||
task._onDone(event.location);
|
||||
task._onDone({ location });
|
||||
}
|
||||
tasksMap.delete(event.id);
|
||||
tasksMap.delete(id);
|
||||
});
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
|
||||
@@ -31,10 +31,10 @@ RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
|
||||
tasksMap.delete(event.id);
|
||||
});
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadBegin', event => {
|
||||
let task = tasksMap.get(event.id);
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, expectedBytes, headers }) => {
|
||||
let task = tasksMap.get(id);
|
||||
if (task) {
|
||||
task._onBegin(event.expectedBytes);
|
||||
task._onBegin({ expectedBytes, headers });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ export function checkForExistingDownloads() {
|
||||
return RNBackgroundDownloader.checkForExistingDownloads()
|
||||
.then(foundTasks => {
|
||||
return foundTasks.map(taskInfo => {
|
||||
let task = new DownloadTask(taskInfo);
|
||||
let task = new DownloadTask(taskInfo,tasksMap.get(taskInfo.id));
|
||||
if (taskInfo.state === RNBackgroundDownloader.TaskRunning) {
|
||||
task.state = 'DOWNLOADING';
|
||||
} else if (taskInfo.state === RNBackgroundDownloader.TaskSuspended) {
|
||||
@@ -71,6 +71,10 @@ export function checkForExistingDownloads() {
|
||||
});
|
||||
}
|
||||
|
||||
export function completeHandler (jobId) {
|
||||
return RNBackgroundDownloader.completeHandler(jobId)
|
||||
}
|
||||
|
||||
export function download(options) {
|
||||
if (!options.id || !options.url || !options.destination) {
|
||||
throw new Error('[RNBackgroundDownloader] id, url and destination are required');
|
||||
@@ -107,6 +111,7 @@ export const Priority = {
|
||||
export default {
|
||||
download,
|
||||
checkForExistingDownloads,
|
||||
completeHandler,
|
||||
setHeaders,
|
||||
directories,
|
||||
Network,
|
||||
|
||||
+153
-29
@@ -23,6 +23,7 @@ static CompletionHandler storedCompletionHandler;
|
||||
NSMutableDictionary<NSString *, NSDictionary *> *progressReports;
|
||||
NSDate *lastProgressReport;
|
||||
NSNumber *sharedLock;
|
||||
BOOL isNotificationCenterInited;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
@@ -42,7 +43,7 @@ RCT_EXPORT_MODULE();
|
||||
|
||||
- (NSDictionary *)constantsToExport {
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
|
||||
|
||||
return @{
|
||||
@"documents": [paths firstObject],
|
||||
@"TaskRunning": @(NSURLSessionTaskStateRunning),
|
||||
@@ -53,6 +54,7 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
|
||||
- (id) init {
|
||||
NSLog(@"[RNBackgroundDownloader] - [init]");
|
||||
self = [super init];
|
||||
if (self) {
|
||||
taskToConfigMap = [self deserialize:[[NSUserDefaults standardUserDefaults] objectForKey:ID_TO_CONFIG_MAP_KEY]];
|
||||
@@ -65,6 +67,18 @@ RCT_EXPORT_MODULE();
|
||||
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSString *sessonIdentifier = [bundleIdentifier stringByAppendingString:@".backgrounddownloadtask"];
|
||||
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessonIdentifier];
|
||||
sessionConfig.HTTPMaximumConnectionsPerHost = 4;
|
||||
sessionConfig.timeoutIntervalForRequest = 60 * 60; // MAX TIME TO GET NEW DATA IN REQUEST - 1 HOUR
|
||||
sessionConfig.timeoutIntervalForResource = 60 * 60 * 24; // MAX TIME TO DOWNLOAD RESOURCE - 1 DAY
|
||||
sessionConfig.discretionary = NO;
|
||||
sessionConfig.sessionSendsLaunchEvents = YES;
|
||||
if (@available(iOS 9.0, *)) {
|
||||
sessionConfig.shouldUseExtendedBackgroundIdleMode = YES;
|
||||
}
|
||||
if (@available(iOS 13.0, *)) {
|
||||
sessionConfig.allowsExpensiveNetworkAccess = YES;
|
||||
}
|
||||
|
||||
progressReports = [[NSMutableDictionary alloc] init];
|
||||
lastProgressReport = [[NSDate alloc] init];
|
||||
sharedLock = [NSNumber numberWithInt:1];
|
||||
@@ -73,12 +87,63 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
|
||||
- (void)lazyInitSession {
|
||||
if (urlSession == nil) {
|
||||
urlSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
|
||||
NSLog(@"[RNBackgroundDownloader] - [lazyInitSession]");
|
||||
@synchronized (sharedLock) {
|
||||
if (urlSession == nil) {
|
||||
urlSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
|
||||
}
|
||||
if (isNotificationCenterInited != YES) {
|
||||
isNotificationCenterInited = YES;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(resumeTasks:)
|
||||
name:UIApplicationWillEnterForegroundNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
NSLog(@"[RNBackgroundDownloader] - [dealloc]");
|
||||
[urlSession invalidateAndCancel];
|
||||
urlSession = nil;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
// NOTE: FIXES HANGING DOWNLOADS WHEN GOING TO BG
|
||||
- (void) resumeTasks:(NSNotification *) note {
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 1");
|
||||
@synchronized (self->sharedLock) {
|
||||
[urlSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 2");
|
||||
for (NSURLSessionDownloadTask *task in downloadTasks) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 3 - state - %@", [NSNumber numberWithInt:task.state]);
|
||||
// running - 0
|
||||
// suspended - 1
|
||||
// canceling - 2
|
||||
// completed - 3
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 4 - totalBytes - %@", [NSNumber numberWithInt:task.countOfBytesExpectedToReceive]);
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 5 - bytesWritten - %@", [NSNumber numberWithInt:task.countOfBytesReceived]);
|
||||
|
||||
if (task.state == NSURLSessionTaskStateRunning) {
|
||||
[task suspend]; // PAUSE
|
||||
[task resume];
|
||||
}
|
||||
}
|
||||
// TODO: MAYBE ADD FOR OTHER TASKS TYPES
|
||||
// for (NSURLSessionDataTask *task in dataTasks) {
|
||||
// NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 5");
|
||||
// [task resume];
|
||||
// }
|
||||
// for (NSURLSessionUploadTask *task in dataTasks) {
|
||||
// NSLog(@"[RNBackgroundDownloader] - [resumeTasks] 6");
|
||||
// [task resume];
|
||||
// }
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeTaskFromMap: (NSURLSessionTask *)task {
|
||||
NSLog(@"[RNBackgroundDownloader] - [removeTaskFromMap]");
|
||||
@synchronized (sharedLock) {
|
||||
NSNumber *taskId = @(task.taskIdentifier);
|
||||
RNBGDTaskConfig *taskConfig = taskToConfigMap[taskId];
|
||||
@@ -98,6 +163,7 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
|
||||
+ (void)setCompletionHandlerWithIdentifier: (NSString *)identifier completionHandler: (CompletionHandler)completionHandler {
|
||||
NSLog(@"[RNBackgroundDownloader] - [setCompletionHandlerWithIdentifier]");
|
||||
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSString *sessonIdentifier = [bundleIdentifier stringByAppendingString:@".backgrounddownloadtask"];
|
||||
if ([sessonIdentifier isEqualToString:identifier]) {
|
||||
@@ -105,9 +171,31 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
}
|
||||
|
||||
- (NSError *)getServerError: (nonnull NSURLSessionDownloadTask *)downloadTask {
|
||||
NSLog(@"[RNBackgroundDownloader] - [getServerError]");
|
||||
NSError *serverError;
|
||||
NSInteger httpStatusCode = [((NSHTTPURLResponse *)downloadTask.response) statusCode];
|
||||
if(httpStatusCode != 200) {
|
||||
serverError = [NSError errorWithDomain:NSURLErrorDomain
|
||||
code:httpStatusCode
|
||||
userInfo:@{NSLocalizedDescriptionKey: [NSHTTPURLResponse localizedStringForStatusCode: httpStatusCode]}];
|
||||
}
|
||||
return serverError;
|
||||
}
|
||||
|
||||
- (BOOL)saveDownloadedFile: (nonnull RNBGDTaskConfig *) taskConfig downloadURL:(nonnull NSURL *)location error:(NSError **)saveError {
|
||||
NSLog(@"[RNBackgroundDownloader] - [saveDownloadedFile]");
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *destURL = [NSURL fileURLWithPath:taskConfig.destination];
|
||||
[fileManager createDirectoryAtURL:[destURL URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
[fileManager removeItemAtURL:destURL error:nil];
|
||||
|
||||
return [fileManager moveItemAtURL:location toURL:destURL error:saveError];
|
||||
}
|
||||
|
||||
#pragma mark - JS exported methods
|
||||
RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [download]");
|
||||
NSString *identifier = options[@"id"];
|
||||
NSString *url = options[@"url"];
|
||||
NSString *destination = options[@"destination"];
|
||||
@@ -116,17 +204,22 @@ RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [Error] id, url and destination must be set");
|
||||
return;
|
||||
}
|
||||
[self lazyInitSession];
|
||||
|
||||
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
|
||||
if (headers != nil) {
|
||||
for (NSString *headerKey in headers) {
|
||||
[request setValue:[headers valueForKey:headerKey] forHTTPHeaderField:headerKey];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@synchronized (sharedLock) {
|
||||
[self lazyInitSession];
|
||||
NSURLSessionDownloadTask __strong *task = [urlSession downloadTaskWithRequest:request];
|
||||
if (task == nil) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [Error] failed to create download task");
|
||||
return;
|
||||
}
|
||||
|
||||
RNBGDTaskConfig *taskConfig = [[RNBGDTaskConfig alloc] initWithDictionary: @{@"id": identifier, @"destination": destination}];
|
||||
|
||||
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
||||
@@ -134,12 +227,14 @@ RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
|
||||
|
||||
idToTaskMap[identifier] = task;
|
||||
idToPercentMap[identifier] = @0.0;
|
||||
|
||||
|
||||
[task resume];
|
||||
lastProgressReport = [[NSDate alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(pauseTask: (NSString *)identifier) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [pauseTask]");
|
||||
@synchronized (sharedLock) {
|
||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||
if (task != nil && task.state == NSURLSessionTaskStateRunning) {
|
||||
@@ -149,6 +244,7 @@ RCT_EXPORT_METHOD(pauseTask: (NSString *)identifier) {
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(resumeTask: (NSString *)identifier) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTask]");
|
||||
@synchronized (sharedLock) {
|
||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||
if (task != nil && task.state == NSURLSessionTaskStateSuspended) {
|
||||
@@ -158,6 +254,7 @@ RCT_EXPORT_METHOD(resumeTask: (NSString *)identifier) {
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopTask: (NSString *)identifier) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [stopTask]");
|
||||
@synchronized (sharedLock) {
|
||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||
if (task != nil) {
|
||||
@@ -168,6 +265,7 @@ RCT_EXPORT_METHOD(stopTask: (NSString *)identifier) {
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [checkForExistingDownloads]");
|
||||
[self lazyInitSession];
|
||||
[urlSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
|
||||
NSMutableArray *idsFound = [[NSMutableArray alloc] init];
|
||||
@@ -205,22 +303,37 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
NSLog(@"[RNBackgroundDownloader] - [completeHandlerIOS]");
|
||||
if (storedCompletionHandler) {
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
storedCompletionHandler();
|
||||
storedCompletionHandler = nil;
|
||||
}];
|
||||
}
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSURLSessionDownloadDelegate methods
|
||||
- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
|
||||
NSLog(@"[RNBackgroundDownloader] - [didFinishDownloadingToURL]");
|
||||
@synchronized (sharedLock) {
|
||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
||||
if (taskCofig != nil) {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *destURL = [NSURL fileURLWithPath:taskCofig.destination];
|
||||
[fileManager createDirectoryAtURL:[destURL URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
[fileManager removeItemAtURL:destURL error:nil];
|
||||
NSError *moveError;
|
||||
BOOL moved = [fileManager moveItemAtURL:location toURL:destURL error:&moveError];
|
||||
RNBGDTaskConfig *taskConfig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
||||
if (taskConfig != nil) {
|
||||
NSError *error = [self getServerError:downloadTask];
|
||||
if (error == nil) {
|
||||
[self saveDownloadedFile:taskConfig downloadURL:location error:&error];
|
||||
}
|
||||
if (self.bridge) {
|
||||
if (moved) {
|
||||
[self sendEventWithName:@"downloadComplete" body:@{@"id": taskCofig.id}];
|
||||
if (error == nil) {
|
||||
NSDictionary *responseHeaders = ((NSHTTPURLResponse *)downloadTask.response).allHeaderFields;
|
||||
[self sendEventWithName:@"downloadComplete" body:@{@"id": taskConfig.id, @"headers": responseHeaders, @"location": taskConfig.destination}];
|
||||
} else {
|
||||
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskCofig.id, @"error": [moveError localizedDescription]}];
|
||||
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskConfig.id, @"error": [error localizedDescription]}];
|
||||
}
|
||||
}
|
||||
[self removeTaskFromMap:downloadTask];
|
||||
@@ -229,26 +342,33 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
|
||||
NSLog(@"[RNBackgroundDownloader] - [didResumeAtOffset]");
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
||||
NSLog(@"[RNBackgroundDownloader] - [didWriteData]");
|
||||
@synchronized (sharedLock) {
|
||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(downloadTask.taskIdentifier)];
|
||||
if (taskCofig != nil) {
|
||||
if (!taskCofig.reportedBegin) {
|
||||
[self sendEventWithName:@"downloadBegin" body:@{@"id": taskCofig.id, @"expectedBytes": [NSNumber numberWithLongLong: totalBytesExpectedToWrite]}];
|
||||
NSDictionary *responseHeaders = ((NSHTTPURLResponse *)downloadTask.response).allHeaderFields;
|
||||
[self sendEventWithName:@"downloadBegin" body:@{
|
||||
@"id": taskCofig.id,
|
||||
@"expectedBytes": [NSNumber numberWithLongLong: totalBytesExpectedToWrite],
|
||||
@"headers": responseHeaders
|
||||
}];
|
||||
taskCofig.reportedBegin = YES;
|
||||
}
|
||||
|
||||
|
||||
NSNumber *prevPercent = idToPercentMap[taskCofig.id];
|
||||
NSNumber *percent = [NSNumber numberWithFloat:(float)totalBytesWritten/(float)totalBytesExpectedToWrite];
|
||||
if ([percent floatValue] - [prevPercent floatValue] > 0.01f) {
|
||||
progressReports[taskCofig.id] = @{@"id": taskCofig.id, @"written": [NSNumber numberWithLongLong: totalBytesWritten], @"total": [NSNumber numberWithLongLong: totalBytesExpectedToWrite], @"percent": percent};
|
||||
idToPercentMap[taskCofig.id] = percent;
|
||||
}
|
||||
|
||||
|
||||
NSDate *now = [[NSDate alloc] init];
|
||||
if ([now timeIntervalSinceDate:lastProgressReport] > 1.5 && progressReports.count > 0) {
|
||||
if ([now timeIntervalSinceDate:lastProgressReport] > 0.25 && progressReports.count > 0) {
|
||||
if (self.bridge) {
|
||||
[self sendEventWithName:@"downloadProgress" body:[progressReports allValues]];
|
||||
}
|
||||
@@ -260,6 +380,7 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
|
||||
NSLog(@"[RNBackgroundDownloader] - [didCompleteWithError]");
|
||||
@synchronized (sharedLock) {
|
||||
RNBGDTaskConfig *taskCofig = taskToConfigMap[@(task.taskIdentifier)];
|
||||
if (error != nil && error.code != -999 && taskCofig != nil) {
|
||||
@@ -272,12 +393,15 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
}
|
||||
|
||||
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
|
||||
if (storedCompletionHandler) {
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
storedCompletionHandler();
|
||||
storedCompletionHandler = nil;
|
||||
}];
|
||||
}
|
||||
NSLog(@"[RNBackgroundDownloader] - [URLSessionDidFinishEventsForBackgroundURLSession]");
|
||||
// USE completionHandler FROM JS INSTEAD OF THIS
|
||||
// TOREMOVE
|
||||
// if (storedCompletionHandler) {
|
||||
// [[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
// storedCompletionHandler();
|
||||
// storedCompletionHandler = nil;
|
||||
// }];
|
||||
// }
|
||||
}
|
||||
|
||||
#pragma mark - serialization
|
||||
@@ -289,7 +413,7 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
if (data == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
||||
}
|
||||
|
||||
|
||||
+21
-14
@@ -12,7 +12,7 @@ export default class DownloadTask {
|
||||
bytesWritten = 0
|
||||
totalBytes = 0
|
||||
|
||||
constructor(taskInfo) {
|
||||
constructor (taskInfo, originalTask) {
|
||||
if (typeof taskInfo === 'string') {
|
||||
this.id = taskInfo;
|
||||
} else {
|
||||
@@ -21,40 +21,47 @@ export default class DownloadTask {
|
||||
this.bytesWritten = taskInfo.bytesWritten;
|
||||
this.totalBytes = taskInfo.totalBytes;
|
||||
}
|
||||
|
||||
if (originalTask) {
|
||||
this._beginHandler = originalTask._beginHandler
|
||||
this._progressHandler = originalTask._progressHandler
|
||||
this._doneHandler = originalTask._doneHandler
|
||||
this._errorHandler = originalTask._errorHandler
|
||||
}
|
||||
}
|
||||
|
||||
begin(handler) {
|
||||
begin (handler) {
|
||||
validateHandler(handler);
|
||||
this._beginHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
progress(handler) {
|
||||
progress (handler) {
|
||||
validateHandler(handler);
|
||||
this._progressHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
done(handler) {
|
||||
done (handler) {
|
||||
validateHandler(handler);
|
||||
this._doneHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
error(handler) {
|
||||
error (handler) {
|
||||
validateHandler(handler);
|
||||
this._errorHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
_onBegin(expectedBytes) {
|
||||
_onBegin ({ expectedBytes, headers }) {
|
||||
this.state = 'DOWNLOADING';
|
||||
if (this._beginHandler) {
|
||||
this._beginHandler(expectedBytes);
|
||||
this._beginHandler({ expectedBytes, headers });
|
||||
}
|
||||
}
|
||||
|
||||
_onProgress(percent, bytesWritten, totalBytes) {
|
||||
_onProgress (percent, bytesWritten, totalBytes) {
|
||||
this.percent = percent;
|
||||
this.bytesWritten = bytesWritten;
|
||||
this.totalBytes = totalBytes;
|
||||
@@ -63,31 +70,31 @@ export default class DownloadTask {
|
||||
}
|
||||
}
|
||||
|
||||
_onDone() {
|
||||
_onDone ({ location }) {
|
||||
this.state = 'DONE';
|
||||
if (this._doneHandler) {
|
||||
this._doneHandler();
|
||||
this._doneHandler({ location });
|
||||
}
|
||||
}
|
||||
|
||||
_onError(error, errorCode) {
|
||||
_onError (error, errorCode) {
|
||||
this.state = 'FAILED';
|
||||
if (this._errorHandler) {
|
||||
this._errorHandler(error, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
pause() {
|
||||
pause () {
|
||||
this.state = 'PAUSED';
|
||||
RNBackgroundDownloader.pauseTask(this.id);
|
||||
}
|
||||
|
||||
resume() {
|
||||
resume () {
|
||||
this.state = 'DOWNLOADING';
|
||||
RNBackgroundDownloader.resumeTask(this.id);
|
||||
}
|
||||
|
||||
stop() {
|
||||
stop () {
|
||||
this.state = 'STOPPED';
|
||||
RNBackgroundDownloader.stopTask(this.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user