diff --git a/README.md b/README.md index 8831db5cf..5f73c6d81 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ RNFetchBlob console.log('The file saved to ', res.path()) // Beware that when using a file path as Image source on Android, // you must prepend "file://"" before the file path - imageView = + imageView = }) ``` diff --git a/android.js b/android.js index 80c4e4a96..3e4faa472 100644 --- a/android.js +++ b/android.js @@ -38,9 +38,25 @@ function addCompleteDownload(config) { return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.') } +function getSDCardDir() { + if(Platform.OS === 'android') + return RNFetchBlob.getSDCardDir() + else + return Promise.reject('RNFetchBlob.android.getSDCardDir only supports Android.') +} + +function getSDCardApplicationDir() { + if(Platform.OS === 'android') + return RNFetchBlob.getSDCardApplicationDir() + else + return Promise.reject('RNFetchBlob.android.getSDCardApplicationDir only supports Android.') +} + export default { actionViewIntent, getContentIntent, - addCompleteDownload + addCompleteDownload, + getSDCardDir, + getSDCardApplicationDir, } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index c4980d445..64b0b4833 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -100,7 +100,6 @@ public void run() { RNFetchBlobFS.createFileASCII(path, dataArray, promise); } }); - } @ReactMethod @@ -164,7 +163,6 @@ public void run() { RNFetchBlobFS.cp(path, dest, callback); } }); - } @ReactMethod @@ -225,7 +223,6 @@ public void run() { RNFetchBlobFS.writeFile(path, encoding, data, append, promise); } }); - } @ReactMethod @@ -260,7 +257,6 @@ public void run() { new RNFetchBlobFS(ctx).scanFile(p, m, callback); } }); - } @ReactMethod @@ -331,7 +327,7 @@ public void enableUploadProgressReport(String taskId, int interval, int count) { @ReactMethod public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) { new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run(); -} + } @ReactMethod public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) { @@ -377,4 +373,13 @@ public void addCompleteDownload (ReadableMap config, Promise promise) { } + @ReactMethod + public void getSDCardDir(Promise promise) { + RNFetchBlobFS.getSDCardDir(promise); + } + + @ReactMethod + public void getSDCardApplicationDir(Promise promise) { + RNFetchBlobFS.getSDCardApplicationDir(this.getReactApplicationContext(), promise); + } } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index dde39bc4c..39972d066 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -246,12 +246,38 @@ static Map getSystemfolders(ReactApplicationContext ctx) { state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath()); - res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath()); + try { + res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath()); + } catch(Exception e) { + res.put("SDCardApplicationDir", ""); + } } res.put("MainBundleDir", ctx.getApplicationInfo().dataDir); return res; } + static public void getSDCardDir(Promise promise) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + promise.resolve(Environment.getExternalStorageDirectory().getAbsolutePath()); + } else { + promise.reject("RNFetchBlob.getSDCardDir", "External storage not mounted"); + } + + } + + static public void getSDCardApplicationDir(ReactApplicationContext ctx, Promise promise) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + try { + final String path = ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath(); + promise.resolve(path); + } catch (Exception e) { + promise.reject("RNFetchBlob.getSDCardApplicationDir", e.getLocalizedMessage()); + } + } else { + promise.reject("RNFetchBlob.getSDCardApplicationDir", "External storage not mounted"); + } + } + /** * Static method that returns a temp file path * @param taskId An unique string for identify diff --git a/fs.js b/fs.js index a286e937a..2b4b3ba78 100644 --- a/fs.js +++ b/fs.js @@ -13,17 +13,26 @@ import RNFetchBlobFile from './class/RNFetchBlobFile' const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob const dirs = { - DocumentDir: RNFetchBlob.DocumentDir, - CacheDir: RNFetchBlob.CacheDir, - PictureDir: RNFetchBlob.PictureDir, - MusicDir: RNFetchBlob.MusicDir, - MovieDir: RNFetchBlob.MovieDir, - DownloadDir: RNFetchBlob.DownloadDir, - DCIMDir: RNFetchBlob.DCIMDir, - SDCardDir: RNFetchBlob.SDCardDir, - SDCardApplicationDir: RNFetchBlob.SDCardApplicationDir, - MainBundleDir: RNFetchBlob.MainBundleDir, - LibraryDir: RNFetchBlob.LibraryDir + DocumentDir : RNFetchBlob.DocumentDir, + CacheDir : RNFetchBlob.CacheDir, + PictureDir : RNFetchBlob.PictureDir, + MusicDir : RNFetchBlob.MusicDir, + MovieDir : RNFetchBlob.MovieDir, + DownloadDir : RNFetchBlob.DownloadDir, + DCIMDir : RNFetchBlob.DCIMDir, + get SDCardDir() { + console.warn('SDCardDir as a constant is deprecated and will be removed in feature release. ' + + 'Use RNFetchBlob.android.getSDCardDir():Promise instead.'); + return RNFetchBlob.SDCardDir; + }, + get SDCardApplicationDir() { + console.warn('SDCardApplicationDir as a constant is deprecated and will be removed in feature release. ' + + 'Use RNFetchBlob.android.getSDCardApplicationDir():Promise instead. ' + + 'This variable can be empty on error in native code.'); + return RNFetchBlob.SDCardApplicationDir; + }, + MainBundleDir : RNFetchBlob.MainBundleDir, + LibraryDir : RNFetchBlob.LibraryDir } function addCode(code: string, error: Error): Error { diff --git a/ios/RNFetchBlob.xcodeproj/project.pbxproj b/ios/RNFetchBlob.xcodeproj/project.pbxproj index acd524413..070fcabb1 100644 --- a/ios/RNFetchBlob.xcodeproj/project.pbxproj +++ b/ios/RNFetchBlob.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */; }; A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */; }; A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */; }; A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; }; @@ -29,6 +30,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobRequest.h; sourceTree = ""; }; + 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobRequest.m; sourceTree = ""; }; A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobFS.m; sourceTree = ""; }; A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobFS.h; sourceTree = ""; }; A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobConst.h; sourceTree = ""; }; @@ -71,8 +74,10 @@ A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */, A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */, A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */, - A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */, A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */, + A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */, + 8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */, + 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */, A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */, A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */, A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */, @@ -149,6 +154,7 @@ buildActionMask = 2147483647; files = ( A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */, + 8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */, A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */, A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */, A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */, diff --git a/ios/RNFetchBlob/RNFetchBlob.h b/ios/RNFetchBlob/RNFetchBlob.h index e6385114f..669a093b5 100644 --- a/ios/RNFetchBlob/RNFetchBlob.h +++ b/ios/RNFetchBlob/RNFetchBlob.h @@ -39,7 +39,6 @@ @property (retain) UIDocumentInteractionController * documentController; + (RCTBridge *)getRCTBridge; -+ (void) checkExpiredSessions; @end diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 47bda76c5..d2c25a1f1 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -38,7 +38,7 @@ - (dispatch_queue_t) methodQueue { + (RCTBridge *)getRCTBridge { - RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view; + RCTRootView * rootView = (RCTRootView*) [[UIApplication sharedApplication] keyWindow].rootViewController.view; return rootView.bridge; } @@ -96,8 +96,12 @@ - (NSDictionary *)constantsToExport // send HTTP request else { - RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init]; - [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback]; + [[RNFetchBlobNetwork sharedInstance] sendRequest:options + contentLength:bodyLength + bridge:self.bridge + taskId:taskId + withRequest:req + callback:callback]; } }]; @@ -128,8 +132,12 @@ - (NSDictionary *)constantsToExport // send HTTP request else { - __block RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init]; - [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback]; + [[RNFetchBlobNetwork sharedInstance] sendRequest:options + contentLength:bodyLength + bridge:self.bridge + taskId:taskId + withRequest:req + callback:callback]; } }]; } @@ -518,7 +526,7 @@ - (NSDictionary *)constantsToExport #pragma mark - net.cancelRequest RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) { - [RNFetchBlobNetwork cancelRequest:taskId]; + [[RNFetchBlobNetwork sharedInstance] cancelRequest:taskId]; callback(@[[NSNull null], taskId]); } @@ -528,14 +536,14 @@ - (NSDictionary *)constantsToExport { RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count]; - [RNFetchBlobNetwork enableProgressReport:taskId config:cfg]; + [[RNFetchBlobNetwork sharedInstance] enableProgressReport:taskId config:cfg]; } #pragma mark - net.enableUploadProgressReport RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count) { RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Upload interval:interval count:count]; - [RNFetchBlobNetwork enableUploadProgress:taskId config:cfg]; + [[RNFetchBlobNetwork sharedInstance] enableUploadProgress:taskId config:cfg]; } #pragma mark - fs.slice diff --git a/ios/RNFetchBlobConst.m b/ios/RNFetchBlobConst.m index 6f7fef4b2..bc9b793a5 100644 --- a/ios/RNFetchBlobConst.m +++ b/ios/RNFetchBlobConst.m @@ -7,38 +7,38 @@ // #import "RNFetchBlobConst.h" -extern NSString *const FILE_PREFIX = @"RNFetchBlob-file://"; -extern NSString *const ASSET_PREFIX = @"bundle-assets://"; -extern NSString *const AL_PREFIX = @"assets-library://"; +NSString *const FILE_PREFIX = @"RNFetchBlob-file://"; +NSString *const ASSET_PREFIX = @"bundle-assets://"; +NSString *const AL_PREFIX = @"assets-library://"; // fetch configs -extern NSString *const CONFIG_USE_TEMP = @"fileCache"; -extern NSString *const CONFIG_FILE_PATH = @"path"; -extern NSString *const CONFIG_FILE_EXT = @"appendExt"; -extern NSString *const CONFIG_TRUSTY = @"trusty"; -extern NSString *const CONFIG_INDICATOR = @"indicator"; -extern NSString *const CONFIG_KEY = @"key"; -extern NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes"; +NSString *const CONFIG_USE_TEMP = @"fileCache"; +NSString *const CONFIG_FILE_PATH = @"path"; +NSString *const CONFIG_FILE_EXT = @"appendExt"; +NSString *const CONFIG_TRUSTY = @"trusty"; +NSString *const CONFIG_INDICATOR = @"indicator"; +NSString *const CONFIG_KEY = @"key"; +NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes"; -extern NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState"; -extern NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush"; -extern NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress"; -extern NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload"; -extern NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire"; +NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState"; +NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush"; +NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress"; +NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload"; +NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire"; -extern NSString *const MSG_EVENT = @"RNFetchBlobMessage"; -extern NSString *const MSG_EVENT_LOG = @"log"; -extern NSString *const MSG_EVENT_WARN = @"warn"; -extern NSString *const MSG_EVENT_ERROR = @"error"; -extern NSString *const FS_EVENT_DATA = @"data"; -extern NSString *const FS_EVENT_END = @"end"; -extern NSString *const FS_EVENT_WARN = @"warn"; -extern NSString *const FS_EVENT_ERROR = @"error"; +NSString *const MSG_EVENT = @"RNFetchBlobMessage"; +NSString *const MSG_EVENT_LOG = @"log"; +NSString *const MSG_EVENT_WARN = @"warn"; +NSString *const MSG_EVENT_ERROR = @"error"; +NSString *const FS_EVENT_DATA = @"data"; +NSString *const FS_EVENT_END = @"end"; +NSString *const FS_EVENT_WARN = @"warn"; +NSString *const FS_EVENT_ERROR = @"error"; -extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress"; -extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress"; +NSString *const KEY_REPORT_PROGRESS = @"reportProgress"; +NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress"; // response type -extern NSString *const RESP_TYPE_BASE64 = @"base64"; -extern NSString *const RESP_TYPE_UTF8 = @"utf8"; -extern NSString *const RESP_TYPE_PATH = @"path"; +NSString *const RESP_TYPE_BASE64 = @"base64"; +NSString *const RESP_TYPE_UTF8 = @"utf8"; +NSString *const RESP_TYPE_PATH = @"path"; diff --git a/ios/RNFetchBlobFS.h b/ios/RNFetchBlobFS.h index 7cf0e4faa..222b2f8e6 100644 --- a/ios/RNFetchBlobFS.h +++ b/ios/RNFetchBlobFS.h @@ -34,8 +34,8 @@ NSString * streamId; } -@property (nonatomic) NSOutputStream * outStream; -@property (nonatomic) NSInputStream * inStream; +@property (nonatomic) NSOutputStream * _Nullable outStream; +@property (nonatomic) NSInputStream * _Nullable inStream; @property (strong, nonatomic) RCTResponseSenderBlock callback; @property (nonatomic) RCTBridge * bridge; @property (nonatomic) NSString * encoding; diff --git a/ios/RNFetchBlobNetwork.h b/ios/RNFetchBlobNetwork.h index d3b4654a5..1512712af 100644 --- a/ios/RNFetchBlobNetwork.h +++ b/ios/RNFetchBlobNetwork.h @@ -6,9 +6,13 @@ // Copyright © 2016 wkh237. All rights reserved. // +#ifndef RNFetchBlobNetwork_h +#define RNFetchBlobNetwork_h + #import #import "RNFetchBlobProgress.h" #import "RNFetchBlobFS.h" +#import "RNFetchBlobRequest.h" #if __has_include() #import @@ -16,42 +20,26 @@ #import "RCTBridgeModule.h" #endif -#ifndef RNFetchBlobNetwork_h -#define RNFetchBlobNetwork_h - - - -typedef void(^CompletionHander)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error); -typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse * _Nullable response, NSError * _Nullable error); @interface RNFetchBlobNetwork : NSObject -@property (nullable, nonatomic) NSString * taskId; -@property (nonatomic) int expectedBytes; -@property (nonatomic) int receivedBytes; -@property (nonatomic) BOOL isServerPush; -@property (nullable, nonatomic) NSMutableData * respData; -@property (strong, nonatomic) RCTResponseSenderBlock callback; -@property (nullable, nonatomic) RCTBridge * bridge; -@property (nullable, nonatomic) NSDictionary * options; -@property (nullable, nonatomic) RNFetchBlobFS * fileStream; -@property (strong, nonatomic) CompletionHander fileTaskCompletionHandler; -@property (strong, nonatomic) DataTaskCompletionHander dataTaskCompletionHandler; -@property (nullable, nonatomic) NSError * error; - +@property(nonnull, nonatomic) NSOperationQueue *taskQueue; +@property(nonnull, nonatomic) NSMapTable * requestsTable; ++ (RNFetchBlobNetwork* _Nullable)sharedInstance; + (NSMutableDictionary * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers; -+ (void) cancelRequest:(NSString *)taskId; -+ (void) enableProgressReport:(NSString *) taskId; -+ (void) enableUploadProgress:(NSString *) taskId; + (void) emitExpiredTasks; - (nullable id) init; -- (void) sendRequest; -- (void) sendRequest:(NSDictionary * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback; -+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config; -+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config; - +- (void) sendRequest:(NSDictionary * _Nullable )options + contentLength:(long)contentLength + bridge:(RCTBridge * _Nullable)bridgeRef + taskId:(NSString * _Nullable)taskId + withRequest:(NSURLRequest * _Nullable)req + callback:(_Nullable RCTResponseSenderBlock) callback; +- (void) cancelRequest:(NSString * _Nonnull)taskId; +- (void) enableProgressReport:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config; +- (void) enableUploadProgress:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config; @end diff --git a/ios/RNFetchBlobNetwork.m b/ios/RNFetchBlobNetwork.m index 34ebabb95..4531bf35e 100644 --- a/ios/RNFetchBlobNetwork.m +++ b/ios/RNFetchBlobNetwork.m @@ -8,13 +8,10 @@ #import -#import "RNFetchBlob.h" -#import "RNFetchBlobFS.h" #import "RNFetchBlobNetwork.h" + +#import "RNFetchBlob.h" #import "RNFetchBlobConst.h" -#import "RNFetchBlobReqBuilder.h" -#import "IOS7Polyfill.h" -#import #import "RNFetchBlobProgress.h" #if __has_include() @@ -35,130 +32,43 @@ // //////////////////////////////////////// -NSMapTable * taskTable; NSMapTable * expirationTable; -NSMutableDictionary * progressTable; -NSMutableDictionary * uploadProgressTable; __attribute__((constructor)) static void initialize_tables() { - if(expirationTable == nil) - { + if (expirationTable == nil) { expirationTable = [[NSMapTable alloc] init]; } - if(taskTable == nil) - { - taskTable = [[NSMapTable alloc] init]; - } - if(progressTable == nil) - { - progressTable = [[NSMutableDictionary alloc] init]; - } - if(uploadProgressTable == nil) - { - uploadProgressTable = [[NSMutableDictionary alloc] init]; - } -} - - -typedef NS_ENUM(NSUInteger, ResponseFormat) { - UTF8, - BASE64, - AUTO -}; - - -@interface RNFetchBlobNetwork () -{ - BOOL * respFile; - BOOL isNewPart; - BOOL * isIncrement; - NSMutableData * partBuffer; - NSString * destPath; - NSOutputStream * writeStream; - long bodyLength; - NSMutableDictionary * respInfo; - NSInteger respStatus; - NSMutableArray * redirects; - ResponseFormat responseFormat; - BOOL * followRedirect; - BOOL backgroundTask; } -@end @implementation RNFetchBlobNetwork -NSOperationQueue *taskQueue; -@synthesize taskId; -@synthesize expectedBytes; -@synthesize receivedBytes; -@synthesize respData; -@synthesize callback; -@synthesize bridge; -@synthesize options; -@synthesize fileTaskCompletionHandler; -@synthesize dataTaskCompletionHandler; -@synthesize error; - -// constructor - (id)init { self = [super init]; - if(taskQueue == nil) { - @synchronized ([RNFetchBlobNetwork class]) { - if (taskQueue == nil) { - taskQueue = [[NSOperationQueue alloc] init]; - taskQueue.maxConcurrentOperationCount = 10; - } - } + if (self) { + self.requestsTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory]; + + self.taskQueue = [[NSOperationQueue alloc] init]; + self.taskQueue.qualityOfService = NSQualityOfServiceUtility; + self.taskQueue.maxConcurrentOperationCount = 10; } + return self; } -+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config -{ - if(progressTable == nil) - { - progressTable = [[NSMutableDictionary alloc] init]; - } - [progressTable setValue:config forKey:taskId]; -} - -+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config -{ - if(uploadProgressTable == nil) - { - uploadProgressTable = [[NSMutableDictionary alloc] init]; - } - [uploadProgressTable setValue:config forKey:taskId]; -} - -// removing case from headers -+ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers -{ - - NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init]; - for(NSString * key in headers) { - [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]]; - } - - return mheaders; -} - -- (NSString *)md5:(NSString *)input { - const char* str = [input UTF8String]; - unsigned char result[CC_MD5_DIGEST_LENGTH]; - CC_MD5(str, (CC_LONG)strlen(str), result); ++ (RNFetchBlobNetwork* _Nullable)sharedInstance { + static id _sharedInstance = nil; + static dispatch_once_t onceToken; - NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2]; - for(int i = 0; i 0) - { - defaultConfigObject.timeoutIntervalForRequest = timeout/1000; - } - defaultConfigObject.HTTPMaximumConnectionsPerHost = 10; - session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:taskQueue]; - if(path != nil || [self.options valueForKey:CONFIG_USE_TEMP]!= nil) - { - respFile = YES; - - NSString* cacheKey = taskId; - if (key != nil) { - cacheKey = [self md5:key]; - if (cacheKey == nil) { - cacheKey = taskId; - } - - destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]]; - if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) { - callback(@[[NSNull null], RESP_TYPE_PATH, destPath]); - return; - } - } - - if(path != nil) - destPath = path; - else - destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]]; - } - else - { - respData = [[NSMutableData alloc] init]; - respFile = NO; - } - - __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req]; + RNFetchBlobRequest *request = [[RNFetchBlobRequest alloc] init]; + [request sendRequest:options + contentLength:contentLength + bridge:bridgeRef + taskId:taskId + withRequest:req + taskOperationQueue:self.taskQueue + callback:callback]; - [taskTable setObject:@{ @"session" : task, @"isCancelled" : @NO } forKey:taskId]; - [task resume]; - - // network status indicator - if ([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES) { - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; - }); - } - __block UIApplication * app = [UIApplication sharedApplication]; - -} - -// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled -+ (void) emitExpiredTasks -{ - NSEnumerator * emu = [expirationTable keyEnumerator]; - NSString * key; - - while((key = [emu nextObject])) - { - RCTBridge * bridge = [RNFetchBlob getRCTBridge]; - NSData * args = @{ @"taskId": key }; - [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args]; - + @synchronized([RNFetchBlobNetwork class]) { + [self.requestsTable setObject:request forKey:taskId]; } - - // clear expired task entries - [expirationTable removeAllObjects]; - expirationTable = [[NSMapTable alloc] init]; - } -//////////////////////////////////////// -// -// NSURLSession delegates -// -//////////////////////////////////////// - - -#pragma mark NSURLSession delegate methods - - -#pragma mark - Received Response -// set expected content length on response received -- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler +- (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config { - expectedBytes = [response expectedContentLength]; - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; - NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; - NSString * respType = @""; - respStatus = statusCode; - if ([response respondsToSelector:@selector(allHeaderFields)]) - { - NSDictionary *headers = [httpResponse allHeaderFields]; - NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString]; - if(self.isServerPush == NO) - { - self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"]; - } - if(self.isServerPush) - { - if(partBuffer != nil) - { - [self.bridge.eventDispatcher - sendDeviceEventWithName:EVENT_SERVER_PUSH - body:@{ - @"taskId": taskId, - @"chunk": [partBuffer base64EncodedStringWithOptions:0], - } - ]; - } - partBuffer = [[NSMutableData alloc] init]; - completionHandler(NSURLSessionResponseAllow); - return; - } - if(respCType != nil) - { - NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE]; - if([respCType RNFBContainsString:@"text/"]) - { - respType = @"text"; - } - else if([respCType RNFBContainsString:@"application/json"]) - { - respType = @"json"; - } - // If extra blob content type is not empty, check if response type matches - else if( extraBlobCTypes != nil) { - for(NSString * substr in extraBlobCTypes) - { - if([respCType RNFBContainsString:[substr lowercaseString]]) - { - respType = @"blob"; - respFile = YES; - destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil]; - break; - } - } - } - else - { - respType = @"blob"; - // for XMLHttpRequest, switch response data handling strategy automatically - if([options valueForKey:@"auto"] == YES) { - respFile = YES; - destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""]; - } - } - } - else - respType = @"text"; - respInfo = @{ - @"taskId": taskId, - @"state": @"2", - @"headers": headers, - @"redirects": redirects, - @"respType" : respType, - @"timeout" : @NO, - @"status": [NSNumber numberWithInteger:statusCode] - }; - -#pragma mark - handling cookies - // # 153 get cookies - if(response.URL != nil) - { - NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - NSArray * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL]; - if(cookies != nil && [cookies count] > 0) { - [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil]; - } - } - - [self.bridge.eventDispatcher - sendDeviceEventWithName: EVENT_STATE_CHANGE - body:respInfo - ]; - headers = nil; - respInfo = nil; - - } - else - NSLog(@"oops"); - - if(respFile == YES) - { - @try{ - NSFileManager * fm = [NSFileManager defaultManager]; - NSString * folder = [destPath stringByDeletingLastPathComponent]; - if(![fm fileExistsAtPath:folder]) - { - [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil]; - } - BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue]; - BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"]; - - appendToExistingFile = !overwrite; - - // For solving #141 append response data if the file already exists - // base on PR#139 @kejinliang - if(appendToExistingFile) - { - destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""]; - } - if (![fm fileExistsAtPath:destPath]) - { - [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil]; - } - writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile]; - [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - [writeStream open]; - } - @catch(NSException * ex) - { - NSLog(@"write file error"); + if (config) { + @synchronized ([RNFetchBlobNetwork class]) { + [self.requestsTable objectForKey:taskId].progressConfig = config; } } - - completionHandler(NSURLSessionResponseAllow); -} - - -// download progress handler -- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data -{ - // For #143 handling multipart/x-mixed-replace response - if(self.isServerPush) - { - [partBuffer appendData:data]; - return ; - } - - NSNumber * received = [NSNumber numberWithLong:[data length]]; - receivedBytes += [received longValue]; - NSString * chunkString = @""; - - if(isIncrement == YES) - { - chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - } - - if(respFile == NO) - { - [respData appendData:data]; - } - else - { - [writeStream write:[data bytes] maxLength:[data length]]; - } - RNFetchBlobProgress * pconfig = [progressTable valueForKey:taskId]; - if(expectedBytes == 0) - return; - NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)]; - if(pconfig != nil && [pconfig shouldReport:now]) - { - [self.bridge.eventDispatcher - sendDeviceEventWithName:EVENT_PROGRESS - body:@{ - @"taskId": taskId, - @"written": [NSString stringWithFormat:@"%d", receivedBytes], - @"total": [NSString stringWithFormat:@"%d", expectedBytes], - @"chunk": chunkString - } - ]; - } - received = nil; - -} - -- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error -{ - if([session isEqual:session]) - session = nil; } - -- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error +- (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config { - - self.error = error; - NSString * errMsg = [NSNull null]; - NSString * respStr = [NSNull null]; - NSString * rnfbRespType = @""; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - }); - - if(respInfo == nil) - { - respInfo = [NSNull null]; - } - - if(error != nil) - { - errMsg = [error localizedDescription]; - } - NSDictionary * taskSession = [taskTable objectForKey:taskId]; - BOOL isCancelled = [[taskSession valueForKey:@"isCancelled"] boolValue]; - if(isCancelled) { - errMsg = @"task cancelled"; - } - - if(respFile == YES) - { - [writeStream close]; - rnfbRespType = RESP_TYPE_PATH; - respStr = destPath; - } - // base64 response - else { - // #73 fix unicode data encoding issue : - // when response type is BASE64, we should first try to encode the response data to UTF8 format - // if it turns out not to be `nil` that means the response data contains valid UTF8 string, - // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding. - NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding]; - - if(responseFormat == BASE64) - { - rnfbRespType = RESP_TYPE_BASE64; - respStr = [respData base64EncodedStringWithOptions:0]; - } - else if (responseFormat == UTF8) - { - rnfbRespType = RESP_TYPE_UTF8; - respStr = utf8; - } - else - { - if(utf8 != nil) - { - rnfbRespType = RESP_TYPE_UTF8; - respStr = utf8; - } - else - { - rnfbRespType = RESP_TYPE_BASE64; - respStr = [respData base64EncodedStringWithOptions:0]; - } + if (config) { + @synchronized ([RNFetchBlobNetwork class]) { + [self.requestsTable objectForKey:taskId].uploadProgressConfig = config; } } - - - callback(@[ errMsg, rnfbRespType, respStr]); - - @synchronized(taskTable, uploadProgressTable, progressTable) - { - if([taskTable objectForKey:taskId] == nil) - NSLog(@"object released by ARC."); - else - [taskTable removeObjectForKey:taskId]; - [uploadProgressTable removeObjectForKey:taskId]; - [progressTable removeObjectForKey:taskId]; - } - - respData = nil; - receivedBytes = 0; - [session finishTasksAndInvalidate]; - -} - -// upload progress handler -- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite -{ - RNFetchBlobProgress * pconfig = [uploadProgressTable valueForKey:taskId]; - if(totalBytesExpectedToWrite == 0) - return; - NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)]; - if(pconfig != nil && [pconfig shouldReport:now]) { - [self.bridge.eventDispatcher - sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD - body:@{ - @"taskId": taskId, - @"written": [NSString stringWithFormat:@"%d", totalBytesWritten], - @"total": [NSString stringWithFormat:@"%d", totalBytesExpectedToWrite] - } - ]; - } } -+ (void) cancelRequest:(NSString *)taskId +- (void) cancelRequest:(NSString *)taskId { - NSDictionary * task = [taskTable objectForKey:taskId]; + NSURLSessionDataTask * task; - if(task != nil) { - NSURLSessionDataTask * session = [task objectForKey:@"session"]; - if(session.state == NSURLSessionTaskStateRunning) { - [task setValue:@NO forKey:@"isCancelled"]; - [session cancel]; - } + @synchronized ([RNFetchBlobNetwork class]) { + task = [self.requestsTable objectForKey:taskId].task; } -} - - -- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler -{ - BOOL trusty = [options valueForKey:CONFIG_TRUSTY]; - if(!trusty) - { - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); - } - else - { - completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + if (task && task.state == NSURLSessionTaskStateRunning) { + [task cancel]; } } - -- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session +// removing case from headers ++ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers { - NSLog(@"sess done in background"); + NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init]; + for (NSString * key in headers) { + [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]]; + } + + return mheaders; } -- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler +// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled ++ (void) emitExpiredTasks { - - if(followRedirect) - { - if(request.URL != nil) - [redirects addObject:[request.URL absoluteString]]; - completionHandler(request); - } - else - { - completionHandler(nil); + @synchronized ([RNFetchBlobNetwork class]){ + NSEnumerator * emu = [expirationTable keyEnumerator]; + NSString * key; + + while ((key = [emu nextObject])) + { + RCTBridge * bridge = [RNFetchBlob getRCTBridge]; + id args = @{ @"taskId": key }; + [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args]; + + } + + // clear expired task entries + [expirationTable removeAllObjects]; + expirationTable = [[NSMapTable alloc] init]; } } diff --git a/ios/RNFetchBlobReqBuilder.h b/ios/RNFetchBlobReqBuilder.h index e7abeb9c7..1edc3ff50 100644 --- a/ios/RNFetchBlobReqBuilder.h +++ b/ios/RNFetchBlobReqBuilder.h @@ -29,7 +29,7 @@ body:(NSString *)body onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete; -+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers; ++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers; @end diff --git a/ios/RNFetchBlobReqBuilder.m b/ios/RNFetchBlobReqBuilder.m index b6048f553..16154436e 100644 --- a/ios/RNFetchBlobReqBuilder.m +++ b/ios/RNFetchBlobReqBuilder.m @@ -277,7 +277,7 @@ void __block (^getFieldData)(id field) = ^(id field) } } -+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableDictionary *) headers { ++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers { NSString * normalCase = [headers valueForKey:field]; NSString * ignoredCase = [headers valueForKey:[field lowercaseString]]; diff --git a/ios/RNFetchBlobRequest.h b/ios/RNFetchBlobRequest.h new file mode 100644 index 000000000..b550ac22e --- /dev/null +++ b/ios/RNFetchBlobRequest.h @@ -0,0 +1,47 @@ +// +// RNFetchBlobRequest.h +// RNFetchBlob +// +// Created by Artur Chrusciel on 15.01.18. +// Copyright © 2018 wkh237.github.io. All rights reserved. +// + +#ifndef RNFetchBlobRequest_h +#define RNFetchBlobRequest_h + +#import + +#import "RNFetchBlobProgress.h" + +#if __has_include() +#import +#else +#import "RCTBridgeModule.h" +#endif + +@interface RNFetchBlobRequest : NSObject + +@property (nullable, nonatomic) NSString * taskId; +@property (nonatomic) long long expectedBytes; +@property (nonatomic) long long receivedBytes; +@property (nonatomic) BOOL isServerPush; +@property (nullable, nonatomic) NSMutableData * respData; +@property (nullable, strong, nonatomic) RCTResponseSenderBlock callback; +@property (nullable, nonatomic) RCTBridge * bridge; +@property (nullable, nonatomic) NSDictionary * options; +@property (nullable, nonatomic) NSError * error; +@property (nullable, nonatomic) RNFetchBlobProgress *progressConfig; +@property (nullable, nonatomic) RNFetchBlobProgress *uploadProgressConfig; +@property (nullable, nonatomic, weak) NSURLSessionDataTask *task; + +- (void) sendRequest:(NSDictionary * _Nullable )options + contentLength:(long)contentLength + bridge:(RCTBridge * _Nullable)bridgeRef + taskId:(NSString * _Nullable)taskId + withRequest:(NSURLRequest * _Nullable)req + taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue + callback:(_Nullable RCTResponseSenderBlock) callback; + +@end + +#endif /* RNFetchBlobRequest_h */ diff --git a/ios/RNFetchBlobRequest.m b/ios/RNFetchBlobRequest.m new file mode 100644 index 000000000..c77668ce8 --- /dev/null +++ b/ios/RNFetchBlobRequest.m @@ -0,0 +1,477 @@ +// +// RNFetchBlobRequest.m +// RNFetchBlob +// +// Created by Artur Chrusciel on 15.01.18. +// Copyright © 2018 wkh237.github.io. All rights reserved. +// + +#import "RNFetchBlobRequest.h" + +#import "RNFetchBlobFS.h" +#import "RNFetchBlobConst.h" +#import "RNFetchBlobReqBuilder.h" + +#import "IOS7Polyfill.h" +#import + + +typedef NS_ENUM(NSUInteger, ResponseFormat) { + UTF8, + BASE64, + AUTO +}; + +@interface RNFetchBlobRequest () +{ + BOOL respFile; + BOOL isNewPart; + BOOL isIncrement; + NSMutableData * partBuffer; + NSString * destPath; + NSOutputStream * writeStream; + long bodyLength; + NSInteger respStatus; + NSMutableArray * redirects; + ResponseFormat responseFormat; + BOOL followRedirect; + BOOL backgroundTask; +} + +@end + +@implementation RNFetchBlobRequest + +@synthesize taskId; +@synthesize expectedBytes; +@synthesize receivedBytes; +@synthesize respData; +@synthesize callback; +@synthesize bridge; +@synthesize options; +@synthesize error; + + +- (NSString *)md5:(NSString *)input { + const char* str = [input UTF8String]; + unsigned char result[CC_MD5_DIGEST_LENGTH]; + CC_MD5(str, (CC_LONG)strlen(str), result); + + NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2]; + for (int i = 0; i 0) { + defaultConfigObject.timeoutIntervalForRequest = timeout/1000; + } + + defaultConfigObject.HTTPMaximumConnectionsPerHost = 10; + session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue]; + + if (path || [self.options valueForKey:CONFIG_USE_TEMP]) { + respFile = YES; + + NSString* cacheKey = taskId; + if (key) { + cacheKey = [self md5:key]; + + if (!cacheKey) { + cacheKey = taskId; + } + + destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) { + callback(@[[NSNull null], RESP_TYPE_PATH, destPath]); + + return; + } + } + + if (path) { + destPath = path; + } else { + destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]]; + } + } else { + respData = [[NSMutableData alloc] init]; + respFile = NO; + } + + self.task = [session dataTaskWithRequest:req]; + [self.task resume]; + + // network status indicator + if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; + }); + } +} + +//////////////////////////////////////// +// +// NSURLSession delegates +// +//////////////////////////////////////// + + +#pragma mark NSURLSession delegate methods + + +#pragma mark - Received Response +// set expected content length on response received +- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler +{ + expectedBytes = [response expectedContentLength]; + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; + NSString * respType = @""; + respStatus = statusCode; + + if ([response respondsToSelector:@selector(allHeaderFields)]) + { + NSDictionary *headers = [httpResponse allHeaderFields]; + NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString]; + + if (self.isServerPush) { + if (partBuffer) { + [self.bridge.eventDispatcher + sendDeviceEventWithName:EVENT_SERVER_PUSH + body:@{ + @"taskId": taskId, + @"chunk": [partBuffer base64EncodedStringWithOptions:0], + } + ]; + } + + partBuffer = [[NSMutableData alloc] init]; + completionHandler(NSURLSessionResponseAllow); + + return; + } else { + self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"]; + } + + if(respCType) + { + NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE]; + + if ([respCType RNFBContainsString:@"text/"]) { + respType = @"text"; + } else if ([respCType RNFBContainsString:@"application/json"]) { + respType = @"json"; + } else if(extraBlobCTypes) { // If extra blob content type is not empty, check if response type matches + for (NSString * substr in extraBlobCTypes) { + if ([respCType RNFBContainsString:[substr lowercaseString]]) { + respType = @"blob"; + respFile = YES; + destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil]; + break; + } + } + } else { + respType = @"blob"; + + // for XMLHttpRequest, switch response data handling strategy automatically + if ([options valueForKey:@"auto"]) { + respFile = YES; + destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""]; + } + } + } else { + respType = @"text"; + } + +#pragma mark - handling cookies + // # 153 get cookies + if (response.URL) { + NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + NSArray * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL]; + if (cookies.count) { + [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil]; + } + } + + [self.bridge.eventDispatcher + sendDeviceEventWithName: EVENT_STATE_CHANGE + body:@{ + @"taskId": taskId, + @"state": @"2", + @"headers": headers, + @"redirects": redirects, + @"respType" : respType, + @"timeout" : @NO, + @"status": [NSNumber numberWithInteger:statusCode] + } + ]; + } else { + NSLog(@"oops"); + } + + if (respFile) + { + @try{ + NSFileManager * fm = [NSFileManager defaultManager]; + NSString * folder = [destPath stringByDeletingLastPathComponent]; + + if (![fm fileExistsAtPath:folder]) { + [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil]; + } + + // if not set overwrite in options, defaults to TRUE + BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue]; + BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"]; + + appendToExistingFile = !overwrite; + + // For solving #141 append response data if the file already exists + // base on PR#139 @kejinliang + if (appendToExistingFile) { + destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""]; + } + + if (![fm fileExistsAtPath:destPath]) { + [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil]; + } + + writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile]; + [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + [writeStream open]; + } + @catch(NSException * ex) + { + NSLog(@"write file error"); + } + } + + completionHandler(NSURLSessionResponseAllow); +} + + +// download progress handler +- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data +{ + // For #143 handling multipart/x-mixed-replace response + if (self.isServerPush) + { + [partBuffer appendData:data]; + + return ; + } + + NSNumber * received = [NSNumber numberWithLong:[data length]]; + receivedBytes += [received longValue]; + NSString * chunkString = @""; + + if (isIncrement) { + chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } + + if (respFile) { + [writeStream write:[data bytes] maxLength:[data length]]; + } else { + [respData appendData:data]; + } + + if (expectedBytes == 0) { + return; + } + + NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)]; + + if ([self.progressConfig shouldReport:now]) { + [self.bridge.eventDispatcher + sendDeviceEventWithName:EVENT_PROGRESS + body:@{ + @"taskId": taskId, + @"written": [NSString stringWithFormat:@"%ld", (long) receivedBytes], + @"total": [NSString stringWithFormat:@"%ld", (long) expectedBytes], + @"chunk": chunkString + } + ]; + } +} + +- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error +{ + if ([session isEqual:session]) { + session = nil; + } +} + + +- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error +{ + + self.error = error; + NSString * errMsg; + NSString * respStr; + NSString * rnfbRespType; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + }); + + if (error) { + if (error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled) { + errMsg = @"task cancelled"; + } else { + errMsg = [error localizedDescription]; + } + } + + if (respFile) { + [writeStream close]; + rnfbRespType = RESP_TYPE_PATH; + respStr = destPath; + } else { // base64 response + // #73 fix unicode data encoding issue : + // when response type is BASE64, we should first try to encode the response data to UTF8 format + // if it turns out not to be `nil` that means the response data contains valid UTF8 string, + // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding. + NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding]; + + if (responseFormat == BASE64) { + rnfbRespType = RESP_TYPE_BASE64; + respStr = [respData base64EncodedStringWithOptions:0]; + } else if (responseFormat == UTF8) { + rnfbRespType = RESP_TYPE_UTF8; + respStr = utf8; + } else { + if (utf8) { + rnfbRespType = RESP_TYPE_UTF8; + respStr = utf8; + } else { + rnfbRespType = RESP_TYPE_BASE64; + respStr = [respData base64EncodedStringWithOptions:0]; + } + } + } + + + callback(@[ + errMsg ?: [NSNull null], + rnfbRespType ?: @"", + respStr ?: [NSNull null] + ]); + + respData = nil; + receivedBytes = 0; + [session finishTasksAndInvalidate]; + +} + +// upload progress handler +- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite +{ + if (totalBytesExpectedToWrite == 0) { + return; + } + + NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)]; + + if ([self.uploadProgressConfig shouldReport:now]) { + [self.bridge.eventDispatcher + sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD + body:@{ + @"taskId": taskId, + @"written": [NSString stringWithFormat:@"%ld", (long) totalBytesWritten], + @"total": [NSString stringWithFormat:@"%ld", (long) totalBytesExpectedToWrite] + } + ]; + } +} + + +- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler +{ + if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) { + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } else { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } +} + + +- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session +{ + NSLog(@"sess done in background"); +} + +- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler +{ + + if (followRedirect) { + if (request.URL) { + [redirects addObject:[request.URL absoluteString]]; + } + + completionHandler(request); + } else { + completionHandler(nil); + } +} + + +@end