FIxed merge conflict

This commit is contained in:
Hagen Hübel 2017-12-10 12:31:41 +01:00
commit c27b5feb8f
6 changed files with 180 additions and 21 deletions

View File

@ -4,6 +4,7 @@ typedef void (^DownloadCompleteCallback)(NSNumber*, NSNumber*);
typedef void (^ErrorCallback)(NSError*);
typedef void (^BeginCallback)(NSNumber*, NSNumber*, NSDictionary*);
typedef void (^ProgressCallback)(NSNumber*, NSNumber*);
typedef void (^ResumableCallback)();
@interface RNFSDownloadParams : NSObject
@ -14,6 +15,7 @@ typedef void (^ProgressCallback)(NSNumber*, NSNumber*);
@property (copy) ErrorCallback errorCallback; // Something went wrong
@property (copy) BeginCallback beginCallback; // Download has started (headers received)
@property (copy) ProgressCallback progressCallback; // Download is progressing
@property (copy) ResumableCallback resumableCallback; // Download has stopped but is resumable
@property bool background; // Whether to continue download when app is in background
@property bool discretionary; // Whether the file may be downloaded at the OS's discretion (iOS only)
@property (copy) NSNumber* progressDivider;
@ -24,7 +26,9 @@ typedef void (^ProgressCallback)(NSNumber*, NSNumber*);
@interface RNFSDownloader : NSObject <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
- (void)downloadFile:(RNFSDownloadParams*)params;
- (NSString *)downloadFile:(RNFSDownloadParams*)params;
- (void)stopDownload;
- (void)resumeDownload;
- (BOOL)isResumable;
@end

View File

@ -9,11 +9,12 @@
@property (copy) RNFSDownloadParams* params;
@property (retain) NSURLSession* session;
@property (retain) NSURLSessionTask* task;
@property (retain) NSURLSessionDownloadTask* task;
@property (retain) NSNumber* statusCode;
@property (retain) NSNumber* lastProgressValue;
@property (retain) NSNumber* contentLength;
@property (retain) NSNumber* bytesWritten;
@property (retain) NSData* resumeData;
@property (retain) NSFileHandle* fileHandle;
@ -21,9 +22,11 @@
@implementation RNFSDownloader
- (void)downloadFile:(RNFSDownloadParams*)params
- (NSString *)downloadFile:(RNFSDownloadParams*)params
{
_params = params;
NSString *uuid = nil;
_params = params;
_bytesWritten = 0;
@ -36,14 +39,15 @@
NSError* error = [NSError errorWithDomain:@"Downloader" code:NSURLErrorFileDoesNotExist
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"Failed to create target file at path: %@", _params.toFile]}];
return _params.errorCallback(error);
_params.errorCallback(error);
return nil;
} else {
[_fileHandle closeFile];
}
NSURLSessionConfiguration *config;
if (_params.background) {
NSString *uuid = [[NSUUID UUID] UUIDString];
uuid = [[NSUUID UUID] UUIDString];
config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:uuid];
config.discretionary = _params.discretionary;
} else {
@ -56,6 +60,8 @@
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
_task = [_session downloadTaskWithURL:url];
[_task resume];
return uuid;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
@ -109,23 +115,48 @@
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error && error.code != -999) {
_params.errorCallback(error);
_resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
if (_resumeData != nil) {
_params.resumableCallback();
} else {
_params.errorCallback(error);
}
}
}
- (void)stopDownload
{
if (_task.state == NSURLSessionTaskStateRunning) {
[_task cancel];
[_task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
if (resumeData != nil) {
self.resumeData = resumeData;
_params.resumableCallback();
} else {
NSError *error = [NSError errorWithDomain:@"RNFS"
code:@"Aborted"
userInfo:@{
NSLocalizedDescriptionKey: @"Download has been aborted"
}];
_params.errorCallback(error);
}
}];
NSError *error = [NSError errorWithDomain:@"RNFS"
code:@"Aborted"
userInfo:@{
NSLocalizedDescriptionKey: @"Download has been aborted"
}];
return _params.errorCallback(error);
}
}
- (void)resumeDownload
{
if (_resumeData != nil) {
_task = [_session downloadTaskWithResumeData:_resumeData];
[_task resume];
_resumeData = nil;
}
}
- (BOOL)isResumable
{
return _resumeData != nil;
}
@end

View File

@ -213,10 +213,22 @@ var RNFS = {
RNFSManager.stopDownload(jobId);
},
resumeDownload(jobId: number): void {
RNFSManager.resumeDownload(jobId);
},
isResumable(jobId: number): Promise<bool> {
return RNFSManager.isResumable(jobId);
},
stopUpload(jobId: number): void {
RNFSManager.stopUpload(jobId);
},
completeHandlerIOS(jobId: number): void {
return RNFSManager.completeHandlerIOS(jobId);
},
readDir(dirpath: string): Promise<ReadDirItem[]> {
return readDirGeneric(dirpath, RNFSManager.readDir);
},
@ -444,6 +456,10 @@ var RNFS = {
subscriptions.push(NativeAppEventEmitter.addListener('DownloadProgress-' + jobId, options.progress));
}
if (options.resumable) {
subscriptions.push(NativeAppEventEmitter.addListener('DownloadResumable-' + jobId, options.resumable));
}
var bridgeOptions = {
jobId: jobId,
fromUrl: options.fromUrl,

View File

@ -507,6 +507,7 @@ type DownloadFileOptions = {
progressDivider?: number;
begin?: (res: DownloadBeginCallbackResult) => void;
progress?: (res: DownloadProgressCallbackResult) => void;
resumable?: () => void; // only supported on iOS yet
connectionTimeout?: number // only supported on Android yet
readTimeout?: number // supported on Android and iOS
};
@ -549,17 +550,36 @@ Use it for performance issues.
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`)
This option is currently only available for iOS, and you must [enable
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.
This option is currently only available for iOS, see the [Background Downloads Tutorial (iOS)](#background-downloads-tutorial-ios) section.
(IOS only): If `options.resumable` is provided, it will be invoked when the download has stopped and and can be resumed using `resumeDownload()`.
### `stopDownload(jobId: number): void`
Abort the current download job with this ID. The partial file will remain on the filesystem.
### (iOS only) `resumeDownload(jobId: number): void`
Resume the current download job with this ID.
### (iOS only) `isResumable(jobId: number): Promise<bool>`
Check if the the download job with this ID is resumable with `resumeDownload()`.
Example:
```
if (await RNFS.isResumable(jobId) {
RNFS.resumeDownload(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> }`
`options` (`Object`) - An object containing named parameters
@ -642,6 +662,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.
## 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 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/RCTLog.h>
typedef void (^CompletionHandler)();
@interface RNFSManager : NSObject <RCTBridgeModule>
+(void)setCompletionHandlerForIdentifier: (NSString *)identifier completionHandler: (CompletionHandler)completionHandler;
@end

View File

@ -23,12 +23,15 @@
@interface RNFSManager()
@property (retain) NSMutableDictionary* downloaders;
@property (retain) NSMutableDictionary* uuids;
@property (retain) NSMutableDictionary* uploaders;
@end
@implementation RNFSManager
static NSMutableDictionary *completionHandlers;
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE();
@ -474,14 +477,22 @@ RCT_EXPORT_METHOD(downloadFile:(NSDictionary *)options
@"contentLength": contentLength,
@"bytesWritten": bytesWritten}];
};
params.resumableCallback = ^() {
[self.bridge.eventDispatcher sendAppEventWithName:[NSString stringWithFormat:@"DownloadResumable-%@", jobId] body:nil];
};
if (!self.downloaders) self.downloaders = [[NSMutableDictionary alloc] init];
RNFSDownloader* downloader = [RNFSDownloader alloc];
[downloader downloadFile:params];
NSString *uuid = [downloader downloadFile:params];
[self.downloaders setValue:downloader forKey:[jobId stringValue]];
if (uuid) {
if (!self.uuids) self.uuids = [[NSMutableDictionary alloc] init];
[self.uuids setValue:uuid forKey:[jobId stringValue]];
}
}
RCT_EXPORT_METHOD(stopDownload:(nonnull NSNumber *)jobId)
@ -493,6 +504,44 @@ RCT_EXPORT_METHOD(stopDownload:(nonnull NSNumber *)jobId)
}
}
RCT_EXPORT_METHOD(resumeDownload:(nonnull NSNumber *)jobId)
{
RNFSDownloader* downloader = [self.downloaders objectForKey:[jobId stringValue]];
if (downloader != nil) {
[downloader resumeDownload];
}
}
RCT_EXPORT_METHOD(isResumable:(nonnull NSNumber *)jobId
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject
)
{
RNFSDownloader* downloader = [self.downloaders objectForKey:[jobId stringValue]];
if (downloader != nil) {
resolve([NSNumber numberWithBool:[downloader isResumable]]);
} else {
resolve([NSNumber numberWithBool:NO]);
}
}
RCT_EXPORT_METHOD(completeHandlerIOS:(nonnull NSNumber *)jobId
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
if (self.uuids) {
NSString *uuid = [self.uuids objectForKey:[jobId stringValue]];
CompletionHandler completionHandler = [completionHandlers objectForKey:uuid];
if (completionHandler) {
completionHandler();
[completionHandlers removeObjectForKey:uuid];
}
}
resolve(nil);
}
RCT_EXPORT_METHOD(uploadFiles:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@ -819,4 +868,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