Aucune description

RNFetchBlob.m 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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. // lib event
  13. NSString *const MSG_EVENT = @"RNFetchBlobMessage";
  14. NSString *const MSG_EVENT_LOG = @"log";
  15. NSString *const MSG_EVENT_WARN = @"warn";
  16. NSString *const MSG_EVENT_ERROR = @"error";
  17. NSString *const CONFIG_USE_TEMP = @"fileCache";
  18. NSString *const CONFIG_FILE_PATH = @"path";
  19. NSString *const FS_EVENT_DATA = @"data";
  20. NSString *const FS_EVENT_END = @"end";
  21. NSString *const FS_EVENT_WARN = @"warn";
  22. NSString *const FS_EVENT_ERROR = @"error";
  23. ////////////////////////////////////////
  24. //
  25. // File system access methods
  26. //
  27. ////////////////////////////////////////
  28. @implementation FetchBlobFS
  29. @synthesize outStream;
  30. @synthesize inStream;
  31. @synthesize encoding;
  32. @synthesize callback;
  33. @synthesize taskId;
  34. @synthesize path;
  35. + (NSString *) getCacheDir {
  36. return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  37. }
  38. + (NSString *) getDocumentDir {
  39. return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  40. }
  41. + (NSString *) getMusicDir {
  42. return NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES);
  43. }
  44. + (NSString *) getMovieDir {
  45. return NSSearchPathForDirectoriesInDomains(NSMoviesDirectory, NSUserDomainMask, YES);
  46. }
  47. + (NSString *) getPictureDir {
  48. return NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES);
  49. }
  50. + (NSString *) getTempPath:(NSString*)taskId {
  51. NSString * documentDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  52. NSString * filename = [NSString stringWithFormat:@"RNFetchBlobTmp_%s", taskId];
  53. NSString * tempPath = [documentDir stringByAppendingString: filename];
  54. return tempPath;
  55. }
  56. - (id)initWithCallback:(RCTResponseSenderBlock)callback {
  57. self = [super init];
  58. self.callback = callback;
  59. return self;
  60. }
  61. - (id)initWithBridgeRef:(RCTBridge *)bridgeRef {
  62. self = [super init];
  63. self.callback = callback;
  64. return self;
  65. }
  66. - (void)openWithPath:(NSString *)destPath {
  67. self.outStream = [[NSOutputStream alloc]init];
  68. [self.outStream initToFileAtPath:destPath append:NO];
  69. }
  70. - (void)openWithId:(NSString *)taskId {
  71. NSString * tmpPath = [[self class ]getTempPath: taskId];
  72. // create a file stream
  73. [self openWithPath:tmpPath];
  74. }
  75. // Write file chunk into an opened stream
  76. - (void)write:(NSString *) chunk {
  77. [self.outStream write:[chunk cStringUsingEncoding:NSASCIIStringEncoding] maxLength:chunk.length];
  78. }
  79. - (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding {
  80. self.inStream = [[NSInputStream alloc]init];
  81. self.encoding = encoding;
  82. [self.inStream setDelegate:self];
  83. }
  84. - (void)readWithTaskId:(NSString *)taskId withPath:(NSString *)path useEncoding:(NSString *)encoding {
  85. self.taskId = taskId;
  86. self.path = path;
  87. if(path == nil)
  88. [self readWithPath:[[self class]getTempPath:taskId] useEncoding:encoding];
  89. else
  90. [self readWithPath:path useEncoding:encoding];
  91. }
  92. // close file stream
  93. - (void)closeOutStream {
  94. if(self.outStream != nil) {
  95. [self.outStream close];
  96. self.outStream = nil;
  97. }
  98. }
  99. - (void)closeInStream {
  100. if(self.inStream != nil) {
  101. [self.inStream close];
  102. [self.inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  103. }
  104. }
  105. #pragma mark RNFetchBlobFS read stream delegate
  106. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
  107. switch(eventCode) {
  108. case NSStreamEventHasBytesAvailable:
  109. {
  110. NSMutableData * chunkData = [[NSMutableData data] init];
  111. uint8_t buf[1024];
  112. unsigned int len = 0;
  113. len = [(NSInputStream *)stream read:buf maxLength:1024];
  114. // still have data in stream
  115. if(len) {
  116. [chunkData appendBytes:(const void *)buf length:len];
  117. // TODO : file read progress ?
  118. // [bytesRead setIntValue:[bytesRead intValue]+len];
  119. // dispatch data event
  120. NSString * encodedChunk = [NSString alloc];
  121. if( [self.encoding caseInsensitiveCompare:@"utf8"] ) {
  122. encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
  123. }
  124. else if ( [self.encoding caseInsensitiveCompare:@"ascii"] ) {
  125. encodedChunk = [encodedChunk initWithData:chunkData encoding:NSASCIIStringEncoding];
  126. }
  127. else if ( [self.encoding caseInsensitiveCompare:@"base64"] ) {
  128. encodedChunk = [chunkData base64EncodedStringWithOptions:0];
  129. }
  130. else {
  131. [self.bridge.eventDispatcher
  132. sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
  133. body:@{
  134. @"event": FS_EVENT_ERROR,
  135. @"detail": @"unrecognized encoding"
  136. }
  137. ];
  138. return;
  139. }
  140. [self.bridge.eventDispatcher
  141. sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
  142. body:@{
  143. @"event": FS_EVENT_DATA,
  144. @"detail": encodedChunk
  145. }
  146. ];
  147. }
  148. // end of stream
  149. else {
  150. [self.bridge.eventDispatcher
  151. sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
  152. body:@{
  153. @"event": FS_EVENT_END,
  154. @"detail": @""
  155. }
  156. ];
  157. }
  158. break;
  159. }
  160. case NSStreamEventErrorOccurred:
  161. {
  162. [self.bridge.eventDispatcher
  163. sendAppEventWithName: [NSString stringWithFormat:@"RNFetchBlobStream%s", self.taskId]
  164. body:@{
  165. @"event": FS_EVENT_ERROR,
  166. @"detail": @"error when read file with stream"
  167. }
  168. ];
  169. break;
  170. }
  171. }
  172. }
  173. @end
  174. ////////////////////////////////////////
  175. //
  176. // HTTP request handler
  177. //
  178. ////////////////////////////////////////
  179. @implementation FetchBlobUtils
  180. @synthesize taskId;
  181. @synthesize expectedBytes;
  182. @synthesize receivedBytes;
  183. @synthesize respData;
  184. @synthesize callback;
  185. @synthesize bridge;
  186. @synthesize options;
  187. // removing case from headers
  188. + (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers {
  189. NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
  190. for(NSString * key in headers) {
  191. [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
  192. }
  193. return mheaders;
  194. }
  195. - (id)init {
  196. self = [super init];
  197. return self;
  198. }
  199. - (void) sendRequest:(NSDictionary *)options bridge:(RCTBridge *)bridgeRef taskId:(NSString *)taskId withRequest:(NSURLRequest *)req callback:(RCTResponseSenderBlock) callback {
  200. self.taskId = taskId;
  201. self.respData = [[NSMutableData alloc] initWithLength:0];
  202. self.callback = callback;
  203. self.bridge = bridgeRef;
  204. self.expectedBytes = 0;
  205. self.receivedBytes = 0;
  206. self.options = options;
  207. NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
  208. // open file stream for write
  209. if( path != nil) {
  210. self.fileStream = [[FetchBlobFS alloc]initWithCallback:self.callback];
  211. [self.fileStream openWithPath:path];
  212. }
  213. else if ( [self.options valueForKey:CONFIG_USE_TEMP] == YES ) {
  214. self.fileStream = [[FetchBlobFS alloc]initWithCallback:self.callback];
  215. [self.fileStream openWithId:taskId];
  216. }
  217. NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:NO];
  218. [conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  219. [conn start];
  220. if(!conn) {
  221. callback(@[[NSString stringWithFormat:@"RNFetchBlob could not initialize connection"], [NSNull null]]);
  222. }
  223. }
  224. #pragma mark NSURLConnection delegate methods
  225. - (void) connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response {
  226. [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  227. expectedBytes = [response expectedContentLength];
  228. }
  229. - (void) connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data {
  230. receivedBytes += data.length;
  231. Boolean fileCache = [self.options valueForKey:CONFIG_USE_TEMP];
  232. NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
  233. // write to tmp file
  234. if( fileCache == YES || path != nil ) {
  235. [self.fileStream write:data];
  236. }
  237. // cache data in memory
  238. else {
  239. [respData appendData:data];
  240. }
  241. [self.bridge.eventDispatcher
  242. sendAppEventWithName:@"RNFetchBlobProgress"
  243. body:@{
  244. @"taskId": taskId,
  245. @"written": [NSString stringWithFormat:@"%d", receivedBytes],
  246. @"total": [NSString stringWithFormat:@"%d", expectedBytes]
  247. }
  248. ];
  249. }
  250. - (void) connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  251. expectedBytes = totalBytesExpectedToWrite;
  252. receivedBytes += totalBytesWritten;
  253. [self.bridge.eventDispatcher
  254. sendAppEventWithName:@"RNFetchBlobProgress"
  255. body:@{
  256. @"taskId": taskId,
  257. @"written": [NSString stringWithFormat:@"%d", receivedBytes],
  258. @"total": [NSString stringWithFormat:@"%d", expectedBytes]
  259. }
  260. ];
  261. }
  262. - (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  263. [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  264. [self.fileStream closeInStream];
  265. [self.fileStream closeOutStream];
  266. callback(@[[error localizedDescription], [NSNull null]]);
  267. }
  268. - (NSCachedURLResponse *) connection:(NSURLConnection *)connection willCacheResponse: (NSCachedURLResponse *)cachedResponse {
  269. return nil;
  270. }
  271. // handle 301 and 302 responses
  272. - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:response {
  273. return request;
  274. }
  275. // request complete
  276. - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
  277. NSData * data;
  278. if(respData != nil)
  279. data = [NSData dataWithData:respData];
  280. else
  281. data = [[NSData alloc] init];
  282. NSString * path = [NSString stringWithString:[self.options valueForKey:CONFIG_FILE_PATH]];
  283. [self.fileStream closeInStream];
  284. // if fileCache is true or file path is given, return a path
  285. if( path != nil ) {
  286. callback(@[[NSNull null], path]);
  287. }
  288. // when fileCache option is set but no path specified, save to tmp path
  289. else if( [self.options valueForKey:CONFIG_USE_TEMP] == YES || path != nil ) {
  290. NSString * tmpPath = [FetchBlobFS getTempPath:taskId];
  291. callback(@[[NSNull null], tmpPath]);
  292. }
  293. // otherwise return base64 string
  294. else {
  295. callback(@[[NSNull null], [data base64EncodedStringWithOptions:0]]);
  296. }
  297. }
  298. @end
  299. ////////////////////////////////////////
  300. //
  301. // Exported native methods
  302. //
  303. ////////////////////////////////////////
  304. #pragma mark RNFetchBlob exported methods
  305. @implementation RNFetchBlob
  306. @synthesize bridge = _bridge;
  307. RCT_EXPORT_MODULE();
  308. // Fetch blob data request
  309. RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options taskId:(NSString *)taskId method:(NSString *)method url:(NSString *)url headers:(NSDictionary *)headers form:(NSArray *)form callback:(RCTResponseSenderBlock)callback)
  310. {
  311. // send request
  312. NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
  313. initWithURL:[NSURL
  314. URLWithString: url]];
  315. NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[ FetchBlobUtils normalizeHeaders:headers]];
  316. NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
  317. NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
  318. // generate boundary
  319. NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
  320. // if method is POST or PUT, convert data string format
  321. if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
  322. NSMutableData * postData = [[NSMutableData alloc] init];
  323. // combine multipart/form-data body
  324. for(id field in form) {
  325. NSString * name = [field valueForKey:@"name"];
  326. NSString * content = [field valueForKey:@"data"];
  327. // field is a text field
  328. if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {
  329. [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  330. [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]];
  331. [postData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  332. [postData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]];
  333. }
  334. // field contains a file
  335. else {
  336. NSData* blobData = [[NSData alloc] initWithBase64EncodedString:content options:0];
  337. NSString * filename = [field valueForKey:@"filename"];
  338. [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  339. [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
  340. [postData appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  341. [postData appendData:blobData];
  342. [postData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  343. }
  344. }
  345. // close form data
  346. [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  347. [request setHTTPBody:postData];
  348. // set content-length
  349. [mheaders setValue:[NSString stringWithFormat:@"%d",[postData length]] forKey:@"Content-Length"];
  350. [mheaders setValue:[NSString stringWithFormat:@"100-continue",[postData length]] forKey:@"Expect"];
  351. // appaned boundary to content-type
  352. [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"];
  353. }
  354. [request setHTTPMethod: method];
  355. [request setAllHTTPHeaderFields:mheaders];
  356. // send HTTP request
  357. FetchBlobUtils * utils = [[FetchBlobUtils alloc] init];
  358. [utils sendRequest:options bridge:self.bridge taskId:taskId withRequest:request callback:callback];
  359. }
  360. // Fetch blob data request
  361. RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options taskId:(NSString *)taskId method:(NSString *)method url:(NSString *)url headers:(NSDictionary *)headers body:(NSString *)body callback:(RCTResponseSenderBlock)callback)
  362. {
  363. // send request
  364. NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
  365. initWithURL:[NSURL
  366. URLWithString: url]];
  367. NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[FetchBlobUtils normalizeHeaders:headers]];
  368. // if method is POST or PUT, convert data string format
  369. if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
  370. if(body != nil) {
  371. // generate octet-stream body
  372. NSData* blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
  373. NSMutableData* postBody = [[NSMutableData alloc] init];
  374. [postBody appendData:[NSData dataWithData:blobData]];
  375. [request setHTTPBody:postBody];
  376. [mheaders setValue:@"application/octet-stream" forKey:@"content-type"];
  377. }
  378. }
  379. [request setHTTPMethod: method];
  380. [request setAllHTTPHeaderFields:mheaders];
  381. // send HTTP request
  382. FetchBlobUtils * utils = [[FetchBlobUtils alloc] init];
  383. [utils sendRequest:options bridge:self.bridge taskId:taskId withRequest:request callback:callback];
  384. }
  385. RCT_EXPORT_METHOD(readStream:(NSString *)taskId withPath:(NSString *)path withEncoding:(NSString *)encoding) {
  386. FetchBlobFS *fileStream = [[FetchBlobFS alloc] initWithBridgeRef:self.bridge];
  387. [fileStream readWithTaskId:taskId withPath:path useEncoding:encoding];
  388. }
  389. RCT_EXPORT_METHOD(flush:(NSString *)taskId withPath:(NSString *)path) {
  390. NSError * error = nil;
  391. NSString * tmpPath = nil;
  392. if(path != nil)
  393. tmpPath = path;
  394. else
  395. tmpPath = [FetchBlobFS getTempPath:taskId];
  396. [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
  397. }
  398. @end