Browse Source

Correct fs API implementation

Ben Hsieh 8 years ago
parent
commit
79865a0b3a

+ 47
- 15
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

@@ -16,6 +16,7 @@ import java.io.FileOutputStream;
16 16
 import java.io.IOException;
17 17
 import java.io.InputStream;
18 18
 import java.io.OutputStream;
19
+import java.io.OutputStreamWriter;
19 20
 import java.nio.charset.Charset;
20 21
 import java.nio.charset.StandardCharsets;
21 22
 import java.util.HashMap;
@@ -163,19 +164,8 @@ public class RNFetchBlobFS {
163 164
 
164 165
         RNFetchBlobFS fs = fileStreams.get(streamId);
165 166
         FileOutputStream stream = fs.writeStreamInstance;
166
-        byte [] chunk;
167
-        if(fs.encoding.equalsIgnoreCase("ascii")) {
168
-            chunk = data.getBytes(Charset.forName("US-ASCII"));
169
-        }
170
-        else if(fs.encoding.equalsIgnoreCase("base64")) {
171
-            chunk = Base64.decode(data, 0);
172
-        }
173
-        else if(fs.encoding.equalsIgnoreCase("utf8")) {
174
-            chunk = data.getBytes(Charset.forName("UTF-8"));
175
-        }
176
-        else {
177
-            chunk = data.getBytes(Charset.forName("US-ASCII"));
178
-        }
167
+        byte [] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
168
+
179 169
         try {
180 170
             stream.write(chunk);
181 171
             callback.invoke(null);
@@ -303,9 +293,51 @@ public class RNFetchBlobFS {
303 293
     static void ls(String path, Callback callback) {
304 294
         File src = new File(path);
305 295
         if(!src.exists() || !src.isDirectory())
306
-            callback.invoke(null);
296
+            callback.invoke("failed to list path `"+path+"` for it is not exist or it is not a folder");
307 297
         String [] files = new File(path).list();
308
-        callback.invoke(files);
298
+        callback.invoke(null, files);
299
+    }
300
+
301
+    /**
302
+     * Create new file at path
303
+     * @param path
304
+     * @param data
305
+     * @param encoding
306
+     * @param callback
307
+     */
308
+    static void createFile(String path, String data, String encoding, Callback callback) {
309
+        try {
310
+            File dest = new File(path);
311
+            boolean created = dest.createNewFile();
312
+            if(!created) {
313
+                callback.invoke("failed to create file at path `" + path + "` for its parent path may not exists");
314
+                return;
315
+            }
316
+            OutputStream ostream = new FileOutputStream(dest);
317
+            ostream.write(RNFetchBlobFS.stringToBytes(data, encoding));
318
+            callback.invoke(null, path);
319
+        } catch(Exception err) {
320
+            callback.invoke(err.getLocalizedMessage());
321
+        }
322
+    }
323
+
324
+    /**
325
+     * String to byte converter method
326
+     * @param data  Raw data in string format
327
+     * @param encoding Decoder name
328
+     * @return  Converted data byte array
329
+     */
330
+    private static byte[] stringToBytes(String data, String encoding) {
331
+        if(encoding.equalsIgnoreCase("ascii")) {
332
+            return data.getBytes(Charset.forName("US-ASCII"));
333
+        }
334
+        else if(encoding.equalsIgnoreCase("base64")) {
335
+            return Base64.decode(data, 0);
336
+        }
337
+        else if(encoding.equalsIgnoreCase("utf8")) {
338
+            return data.getBytes(Charset.forName("UTF-8"));
339
+        }
340
+        return data.getBytes(Charset.forName("US-ASCII"));
309 341
     }
310 342
 
311 343
     /**

+ 46
- 5
src/fs.js View File

@@ -55,6 +55,17 @@ function session(name:string):RNFetchBlobSession {
55 55
   }
56 56
 }
57 57
 
58
+function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise {
59
+  return new Promise((resolve, reject) => {
60
+    RNFetchBlob.createFile(path, data, encoding, (err) => {
61
+      if(err)
62
+        reject(err)
63
+      else
64
+        resolve()
65
+    })
66
+  })
67
+}
68
+
58 69
 /**
59 70
  * Create write stream to a file.
60 71
  * @param  {string} path Target path of file stream.
@@ -95,7 +106,7 @@ function readStream(
95 106
 
96 107
   if(!path)
97 108
     throw Error('RNFetchBlob could not open file stream with empty `path`')
98
-
109
+  encoding = encoding || 'utf8'
99 110
   let stream:RNFetchBlobStream = {
100 111
     onData : function(fn) {
101 112
       this._onData = fn
@@ -118,7 +129,10 @@ function readStream(
118 129
       stream._onEnd(detail)
119 130
     }
120 131
     else {
121
-      stream._onError(detail)
132
+      if(stream._onError)
133
+        stream._onError(detail)
134
+      else
135
+        throw new Error(detail)
122 136
     }
123 137
     // when stream closed or error, remove event handler
124 138
     if (event === 'error' || event === 'end') {
@@ -157,7 +171,7 @@ function cp(path:string, dest:string):Promise<boolean> {
157 171
 
158 172
 function mv(path:string, dest:string):Promise<boolean> {
159 173
   return new Promise((resolve, reject) => {
160
-    RNFetchBlob.ls(path, dest, (err, res) => {
174
+    RNFetchBlob.mv(path, dest, (err, res) => {
161 175
       if(err)
162 176
         reject(err)
163 177
       else
@@ -212,6 +226,21 @@ function exists(path:string):Promise<bool, bool> {
212 226
 
213 227
 }
214 228
 
229
+function isDir(path:string):Promise<bool, bool> {
230
+
231
+  return new Promise((resolve, reject) => {
232
+    try {
233
+      RNFetchBlob.exists(path, (exist, isDir) => {
234
+        resolve(isDir)
235
+      })
236
+    } catch(err) {
237
+      reject(err)
238
+    }
239
+  })
240
+
241
+}
242
+
243
+
215 244
 /**
216 245
  * Session class
217 246
  * @class RNFetchBlobSession
@@ -281,7 +310,7 @@ class WriteStream {
281 310
     this.append = append
282 311
   }
283 312
 
284
-  write() {
313
+  write(data:string) {
285 314
     return new Promise((resolve, reject) => {
286 315
       try {
287 316
         RNFetchBlob.writeChunk(this.id, data, (error) => {
@@ -311,5 +340,17 @@ class WriteStream {
311 340
 }
312 341
 
313 342
 export default {
314
-  RNFetchBlobSession, unlink, mkdir, session, ls, readStream, getSystemDirs, mv, cp
343
+  RNFetchBlobSession,
344
+  unlink,
345
+  mkdir,
346
+  session,
347
+  ls,
348
+  readStream,
349
+  getSystemDirs,
350
+  mv,
351
+  cp,
352
+  writeStream,
353
+  exists,
354
+  createFile,
355
+  isDir
315 356
 }

+ 8
- 1
src/index.js View File

@@ -23,11 +23,14 @@ const {
23 23
   RNFetchBlobSession,
24 24
   getSystemDirs,
25 25
   readStream,
26
+  createFile,
26 27
   unlink,
28
+  exists,
27 29
   mkdir,
28 30
   session,
29 31
   writeStream,
30 32
   ls,
33
+  isDir,
31 34
   mv,
32 35
   cp
33 36
 } = fs
@@ -245,5 +248,9 @@ function getUUID() {
245 248
 }
246 249
 
247 250
 export default {
248
-  fetch, base64, config, getSystemDirs, readStream, unlink, session, ls, mkdir, mv, cp, writeStream
251
+  fetch,
252
+  base64,
253
+  config,
254
+  session,
255
+  fs,
249 256
 }

+ 77
- 31
src/ios/RNFetchBlob/RNFetchBlob.m View File

@@ -26,6 +26,7 @@ NSString *const FS_EVENT_DATA = @"data";
26 26
 NSString *const FS_EVENT_END = @"end";
27 27
 NSString *const FS_EVENT_WARN = @"warn";
28 28
 NSString *const FS_EVENT_ERROR = @"error";
29
+NSMutableDictionary *fileStreams = nil;
29 30
 
30 31
 ////////////////////////////////////////
31 32
 //
@@ -48,12 +49,17 @@ NSString *const FS_EVENT_ERROR = @"error";
48 49
 // static member getter
49 50
 + (NSArray *) getFileStreams {
50 51
     
51
-    static NSMutableData *fileStreams = nil;
52 52
     if(fileStreams == nil)
53
-        fileStreams = [[NSArray alloc] init];
53
+        fileStreams = [[NSMutableDictionary alloc] init];
54 54
     return fileStreams;
55 55
 }
56 56
 
57
++(void) setFileStream:(FetchBlobFS *) instance withId:(NSString *) uuid {
58
+    if(fileStreams == nil)
59
+        fileStreams = [[NSMutableDictionary alloc] init];
60
+    [fileStreams setValue:instance forKey:uuid];
61
+}
62
+
57 63
 + (NSString *) getCacheDir {
58 64
     return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
59 65
 }
@@ -129,7 +135,7 @@ NSString *const FS_EVENT_ERROR = @"error";
129 135
     [self.outStream open];
130 136
     NSString *uuid = [[NSUUID UUID] UUIDString];
131 137
     self.streamId = uuid;
132
-    [[FetchBlobFS getFileStreams] setValue:self forKey:uuid];
138
+    [FetchBlobFS setFileStream:self withId:uuid];
133 139
     return uuid;
134 140
 }
135 141
 
@@ -145,7 +151,7 @@ NSString *const FS_EVENT_ERROR = @"error";
145 151
     else if([[self.encoding lowercaseString] isEqualToString:@"ascii"]) {
146 152
         decodedData = [chunk dataUsingEncoding:NSASCIIStringEncoding];
147 153
     }
148
-    NSUInteger left = [chunk length];
154
+    NSUInteger left = [decodedData length];
149 155
     NSUInteger nwr = 0;
150 156
     do {
151 157
         nwr = [self.outStream write:[decodedData bytes] maxLength:left];
@@ -171,6 +177,15 @@ NSString *const FS_EVENT_ERROR = @"error";
171 177
     }
172 178
 }
173 179
 
180
+// close file write stream
181
+- (void)closeOutStream {
182
+    if(self.outStream != nil) {
183
+        [self.outStream close];
184
+        self.outStream = nil;
185
+    }
186
+
187
+}
188
+
174 189
 - (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding bufferSize:(int) bufferSize{
175 190
     
176 191
     self.inStream = [[NSInputStream alloc] initWithFileAtPath:path];
@@ -184,22 +199,13 @@ NSString *const FS_EVENT_ERROR = @"error";
184 199
     // start NSStream is a runloop
185 200
     dispatch_async(queue, ^ {
186 201
         [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
187
-                             forMode:NSDefaultRunLoopMode];
202
+                            forMode:NSDefaultRunLoopMode];
188 203
         [inStream open];
189 204
         [[NSRunLoop currentRunLoop] run];
190
-    
205
+        
191 206
     });
192 207
 }
193 208
 
194
-// close file write stream
195
-- (void)closeOutStream {
196
-    if(self.outStream != nil) {
197
-        [self.outStream close];
198
-        self.outStream = nil;
199
-    }
200
-
201
-}
202
-
203 209
 // close file read stream
204 210
 - (void)closeInStream {
205 211
     if(self.inStream != nil) {
@@ -245,7 +251,7 @@ void runOnMainQueueWithoutDeadlocking(void (^block)(void))
245 251
             NSMutableData * chunkData = [[NSMutableData data] init];
246 252
             NSInteger chunkSize = 4096;
247 253
             if([[self.encoding lowercaseString] isEqualToString:@"base64"])
248
-                chunkSize = 4098;
254
+                chunkSize = 4095;
249 255
             if(self.bufferSize > 0)
250 256
                 chunkSize = self.bufferSize;
251 257
             uint8_t buf[chunkSize];
@@ -257,7 +263,7 @@ void runOnMainQueueWithoutDeadlocking(void (^block)(void))
257 263
                 [chunkData appendBytes:(const void *)buf length:len];
258 264
                 // TODO : file read progress ?
259 265
                 // dispatch data event
260
-                NSString * encodedChunk;
266
+                NSString * encodedChunk = [NSString alloc];
261 267
                 if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
262 268
                     encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
263 269
                 }
@@ -277,15 +283,15 @@ void runOnMainQueueWithoutDeadlocking(void (^block)(void))
277 283
                      ];
278 284
                     return;
279 285
                 }
280
-                runOnMainQueueWithoutDeadlocking(^{
281
-                    [self.bridge.eventDispatcher
282
-                     sendDeviceEventWithName:streamEventCode
283
-                     body:@{
284
-                            @"event": FS_EVENT_DATA,
285
-                            @"detail": encodedChunk
286
-                            }
287
-                     ];
288
-                });
286
+
287
+                [self.bridge.eventDispatcher
288
+                 sendDeviceEventWithName:streamEventCode
289
+                 body:@{
290
+                        @"event": FS_EVENT_DATA,
291
+                        @"detail": encodedChunk
292
+                        }
293
+                 ];
294
+
289 295
             }
290 296
             // end of stream
291 297
             else {
@@ -647,6 +653,30 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
647 653
     });
648 654
 }
649 655
 
656
+RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) {
657
+    
658
+    NSFileManager * fm = [NSFileManager defaultManager];
659
+    NSData * fileContent = nil;
660
+    
661
+    if([[encoding lowercaseString] isEqualToString:@"utf8"]) {
662
+        fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]];
663
+    }
664
+    else if([[encoding lowercaseString] isEqualToString:@"base64"]) {
665
+        fileContent = [[NSData alloc] initWithBase64EncodedData:data options:0];
666
+    }
667
+    else {
668
+        fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
669
+    }
670
+    
671
+    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
672
+    if(success == YES)
673
+        callback(@[[NSNull null]]);
674
+    else
675
+        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
676
+
677
+}
678
+
679
+
650 680
 RCT_EXPORT_METHOD(exists:(NSString *)path callback:(RCTResponseSenderBlock)callback) {
651 681
     BOOL isDir = NO;
652 682
     BOOL exists = NO;
@@ -657,6 +687,12 @@ RCT_EXPORT_METHOD(exists:(NSString *)path callback:(RCTResponseSenderBlock)callb
657 687
 
658 688
 RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize) {
659 689
     FetchBlobFS *fileStream = [[FetchBlobFS alloc] initWithBridgeRef:self.bridge];
690
+    if(bufferSize == nil) {
691
+        if([[encoding lowercaseString] isEqualToString:@"base64"])
692
+            bufferSize = 4095;
693
+        else
694
+            bufferSize = 4096;
695
+    }
660 696
     [fileStream readWithPath:path useEncoding:encoding bufferSize:bufferSize];
661 697
 }
662 698
 
@@ -664,7 +700,7 @@ RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding
664 700
     FetchBlobFS * fileStream = [[FetchBlobFS alloc] initWithBridgeRef:self.bridge];
665 701
     NSFileManager * fm = [NSFileManager defaultManager];
666 702
     BOOL isDir = nil;
667
-    BOOL exist = ![fm fileExistsAtPath:path isDirectory:&isDir];
703
+    BOOL exist = [fm fileExistsAtPath:path isDirectory:&isDir];
668 704
     if( exist == NO || isDir == YES) {
669 705
         callback(@[[NSString stringWithFormat:@"target path `%@` may not exists or it's a folder", path]]);
670 706
         return;
@@ -711,6 +747,14 @@ RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBloc
711 747
 }
712 748
 
713 749
 RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
750
+    NSFileManager* fm = [NSFileManager defaultManager];
751
+    BOOL exist = nil;
752
+    BOOL isDir = nil;
753
+    exist = [fm fileExistsAtPath:path isDirectory:&isDir];
754
+    if(exist == NO || isDir == NO) {
755
+        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not a folder", path]]);
756
+        return ;
757
+    }
714 758
     NSError * error = nil;
715 759
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
716 760
     
@@ -723,7 +767,7 @@ RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback
723 767
 
724 768
 RCT_EXPORT_METHOD(cp:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
725 769
     NSError * error = nil;
726
-    BOOL result = [[NSFileManager defaultManager] copyItemAtURL:path toURL:dest error:&error];
770
+    BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
727 771
     
728 772
     if(error == nil)
729 773
         callback(@[[NSNull null], @YES]);
@@ -734,7 +778,7 @@ RCT_EXPORT_METHOD(cp:(NSString *)path toPath:(NSString *)dest callback:(RCTRespo
734 778
 
735 779
 RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
736 780
     NSError * error = nil;
737
-    BOOL result = [[NSFileManager defaultManager] moveItemAtURL:path toURL:dest error:&error];
781
+    BOOL result = [[NSFileManager defaultManager] moveItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
738 782
     
739 783
     if(error == nil)
740 784
         callback(@[[NSNull null], @YES]);
@@ -744,8 +788,10 @@ RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTRespo
744 788
 }
745 789
 
746 790
 RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
747
-    if([FetchBlobFS exists:path])
748
-        callback(@[@"file path exists"]);
791
+    if([FetchBlobFS exists:path]) {
792
+        callback(@[@"mkdir failed, folder already exists"]);
793
+        return;
794
+    }
749 795
     else
750 796
         [FetchBlobFS mkdir:path];
751 797
     callback(@[[NSNull null]]);