FIxed merge conflict
This commit is contained in:
commit
c27b5feb8f
|
@ -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
|
||||
|
|
59
Downloader.m
59
Downloader.m
|
@ -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
|
||||
|
|
16
FS.common.js
16
FS.common.js
|
@ -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,
|
||||
|
|
59
README.md
59
README.md
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue