better background downloads

This commit is contained in:
Elad Gil 2017-08-22 11:22:27 +03:00
parent dcbd64a59e
commit f58f726d8e
6 changed files with 83 additions and 12 deletions

View File

@ -24,7 +24,7 @@ typedef void (^ResumableCallback)();
@interface RNFSDownloader : NSObject <NSURLSessionDelegate, NSURLSessionDownloadDelegate> @interface RNFSDownloader : NSObject <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
- (void)downloadFile:(RNFSDownloadParams*)params; - (NSString *)downloadFile:(RNFSDownloadParams*)params;
- (void)stopDownload; - (void)stopDownload;
- (void)resumeDownload; - (void)resumeDownload;
- (BOOL)isResumable; - (BOOL)isResumable;

View File

@ -22,9 +22,11 @@
@implementation RNFSDownloader @implementation RNFSDownloader
- (void)downloadFile:(RNFSDownloadParams*)params - (NSString *)downloadFile:(RNFSDownloadParams*)params
{ {
_params = params; NSString *uuid = nil;
_params = params;
_bytesWritten = 0; _bytesWritten = 0;
@ -37,14 +39,15 @@
NSError* error = [NSError errorWithDomain:@"Downloader" code:NSURLErrorFileDoesNotExist NSError* error = [NSError errorWithDomain:@"Downloader" code:NSURLErrorFileDoesNotExist
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"Failed to create target file at path: %@", _params.toFile]}]; userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"Failed to create target file at path: %@", _params.toFile]}];
return _params.errorCallback(error); _params.errorCallback(error);
return nil;
} else { } else {
[_fileHandle closeFile]; [_fileHandle closeFile];
} }
NSURLSessionConfiguration *config; NSURLSessionConfiguration *config;
if (_params.background) { if (_params.background) {
NSString *uuid = [[NSUUID UUID] UUIDString]; uuid = [[NSUUID UUID] UUIDString];
config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:uuid]; config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:uuid];
} else { } else {
config = [NSURLSessionConfiguration defaultSessionConfiguration]; config = [NSURLSessionConfiguration defaultSessionConfiguration];
@ -55,6 +58,8 @@
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
_task = [_session downloadTaskWithURL:url]; _task = [_session downloadTaskWithURL:url];
[_task resume]; [_task resume];
return uuid;
} }
- (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

View File

@ -224,6 +224,10 @@ var RNFS = {
RNFSManager.stopUpload(jobId); RNFSManager.stopUpload(jobId);
}, },
completeHandlerIOS(jobId: number): void {
RNFSManager.completeHandlerIOS(jobId);
},
readDir(dirpath: string): Promise<ReadDirItem[]> { readDir(dirpath: string): Promise<ReadDirItem[]> {
return readDirGeneric(dirpath, RNFSManager.readDir); return readDirGeneric(dirpath, RNFSManager.readDir);
}, },

View File

@ -503,11 +503,7 @@ Use it for performance issues.
If `progressDivider` = 0, you will receive all `progressCallback` calls, default value is 0. If `progressDivider` = 0, you will receive all `progressCallback` calls, default value is 0.
(IOS only): `options.background` (`Boolean`) - Whether to continue downloads when the app is not focused (default: `false`) (IOS only): `options.background` (`Boolean`) - Whether to continue downloads when the app is not focused (default: `false`)
This option is currently only available for iOS, and you must [enable This option is currently only available for iOS, see the [Background Downloads Tutorial (iOS)](#background-downloads-tutorial-ios) section.
background fetch](https://www.objc.io/issues/5-ios7/multitasking/#background-fetch<Paste>)
for your project in XCode. You only need to enable background fetch in `Info.plist` and set
the fetch interval in `didFinishLaunchingWithOptions`. The `performFetchWithCompletionHandler`
callback is handled by RNFS.
(IOS only): If `options.resumable` is provided, it will be invoked when the download has stopped and and can be resumed using `resumeDownload()`. (IOS only): If `options.resumable` is provided, it will be invoked when the download has stopped and and can be resumed using `resumeDownload()`.
@ -531,6 +527,12 @@ if (await RNFS.isResumable(jobId) {
} }
``` ```
### (iOS only) `completeHandlerIOS(jobId: number): void`
For use when using background downloads, tell iOS you are done handling a completed download.
Read more about background donwloads in the [Background Downloads Tutorial (iOS)](#background-downloads-tutorial-ios) section.
### (iOS only) `uploadFiles(options: UploadFileOptions): { jobId: number, promise: Promise<UploadResult> }` ### (iOS only) `uploadFiles(options: UploadFileOptions): { jobId: number, promise: Promise<UploadResult> }`
`options` (`Object`) - An object containing named parameters `options` (`Object`) - An object containing named parameters
@ -613,6 +615,35 @@ Invalid group identifier will cause a rejection.
For more information read the [Adding an App to an App Group](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19) section. For more information read the [Adding an App to an App Group](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19) section.
## Background Downloads Tutorial (iOS)
Background downloads in iOS require a bit of a setup.
First, in your `AppDelegate.m` file add the following:
```
#import <RNFSManager.h>
...
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
[RNFSManager setCompletionHandlerForIdentifier:identifier completionHandler:completionHandler];
}
```
The `handleEventsForBackgroundURLSession` method is called when a background download is done and your app is not in the foreground.
We need to pass the `completionHandler` to RNFS along with its `identifier`.
The JavaScript will continue to work as usual when the download is done but now you must call `RNFS.completeHandlerIOS(jobId)` when you're done handling the download (show a notification etc.)
**BE AWARE!** iOS will give about 30 sec. to run your code after `handleEventsForBackgroundURLSession` is called and until `completionHandler`
is triggered so don't do anything that might take a long time (like unzipping), you will be able to do it after the user re-launces the app,
otherwide iOS will terminate your app.
## Test / Demo app ## Test / Demo app
Test app to demostrate the use of the module. Useful for testing and developing the module: Test app to demostrate the use of the module. Useful for testing and developing the module:

View File

@ -9,6 +9,10 @@
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#import <React/RCTLog.h> #import <React/RCTLog.h>
typedef void (^CompletionHandler)();
@interface RNFSManager : NSObject <RCTBridgeModule> @interface RNFSManager : NSObject <RCTBridgeModule>
+(void)setCompletionHandlerForIdentifier: (NSString *)identifier completionHandler: (CompletionHandler)completionHandler;
@end @end

View File

@ -23,12 +23,15 @@
@interface RNFSManager() @interface RNFSManager()
@property (retain) NSMutableDictionary* downloaders; @property (retain) NSMutableDictionary* downloaders;
@property (retain) NSMutableDictionary* uuids;
@property (retain) NSMutableDictionary* uploaders; @property (retain) NSMutableDictionary* uploaders;
@end @end
@implementation RNFSManager @implementation RNFSManager
static NSMutableDictionary *completionHandlers;
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
RCT_EXPORT_MODULE(); RCT_EXPORT_MODULE();
@ -463,9 +466,14 @@ RCT_EXPORT_METHOD(downloadFile:(NSDictionary *)options
RNFSDownloader* downloader = [RNFSDownloader alloc]; RNFSDownloader* downloader = [RNFSDownloader alloc];
[downloader downloadFile:params]; NSString *uuid = [downloader downloadFile:params];
[self.downloaders setValue:downloader forKey:[jobId stringValue]]; [self.downloaders setValue:downloader forKey:[jobId stringValue]];
if (uuid) {
NSLog(@"setting uuid: %@", uuid);
if (!self.uuids) self.uuids = [[NSMutableDictionary alloc] init];
[self.uuids setValue:uuid forKey:[jobId stringValue]];
}
} }
RCT_EXPORT_METHOD(stopDownload:(nonnull NSNumber *)jobId) RCT_EXPORT_METHOD(stopDownload:(nonnull NSNumber *)jobId)
@ -500,6 +508,19 @@ RCT_EXPORT_METHOD(isResumable:(nonnull NSNumber *)jobId
} }
} }
RCT_EXPORT_METHOD(completeHandlerIOS:(nonnull NSNumber *)jobId)
{
if (self.uuids) {
NSString *uuid = [self.uuids objectForKey:[jobId stringValue]];
CompletionHandler completionHandler = [completionHandlers objectForKey:uuid];
if (completionHandler) {
NSLog(@"Calling completion handler on: %@", uuid);
completionHandler();
[completionHandlers removeObjectForKey:uuid];
}
}
}
RCT_EXPORT_METHOD(uploadFiles:(NSDictionary *)options RCT_EXPORT_METHOD(uploadFiles:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) rejecter:(RCTPromiseRejectBlock)reject)
@ -773,4 +794,10 @@ RCT_EXPORT_METHOD(touch:(NSString*)filepath
}; };
} }
+(void)setCompletionHandlerForIdentifier: (NSString *)identifier completionHandler: (CompletionHandler)completionHandler
{
if (!completionHandlers) completionHandlers = [[NSMutableDictionary alloc] init];
[completionHandlers setValue:completionHandler forKey:identifier];
}
@end @end