No Description

RNFetchBlobFS.m 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. //
  2. // RNFetchBlobFS.m
  3. // RNFetchBlob
  4. //
  5. // Created by Ben Hsieh on 2016/6/6.
  6. // Copyright © 2016年 suzuri04x2. All rights reserved.
  7. //
  8. #import "RCTConvert.h"
  9. #import "RCTLog.h"
  10. #import <Foundation/Foundation.h>
  11. #import "RCTBridge.h"
  12. #import "RCTEventDispatcher.h"
  13. #import "RNFetchBlobFS.h"
  14. #import "RNFetchBlobConst.h"
  15. NSMutableDictionary *fileStreams = nil;
  16. ////////////////////////////////////////
  17. //
  18. // File system access methods
  19. //
  20. ////////////////////////////////////////
  21. @implementation RNFetchBlobFS
  22. @synthesize outStream;
  23. @synthesize inStream;
  24. @synthesize encoding;
  25. @synthesize callback;
  26. @synthesize taskId;
  27. @synthesize path;
  28. @synthesize appendData;
  29. @synthesize bufferSize;
  30. // static member getter
  31. + (NSArray *) getFileStreams {
  32. if(fileStreams == nil)
  33. fileStreams = [[NSMutableDictionary alloc] init];
  34. return fileStreams;
  35. }
  36. +(void) setFileStream:(RNFetchBlobFS *) instance withId:(NSString *) uuid {
  37. if(fileStreams == nil)
  38. fileStreams = [[NSMutableDictionary alloc] init];
  39. [fileStreams setValue:instance forKey:uuid];
  40. }
  41. + (NSString *) getCacheDir {
  42. return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  43. }
  44. + (NSString *) getDocumentDir {
  45. return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  46. }
  47. + (NSString *) getMusicDir {
  48. return [NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES) firstObject];
  49. }
  50. + (NSString *) getMovieDir {
  51. return [NSSearchPathForDirectoriesInDomains(NSMoviesDirectory, NSUserDomainMask, YES) firstObject];
  52. }
  53. + (NSString *) getPictureDir {
  54. return [NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES) firstObject];
  55. }
  56. + (NSString *) getTempPath {
  57. return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingString:@"/RNFetchBlob_tmp"];
  58. }
  59. + (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext {
  60. NSString * documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  61. NSString * filename = [NSString stringWithFormat:@"/RNFetchBlob_tmp/RNFetchBlobTmp_%@", taskId];
  62. if(ext != nil)
  63. filename = [filename stringByAppendingString: [NSString stringWithFormat:@".%@", ext]];
  64. NSString * tempPath = [documentDir stringByAppendingString: filename];
  65. return tempPath;
  66. }
  67. + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
  68. @try {
  69. NSFileManager * fm = [NSFileManager defaultManager];
  70. NSError * err = nil;
  71. NSString * folder = [path stringByDeletingLastPathComponent];
  72. if(![fm fileExistsAtPath:folder]) {
  73. [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
  74. }
  75. if(![fm fileExistsAtPath:path]) {
  76. if([[encoding lowercaseString] isEqualToString:@"base64"]){
  77. NSData * byteData = [[NSData alloc] initWithBase64EncodedString:data options:0];
  78. [fm createFileAtPath:path contents:byteData attributes:NULL];
  79. }
  80. else
  81. [fm createFileAtPath:path contents:[data dataUsingEncoding:NSUTF8StringEncoding] attributes:NULL];
  82. }
  83. else {
  84. NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
  85. [fileHandle seekToEndOfFile];
  86. if([[encoding lowercaseString] isEqualToString:@"base64"]) {
  87. NSData * byteData = [[NSData alloc] initWithBase64EncodedString:data options:0];
  88. [fileHandle writeData:byteData];
  89. }
  90. else
  91. [fileHandle writeData:[data dataUsingEncoding:NSUTF8StringEncoding]];
  92. [fileHandle closeFile];
  93. }
  94. fm = nil;
  95. resolve([NSNull null]);
  96. }
  97. @catch (NSException * e)
  98. {
  99. reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
  100. }
  101. }
  102. + (void) writeFileArray:(NSString *)path data:(NSArray *)data resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
  103. @try {
  104. NSFileManager * fm = [NSFileManager defaultManager];
  105. NSMutableData * fileContent = [NSMutableData alloc];
  106. // prevent stack overflow, alloc on heap
  107. char * bytes = (char*) malloc([data count]);
  108. for(int i = 0; i < data.count; i++) {
  109. bytes[i] = [[data objectAtIndex:i] charValue];
  110. }
  111. // if append == NO
  112. // BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
  113. [fileContent appendBytes:bytes length:data.count];
  114. NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
  115. [fileHandle seekToEndOfFile];
  116. [fileHandle writeData:fileContent];
  117. [fileHandle closeFile];
  118. free(bytes);
  119. fm = nil;
  120. resolve([NSNull null]);
  121. }
  122. @catch (NSException * e)
  123. {
  124. reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
  125. }
  126. }
  127. + (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
  128. @try
  129. {
  130. NSFileManager * fm = [NSFileManager defaultManager];
  131. NSError *err = nil;
  132. BOOL exists = [fm fileExistsAtPath:path];
  133. if(!exists) {
  134. @throw @"RNFetchBlobFS readFile error", @"file not exists", path;
  135. return;
  136. }
  137. if([[encoding lowercaseString] isEqualToString:@"utf8"]) {
  138. NSString * utf8Result = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err];
  139. resolve(utf8Result);
  140. }
  141. else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
  142. NSData * fileData = [NSData dataWithContentsOfFile:path];
  143. resolve([fileData base64EncodedStringWithOptions:0]);
  144. }
  145. else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
  146. NSData * resultData = [NSData dataWithContentsOfFile:path];
  147. NSMutableArray * resultArray = [NSMutableArray array];
  148. char * bytes = [resultData bytes];
  149. for(int i=0;i<[resultData length];i++) {
  150. [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
  151. }
  152. resolve(resultArray);
  153. }
  154. }
  155. @catch(NSException * e)
  156. {
  157. reject(@"RNFetchBlobFS readFile error", @"error", [e description]);
  158. }
  159. }
  160. + (BOOL) mkdir:(NSString *) path {
  161. BOOL isDir;
  162. NSError * err = nil;
  163. // if temp folder not exists, create one
  164. if(![[NSFileManager defaultManager] fileExistsAtPath: path isDirectory:&isDir]) {
  165. [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
  166. }
  167. return err == nil;
  168. }
  169. + (NSDictionary *) stat:(NSString *) path error:(NSError **) error {
  170. NSMutableDictionary *stat = [[NSMutableDictionary alloc] init];
  171. BOOL isDir = NO;
  172. NSFileManager * fm = [NSFileManager defaultManager];
  173. if([fm fileExistsAtPath:path isDirectory:&isDir] == NO) {
  174. return nil;
  175. }
  176. NSDictionary * info = [fm attributesOfItemAtPath:path error:&error];
  177. NSString * size = [NSString stringWithFormat:@"%d", [info fileSize]];
  178. NSString * filename = [path lastPathComponent];
  179. NSDate * lastModified;
  180. [[NSURL fileURLWithPath:path] getResourceValue:&lastModified forKey:NSURLContentModificationDateKey error:&error];
  181. return @{
  182. @"size" : size,
  183. @"filename" : filename,
  184. @"path" : path,
  185. @"lastModified" : [NSString stringWithFormat:@"%d", [lastModified timeIntervalSince1970]],
  186. @"type" : isDir ? @"directory" : @"file"
  187. };
  188. }
  189. + (BOOL) exists:(NSString *) path {
  190. return [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:NULL];
  191. }
  192. - (id)init {
  193. self = [super init];
  194. return self;
  195. }
  196. - (id)initWithCallback:(RCTResponseSenderBlock)callback {
  197. self = [super init];
  198. self.callback = callback;
  199. return self;
  200. }
  201. - (id)initWithBridgeRef:(RCTBridge *)bridgeRef {
  202. self = [super init];
  203. self.bridge = bridgeRef;
  204. return self;
  205. }
  206. // Create file stream for write data
  207. - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)encode appendData:(BOOL)append {
  208. self.outStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:append];
  209. self.encoding = encode;
  210. [self.outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  211. [self.outStream open];
  212. NSString *uuid = [[NSUUID UUID] UUIDString];
  213. self.streamId = uuid;
  214. [RNFetchBlobFS setFileStream:self withId:uuid];
  215. return uuid;
  216. }
  217. // Write file chunk into an opened stream
  218. - (void)writeEncodeChunk:(NSString *) chunk {
  219. NSMutableData * decodedData = [NSData alloc];
  220. if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
  221. decodedData = [[NSData alloc] initWithBase64EncodedData:chunk options:0];
  222. }
  223. if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
  224. decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
  225. }
  226. else if([[self.encoding lowercaseString] isEqualToString:@"ascii"]) {
  227. decodedData = [chunk dataUsingEncoding:NSASCIIStringEncoding];
  228. }
  229. NSUInteger left = [decodedData length];
  230. NSUInteger nwr = 0;
  231. do {
  232. nwr = [self.outStream write:[decodedData bytes] maxLength:left];
  233. if (-1 == nwr) break;
  234. left -= nwr;
  235. } while (left > 0);
  236. if (left) {
  237. NSLog(@"stream error: %@", [self.outStream streamError]);
  238. }
  239. }
  240. // Write file chunk into an opened stream
  241. - (void)write:(NSData *) chunk {
  242. NSUInteger left = [chunk length];
  243. NSUInteger nwr = 0;
  244. do {
  245. nwr = [self.outStream write:[chunk bytes] maxLength:left];
  246. if (-1 == nwr) break;
  247. left -= nwr;
  248. } while (left > 0);
  249. if (left) {
  250. NSLog(@"stream error: %@", [self.outStream streamError]);
  251. }
  252. }
  253. // close file write stream
  254. - (void)closeOutStream {
  255. if(self.outStream != nil) {
  256. [self.outStream close];
  257. self.outStream = nil;
  258. }
  259. }
  260. - (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding bufferSize:(int) bufferSize {
  261. self.inStream = [[NSInputStream alloc] initWithFileAtPath:path];
  262. self.inStream.delegate = self;
  263. self.encoding = encoding;
  264. self.path = path;
  265. self.bufferSize = bufferSize;
  266. // NSStream needs a runloop so let's create a run loop for it
  267. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
  268. // start NSStream is a runloop
  269. dispatch_async(queue, ^ {
  270. [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
  271. forMode:NSDefaultRunLoopMode];
  272. [inStream open];
  273. [[NSRunLoop currentRunLoop] run];
  274. });
  275. }
  276. // close file read stream
  277. - (void)closeInStream {
  278. if(self.inStream != nil) {
  279. [self.inStream close];
  280. [self.inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  281. [[RNFetchBlobFS getFileStreams] setValue:nil forKey:self.streamId];
  282. self.streamId = nil;
  283. }
  284. }
  285. void runOnMainQueueWithoutDeadlocking(void (^block)(void))
  286. {
  287. if ([NSThread isMainThread])
  288. {
  289. block();
  290. }
  291. else
  292. {
  293. dispatch_sync(dispatch_get_main_queue(), block);
  294. }
  295. }
  296. #pragma mark RNFetchBlobFS read stream delegate
  297. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
  298. NSString * streamEventCode = [NSString stringWithFormat:@"RNFetchBlobStream+%@", self.path];
  299. switch(eventCode) {
  300. // write stream event
  301. case NSStreamEventHasSpaceAvailable:
  302. {
  303. }
  304. // read stream incoming chunk
  305. case NSStreamEventHasBytesAvailable:
  306. {
  307. NSMutableData * chunkData = [[NSMutableData alloc] init];
  308. NSInteger chunkSize = 4096;
  309. if([[self.encoding lowercaseString] isEqualToString:@"base64"])
  310. chunkSize = 4095;
  311. if(self.bufferSize > 0)
  312. chunkSize = self.bufferSize;
  313. // uint8_t * buf = (uint8_t *)malloc(chunkSize);
  314. uint8_t buf[chunkSize];
  315. unsigned int len = 0;
  316. len = [(NSInputStream *)stream read:buf maxLength:chunkSize];
  317. // still have data in stream
  318. if(len) {
  319. [chunkData appendBytes:buf length:len];
  320. // dispatch data event
  321. NSString * encodedChunk = [NSString alloc];
  322. if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
  323. encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
  324. }
  325. // when encoding is ASCII, send byte array data
  326. else if ( [[self.encoding lowercaseString] isEqualToString:@"ascii"] ) {
  327. // RCTBridge only emits string data, so we have to create JSON byte array string
  328. NSMutableArray * asciiArray = [NSMutableArray array];
  329. unsigned char *bytePtr;
  330. if (chunkData.length > 0)
  331. {
  332. bytePtr = (unsigned char *)[chunkData bytes];
  333. NSInteger byteLen = chunkData.length/sizeof(uint8_t);
  334. for (int i = 0; i < byteLen; i++)
  335. {
  336. [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
  337. }
  338. }
  339. [self.bridge.eventDispatcher
  340. sendDeviceEventWithName:streamEventCode
  341. body: @{
  342. @"event": FS_EVENT_DATA,
  343. @"detail": asciiArray
  344. }
  345. ];
  346. // free(buf);
  347. // asciiStr = nil;
  348. // buf = nil;
  349. // chunkData = nil;
  350. return;
  351. }
  352. // convert byte array to base64 data chunks
  353. else if ( [[self.encoding lowercaseString] isEqualToString:@"base64"] ) {
  354. encodedChunk = [chunkData base64EncodedStringWithOptions:0];
  355. }
  356. // unknown encoding, send error event
  357. else {
  358. [self.bridge.eventDispatcher
  359. sendDeviceEventWithName:streamEventCode
  360. body:@{
  361. @"event": FS_EVENT_ERROR,
  362. @"detail": @"unrecognized encoding"
  363. }
  364. ];
  365. return;
  366. }
  367. [self.bridge.eventDispatcher
  368. sendDeviceEventWithName:streamEventCode
  369. body:@{
  370. @"event": FS_EVENT_DATA,
  371. @"detail": encodedChunk
  372. }
  373. ];
  374. // chunkData = nil;
  375. // free(buf);
  376. }
  377. // end of stream
  378. else {
  379. [self.bridge.eventDispatcher
  380. sendDeviceEventWithName:streamEventCode
  381. body:@{
  382. @"event": FS_EVENT_END,
  383. @"detail": @""
  384. }
  385. ];
  386. // chunkData = nil;
  387. // free(buf);
  388. }
  389. break;
  390. }
  391. // stream error
  392. case NSStreamEventErrorOccurred:
  393. {
  394. [self.bridge.eventDispatcher
  395. sendDeviceEventWithName:streamEventCode
  396. body:@{
  397. @"event": FS_EVENT_ERROR,
  398. @"detail": @"RNFetchBlob error when read file with stream, file may not exists"
  399. }
  400. ];
  401. break;
  402. }
  403. }
  404. }
  405. @end