Nessuna descrizione

RNFetchBlob.m 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. //
  2. // RNFetchBlob.m
  3. //
  4. // Created by wkh237 on 2016/4/28.
  5. //
  6. #import "RNFetchBlob.h"
  7. #import "RCTConvert.h"
  8. #import "RCTLog.h"
  9. #import <Foundation/Foundation.h>
  10. #import "RCTBridge.h"
  11. #import "RCTEventDispatcher.h"
  12. NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
  13. // fetch configs
  14. NSString *const CONFIG_USE_TEMP = @"fileCache";
  15. NSString *const CONFIG_FILE_PATH = @"path";
  16. NSString *const CONFIG_FILE_EXT = @"appendExt";
  17. NSString *const MSG_EVENT = @"RNFetchBlobMessage";
  18. NSString *const MSG_EVENT_LOG = @"log";
  19. NSString *const MSG_EVENT_WARN = @"warn";
  20. NSString *const MSG_EVENT_ERROR = @"error";
  21. NSString *const FS_EVENT_DATA = @"data";
  22. NSString *const FS_EVENT_END = @"end";
  23. NSString *const FS_EVENT_WARN = @"warn";
  24. NSString *const FS_EVENT_ERROR = @"error";
  25. ////////////////////////////////////////
  26. //
  27. // File system access methods
  28. //
  29. ////////////////////////////////////////
  30. @implementation FetchBlobFS
  31. @synthesize outStream;
  32. @synthesize inStream;
  33. @synthesize encoding;
  34. @synthesize callback;
  35. @synthesize taskId;
  36. @synthesize path;
  37. @synthesize appendData;
  38. @synthesize bufferSize;
  39. // static member getter
  40. + (NSArray *) getFileStreams {
  41. static NSMutableData *fileStreams = nil;
  42. if(fileStreams == nil)
  43. fileStreams = [[NSArray alloc] init];
  44. return fileStreams;
  45. }
  46. + (NSString *) getCacheDir {
  47. return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  48. }
  49. + (NSString *) getDocumentDir {
  50. return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  51. }
  52. + (NSString *) getMusicDir {
  53. return [NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES) firstObject];
  54. }
  55. + (NSString *) getMovieDir {
  56. return [NSSearchPathForDirectoriesInDomains(NSMoviesDirectory, NSUserDomainMask, YES) firstObject];
  57. }
  58. + (NSString *) getPictureDir {
  59. return [NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES) firstObject];
  60. }
  61. + (NSString *) getTempPath {
  62. return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingString:@"/RNFetchBlob_tmp"];
  63. }
  64. + (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext {
  65. NSString * documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  66. NSString * filename = [NSString stringWithFormat:@"/RNFetchBlob_tmp/RNFetchBlobTmp_%@", taskId];
  67. if(ext != nil)
  68. filename = [filename stringByAppendingString: [NSString stringWithFormat:@".%@", ext]];
  69. NSString * tempPath = [documentDir stringByAppendingString: filename];
  70. return tempPath;
  71. }
  72. + (BOOL) mkdir:(NSString *) path {
  73. BOOL isDir;
  74. NSError * err = nil;
  75. // if temp folder not exists, create one
  76. if(![[NSFileManager defaultManager] fileExistsAtPath: path isDirectory:&isDir]) {
  77. [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
  78. }
  79. return err == nil;
  80. }
  81. + (BOOL) exists:(NSString *) path {
  82. return [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:NULL];
  83. }
  84. - (id)init {
  85. self = [super init];
  86. return self;
  87. }
  88. - (id)initWithCallback:(RCTResponseSenderBlock)callback {
  89. self = [super init];
  90. self.callback = callback;
  91. return self;
  92. }
  93. - (id)initWithBridgeRef:(RCTBridge *)bridgeRef {
  94. self = [super init];
  95. self.bridge = bridgeRef;
  96. return self;
  97. }
  98. // Create file stream for write data
  99. - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)encode appendData:(BOOL)append {
  100. self.outStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:append];
  101. self.encoding = encode;
  102. [self.outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  103. [self.outStream open];
  104. NSString *uuid = [[NSUUID UUID] UUIDString];
  105. self.streamId = uuid;
  106. [[FetchBlobFS getFileStreams] setValue:self forKey:uuid];
  107. return uuid;
  108. }
  109. // Write file chunk into an opened stream
  110. - (void)writeEncodeChunk:(NSString *) chunk {
  111. NSMutableData * decodedData = [NSData alloc];
  112. if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
  113. decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
  114. }
  115. if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
  116. decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
  117. }
  118. else if([[self.encoding lowercaseString] isEqualToString:@"ascii"]) {
  119. decodedData = [chunk dataUsingEncoding:NSASCIIStringEncoding];
  120. }
  121. NSUInteger left = [chunk length];
  122. NSUInteger nwr = 0;
  123. do {
  124. nwr = [self.outStream write:[decodedData bytes] maxLength:left];
  125. if (-1 == nwr) break;
  126. left -= nwr;
  127. } while (left > 0);
  128. if (left) {
  129. NSLog(@"stream error: %@", [self.outStream streamError]);
  130. }
  131. }
  132. // Write file chunk into an opened stream
  133. - (void)write:(NSData *) chunk {
  134. NSUInteger left = [chunk length];
  135. NSUInteger nwr = 0;
  136. do {
  137. nwr = [self.outStream write:[chunk bytes] maxLength:left];
  138. if (-1 == nwr) break;
  139. left -= nwr;
  140. } while (left > 0);
  141. if (left) {
  142. NSLog(@"stream error: %@", [self.outStream streamError]);
  143. }
  144. }
  145. - (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding bufferSize:(int) bufferSize{
  146. self.inStream = [[NSInputStream alloc] initWithFileAtPath:path];
  147. self.inStream.delegate = self;
  148. self.encoding = encoding;
  149. self.path = path;
  150. self.bufferSize = bufferSize;
  151. // NSStream needs a runloop so let's create a run loop for it
  152. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
  153. // start NSStream is a runloop
  154. dispatch_async(queue, ^ {
  155. [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
  156. forMode:NSDefaultRunLoopMode];
  157. [inStream open];
  158. [[NSRunLoop currentRunLoop] run];
  159. });
  160. }
  161. // close file write stream
  162. - (void)closeOutStream {
  163. if(self.outStream != nil) {
  164. [self.outStream close];
  165. self.outStream = nil;
  166. }
  167. }
  168. // close file read stream
  169. - (void)closeInStream {
  170. if(self.inStream != nil) {
  171. [self.inStream close];
  172. [self.inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  173. [[FetchBlobFS getFileStreams] setValue:nil forKey:self.streamId];
  174. self.streamId = nil;
  175. }
  176. }
  177. void runOnMainQueueWithoutDeadlocking(void (^block)(void))
  178. {
  179. if ([NSThread isMainThread])
  180. {
  181. block();
  182. }
  183. else
  184. {
  185. dispatch_sync(dispatch_get_main_queue(), block);
  186. }
  187. }
  188. #pragma mark RNFetchBlobFS read stream delegate
  189. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
  190. NSString * streamEventCode = [NSString stringWithFormat:@"RNFetchBlobStream+%@", self.path];
  191. switch(eventCode) {
  192. // write stream event
  193. case NSStreamEventHasSpaceAvailable:
  194. {
  195. }
  196. // read stream incoming chunk
  197. case NSStreamEventHasBytesAvailable:
  198. {
  199. NSMutableData * chunkData = [[NSMutableData data] init];
  200. NSInteger chunkSize = 4096;
  201. if([[self.encoding lowercaseString] isEqualToString:@"base64"])
  202. chunkSize = 4098;
  203. if(self.bufferSize > 0)
  204. chunkSize = self.bufferSize;
  205. uint8_t buf[chunkSize];
  206. unsigned int len = 0;
  207. len = [(NSInputStream *)stream read:buf maxLength:chunkSize];
  208. // still have data in stream
  209. if(len) {
  210. [chunkData appendBytes:(const void *)buf length:len];
  211. // TODO : file read progress ?
  212. // dispatch data event
  213. NSString * encodedChunk;
  214. if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
  215. encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
  216. }
  217. else if ( [[self.encoding lowercaseString] isEqualToString:@"ascii"] ) {
  218. encodedChunk = [encodedChunk initWithData:chunkData encoding:NSASCIIStringEncoding];
  219. }
  220. else if ( [[self.encoding lowercaseString] isEqualToString:@"base64"] ) {
  221. encodedChunk = [chunkData base64EncodedStringWithOptions:0];
  222. }
  223. else {
  224. [self.bridge.eventDispatcher
  225. sendDeviceEventWithName:streamEventCode
  226. body:@{
  227. @"event": FS_EVENT_ERROR,
  228. @"detail": @"unrecognized encoding"
  229. }
  230. ];
  231. return;
  232. }
  233. runOnMainQueueWithoutDeadlocking(^{
  234. [self.bridge.eventDispatcher
  235. sendDeviceEventWithName:streamEventCode
  236. body:@{
  237. @"event": FS_EVENT_DATA,
  238. @"detail": encodedChunk
  239. }
  240. ];
  241. });
  242. }
  243. // end of stream
  244. else {
  245. [self.bridge.eventDispatcher
  246. sendDeviceEventWithName:streamEventCode
  247. body:@{
  248. @"event": FS_EVENT_END,
  249. @"detail": @""
  250. }
  251. ];
  252. }
  253. break;
  254. }
  255. // stream error
  256. case NSStreamEventErrorOccurred:
  257. {
  258. [self.bridge.eventDispatcher
  259. sendDeviceEventWithName:streamEventCode
  260. body:@{
  261. @"event": FS_EVENT_ERROR,
  262. @"detail": @"RNFetchBlob error when read file with stream, file may not exists"
  263. }
  264. ];
  265. break;
  266. }
  267. }
  268. }
  269. @end
  270. ////////////////////////////////////////
  271. //
  272. // HTTP request handler
  273. //
  274. ////////////////////////////////////////
  275. @implementation FetchBlobUtils
  276. @synthesize taskId;
  277. @synthesize expectedBytes;
  278. @synthesize receivedBytes;
  279. @synthesize respData;
  280. @synthesize callback;
  281. @synthesize bridge;
  282. @synthesize options;
  283. // removing case from headers
  284. + (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers {
  285. NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
  286. for(NSString * key in headers) {
  287. [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
  288. }
  289. return mheaders;
  290. }
  291. - (id)init {
  292. self = [super init];
  293. return self;
  294. }
  295. - (void) sendRequest:(NSDictionary *)options bridge:(RCTBridge *)bridgeRef taskId:(NSString *)taskId withRequest:(NSURLRequest *)req callback:(RCTResponseSenderBlock) callback {
  296. self.taskId = taskId;
  297. self.respData = [[NSMutableData alloc] initWithLength:0];
  298. self.callback = callback;
  299. self.bridge = bridgeRef;
  300. self.expectedBytes = 0;
  301. self.receivedBytes = 0;
  302. self.options = options;
  303. NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
  304. NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
  305. // open file stream for write
  306. if( path != nil) {
  307. self.fileStream = [[FetchBlobFS alloc]initWithCallback:self.callback];
  308. [self.fileStream openWithPath:path encode:@"ascii" appendData:YES ];
  309. }
  310. else if ( [self.options valueForKey:CONFIG_USE_TEMP]!= nil ) {
  311. self.fileStream = [[FetchBlobFS alloc]initWithCallback:self.callback];
  312. [self.fileStream openWithPath:[FetchBlobFS getTempPath:taskId withExtension:ext] encode:@"ascii" appendData:YES];
  313. }
  314. NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:NO];
  315. [conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  316. [conn start];
  317. if(!conn) {
  318. callback(@[[NSString stringWithFormat:@"RNFetchBlob could not initialize connection"], [NSNull null]]);
  319. }
  320. }
  321. #pragma mark NSURLConnection delegate methods
  322. - (void) connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response {
  323. // [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  324. expectedBytes = [response expectedContentLength];
  325. }
  326. - (void) connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data {
  327. receivedBytes += [data length];
  328. Boolean fileCache = [self.options valueForKey:CONFIG_USE_TEMP];
  329. NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
  330. if(path != nil) {
  331. [self.fileStream write:data];
  332. }
  333. // write to tmp file
  334. else if( fileCache != nil) {
  335. NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
  336. [self.fileStream write:data];
  337. }
  338. // cache data in memory
  339. else {
  340. [respData appendData:data];
  341. }
  342. [self.bridge.eventDispatcher
  343. sendDeviceEventWithName:@"RNFetchBlobProgress"
  344. body:@{
  345. @"taskId": taskId,
  346. @"written": [NSString stringWithFormat:@"%d", receivedBytes],
  347. @"total": [NSString stringWithFormat:@"%d", expectedBytes]
  348. }
  349. ];
  350. }
  351. - (void) connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  352. expectedBytes = totalBytesExpectedToWrite;
  353. receivedBytes += totalBytesWritten;
  354. [self.bridge.eventDispatcher
  355. sendDeviceEventWithName:@"RNFetchBlobProgress"
  356. body:@{
  357. @"taskId": taskId,
  358. @"written": [NSString stringWithFormat:@"%d", receivedBytes],
  359. @"total": [NSString stringWithFormat:@"%d", expectedBytes]
  360. }
  361. ];
  362. }
  363. - (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  364. // [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  365. [self.fileStream closeInStream];
  366. [self.fileStream closeOutStream];
  367. callback(@[[error localizedDescription], [NSNull null]]);
  368. }
  369. - (NSCachedURLResponse *) connection:(NSURLConnection *)connection willCacheResponse: (NSCachedURLResponse *)cachedResponse {
  370. return nil;
  371. }
  372. // handle 301 and 302 responses
  373. - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:response {
  374. return request;
  375. }
  376. // request complete
  377. - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
  378. NSData * data;
  379. if(respData != nil)
  380. data = [NSData dataWithData:respData];
  381. else
  382. data = [[NSData alloc] init];
  383. NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
  384. NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
  385. Boolean useCache = [self.options valueForKey:CONFIG_USE_TEMP];
  386. [self.fileStream closeInStream];
  387. // if fileCache is true or file path is given, return a path
  388. if( path != nil ) {
  389. callback(@[[NSNull null], path]);
  390. }
  391. // when fileCache option is set but no path specified, save to tmp path
  392. else if( [self.options valueForKey:CONFIG_USE_TEMP] != nil) {
  393. NSString * tmpPath = [FetchBlobFS getTempPath:taskId withExtension:ext];
  394. callback(@[[NSNull null], tmpPath]);
  395. }
  396. // otherwise return base64 string
  397. else {
  398. callback(@[[NSNull null], [data base64EncodedStringWithOptions:0]]);
  399. }
  400. }
  401. @end
  402. ////////////////////////////////////////
  403. //
  404. // Exported native methods
  405. //
  406. ////////////////////////////////////////
  407. #pragma mark RNFetchBlob exported methods
  408. @implementation RNFetchBlob
  409. @synthesize filePathPrefix;
  410. @synthesize bridge = _bridge;
  411. - (dispatch_queue_t) methodQueue {
  412. return dispatch_queue_create("RNFetchBlob.queue", DISPATCH_QUEUE_SERIAL);
  413. }
  414. RCT_EXPORT_MODULE();
  415. - (id) init {
  416. self = [super init];
  417. self.filePathPrefix = FILE_PREFIX;
  418. BOOL isDir;
  419. // if temp folder not exists, create one
  420. if(![[NSFileManager defaultManager] fileExistsAtPath: [FetchBlobFS getTempPath] isDirectory:&isDir]) {
  421. [[NSFileManager defaultManager] createDirectoryAtPath:[FetchBlobFS getTempPath] withIntermediateDirectories:YES attributes:nil error:NULL];
  422. }
  423. return self;
  424. }
  425. // Fetch blob data request
  426. RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options
  427. taskId:(NSString *)taskId
  428. method:(NSString *)method
  429. url:(NSString *)url
  430. headers:(NSDictionary *)headers
  431. form:(NSArray *)form
  432. callback:(RCTResponseSenderBlock)callback)
  433. {
  434. // send request
  435. NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
  436. initWithURL:[NSURL
  437. URLWithString: url]];
  438. NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[ FetchBlobUtils normalizeHeaders:headers]];
  439. NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
  440. NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
  441. // generate boundary
  442. NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
  443. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  444. // if method is POST or PUT, convert data string format
  445. if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
  446. NSMutableData * postData = [[NSMutableData alloc] init];
  447. // combine multipart/form-data body
  448. for(id field in form) {
  449. NSString * name = [field valueForKey:@"name"];
  450. NSString * content = [field valueForKey:@"data"];
  451. // field is a text field
  452. if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {
  453. [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  454. [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]];
  455. [postData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  456. [postData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]];
  457. }
  458. // field contains a file
  459. else {
  460. NSMutableData * blobData;
  461. if(content != nil) {
  462. if([content hasPrefix:self.filePathPrefix]) {
  463. NSString * orgPath = [content substringFromIndex:[self.filePathPrefix length]];
  464. blobData = [[NSData alloc] initWithContentsOfFile:orgPath];
  465. }
  466. else
  467. blobData = [[NSData alloc] initWithBase64EncodedString:content options:0];
  468. }
  469. NSString * filename = [field valueForKey:@"filename"];
  470. [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  471. [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
  472. [postData appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  473. [postData appendData:blobData];
  474. [postData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  475. }
  476. }
  477. // close form data
  478. [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  479. [request setHTTPBody:postData];
  480. // set content-length
  481. [mheaders setValue:[NSString stringWithFormat:@"%d",[postData length]] forKey:@"Content-Length"];
  482. [mheaders setValue:[NSString stringWithFormat:@"100-continue",[postData length]] forKey:@"Expect"];
  483. // appaned boundary to content-type
  484. [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"];
  485. }
  486. [request setHTTPMethod: method];
  487. [request setAllHTTPHeaderFields:mheaders];
  488. // send HTTP request
  489. FetchBlobUtils * utils = [[FetchBlobUtils alloc] init];
  490. [utils sendRequest:options bridge:self.bridge taskId:taskId withRequest:request callback:callback];
  491. });
  492. }
  493. // Fetch blob data request
  494. RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
  495. taskId:(NSString *)taskId
  496. method:(NSString *)method
  497. url:(NSString *)url
  498. headers:(NSDictionary *)headers
  499. body:(NSString *)body callback:(RCTResponseSenderBlock)callback)
  500. {
  501. // send request
  502. NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
  503. initWithURL:[NSURL
  504. URLWithString: url]];
  505. NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[FetchBlobUtils normalizeHeaders:headers]];
  506. // move heavy task to another thread
  507. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  508. // if method is POST or PUT, convert data string format
  509. if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
  510. // generate octet-stream body
  511. if(body != nil) {
  512. NSMutableData * blobData;
  513. // when body is a string contains file path prefix, try load file from the path
  514. if([body hasPrefix:self.filePathPrefix]) {
  515. NSString * orgPath = [body substringFromIndex:[self.filePathPrefix length]];
  516. blobData = [[NSData alloc] initWithContentsOfFile:orgPath];
  517. }
  518. // otherwise convert it as BASE64 data string
  519. else
  520. blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
  521. [request setHTTPBody:blobData];
  522. [mheaders setValue:@"application/octet-stream" forKey:@"content-type"];
  523. }
  524. }
  525. [request setHTTPMethod: method];
  526. [request setAllHTTPHeaderFields:mheaders];
  527. // send HTTP request
  528. FetchBlobUtils * utils = [[FetchBlobUtils alloc] init];
  529. [utils sendRequest:options bridge:self.bridge taskId:taskId withRequest:request callback:callback];
  530. });
  531. }
  532. RCT_EXPORT_METHOD(exists:(NSString *)path callback:(RCTResponseSenderBlock)callback) {
  533. BOOL isDir = NO;
  534. BOOL exists = NO;
  535. exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
  536. callback(@[@(exists), @(isDir)]);
  537. }
  538. RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize) {
  539. FetchBlobFS *fileStream = [[FetchBlobFS alloc] initWithBridgeRef:self.bridge];
  540. [fileStream readWithPath:path useEncoding:encoding bufferSize:bufferSize];
  541. }
  542. RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding appendData:(BOOL)append callback:(RCTResponseSenderBlock)callback) {
  543. FetchBlobFS * fileStream = [[FetchBlobFS alloc] initWithBridgeRef:self.bridge];
  544. NSFileManager * fm = [NSFileManager defaultManager];
  545. BOOL isDir = nil;
  546. BOOL exist = ![fm fileExistsAtPath:path isDirectory:&isDir];
  547. if( exist == NO || isDir == YES) {
  548. callback(@[[NSString stringWithFormat:@"target path `%@` may not exists or it's a folder", path]]);
  549. return;
  550. }
  551. NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append];
  552. callback(@[[NSNull null], streamId]);
  553. }
  554. RCT_EXPORT_METHOD(writeChunk:(NSString *)streamId withData:(NSString *)data callback:(RCTResponseSenderBlock) callback) {
  555. FetchBlobFS *fs = [[FetchBlobFS getFileStreams] valueForKey:streamId];
  556. [fs writeEncodeChunk:data];
  557. callback(@[[NSNull null]]);
  558. }
  559. RCT_EXPORT_METHOD(closeStream:(NSString *)streamId callback:(RCTResponseSenderBlock) callback) {
  560. FetchBlobFS *fs = [[FetchBlobFS getFileStreams] valueForKey:streamId];
  561. [fs closeOutStream];
  562. callback(@[[NSNull null], @YES]);
  563. }
  564. RCT_EXPORT_METHOD(unlink:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
  565. NSError * error = nil;
  566. NSString * tmpPath = nil;
  567. [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
  568. if(error == nil)
  569. callback(@[[NSNull null]]);
  570. else
  571. callback(@[[NSString stringWithFormat:@"failed to unlink file or path at %@", path]]);
  572. }
  573. RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBlock) callback) {
  574. NSError * error = nil;
  575. NSString * tmpPath = nil;
  576. for(NSString * path in paths) {
  577. [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
  578. if(error != nil) {
  579. callback(@[[NSString stringWithFormat:@"failed to remove session path at %@", path]]);
  580. return;
  581. }
  582. }
  583. callback(@[[NSNull null]]);
  584. }
  585. RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
  586. NSError * error = nil;
  587. NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
  588. if(error == nil)
  589. callback(@[[NSNull null], result == nil ? [NSNull null] :result ]);
  590. else
  591. callback(@[[error localizedDescription], [NSNull null]]);
  592. }
  593. RCT_EXPORT_METHOD(cp:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
  594. NSError * error = nil;
  595. BOOL result = [[NSFileManager defaultManager] copyItemAtURL:path toURL:dest error:&error];
  596. if(error == nil)
  597. callback(@[[NSNull null], @YES]);
  598. else
  599. callback(@[[error localizedDescription], @NO]);
  600. }
  601. RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
  602. NSError * error = nil;
  603. BOOL result = [[NSFileManager defaultManager] moveItemAtURL:path toURL:dest error:&error];
  604. if(error == nil)
  605. callback(@[[NSNull null], @YES]);
  606. else
  607. callback(@[[error localizedDescription], @NO]);
  608. }
  609. RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
  610. if([FetchBlobFS exists:path])
  611. callback(@[@"file path exists"]);
  612. else
  613. [FetchBlobFS mkdir:path];
  614. callback(@[[NSNull null]]);
  615. }
  616. RCT_EXPORT_METHOD(getEnvironmentDirs:(RCTResponseSenderBlock) callback) {
  617. callback(@[
  618. [FetchBlobFS getDocumentDir],
  619. [FetchBlobFS getCacheDir],
  620. ]);
  621. }
  622. #pragma mark RNFetchBlob private methods
  623. @end