Browse Source

Merge branch '0.10.9' into exception_fixes

chrusart 6 years ago
parent
commit
7cebeaa45a
No account linked to committer's email address

+ 50
- 4
README.md View File

@@ -590,10 +590,12 @@ File Access APIs
590 590
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
591 591
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
592 592
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
593
-- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--array-encodingstringpromise)
593
+- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--arraynumber-encodingstring-promisenumber)
594 594
 - [readFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readfilepath-encodingpromise)
595
-- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersizepromise)
596
-- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstring-appendbooleanpromise)
595
+- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersize-interval-promisernfbreadstream)
596
+- [hash (0.10.9)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithm-promise)
597
+- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstringpromise)
598
+- [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
597 599
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
598 600
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
599 601
 - [ls](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#lspathstringpromise)
@@ -643,6 +645,45 @@ RNFetchBlob.fs.readStream(
643 645
 
644 646
 When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`.
645 647
 
648
+Since version 0.10.9 `write()` resolves with the `RNFetchBlob` instance so you can promise-chain write calls:
649
+
650
+```js
651
+RNFetchBlob.fs.writeStream(
652
+    PATH_TO_FILE,
653
+    // encoding, should be one of `base64`, `utf8`, `ascii`
654
+    'utf8',
655
+    // should data append to existing content ?
656
+    true
657
+)
658
+.then(ofstream => ofstream.write('foo'))
659
+.then(ofstream => ofstream.write('bar'))
660
+.then(ofstream => ofstream.write('foobar'))
661
+.then(ofstream => ofstream.close())
662
+.catch(console.error)
663
+```
664
+
665
+or 
666
+
667
+```js
668
+RNFetchBlob.fs.writeStream(
669
+    PATH_TO_FILE,
670
+    // encoding, should be one of `base64`, `utf8`, `ascii`
671
+    'utf8',
672
+    // should data append to existing content ?
673
+    true
674
+)
675
+.then(stream => Promise.all([
676
+    stream.write('foo'),
677
+    stream.write('bar'),
678
+    stream.write('foobar')
679
+]))
680
+// Use array destructuring to get the stream object from the first item of the array we get from Promise.all()
681
+.then(([stream]) => stream.close())
682
+.catch(console.error)
683
+```
684
+
685
+You should **NOT** do something like this:
686
+
646 687
 ```js
647 688
 RNFetchBlob.fs.writeStream(
648 689
     PATH_TO_FILE,
@@ -651,13 +692,18 @@ RNFetchBlob.fs.writeStream(
651 692
     // should data append to existing content ?
652 693
     true)
653 694
 .then((ofstream) => {
695
+    // BAD IDEA - Don't do this, those writes are unchecked:
654 696
     ofstream.write('foo')
655 697
     ofstream.write('bar')
656 698
     ofstream.close()
657 699
 })
658
-
700
+.catch(console.error)  // Cannot catch any write() errors!
659 701
 ```
660 702
 
703
+The problem with the above code is that the promises from the `ofstream.write()` calls are detached and "Lost".
704
+That means the entire promise chain A) resolves without waiting for the writes to finish and B) any errors caused by them are lost.
705
+That code may _seem_ to work if there are no errors, but those writes are of the type "fire and forget": You start them and then turn away and never know if they really succeeded.
706
+
661 707
 ### Cache File Management
662 708
 
663 709
 When using `fileCache` or `path` options along with `fetch` API, response data will automatically store into the file system. The files will **NOT** removed unless you `unlink` it. There're several ways to remove the files

+ 1
- 1
android.js View File

@@ -13,7 +13,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
13 13
 
14 14
 /**
15 15
  * Send an intent to open the file.
16
- * @param  {string]} path Path of the file to be open.
16
+ * @param  {string} path Path of the file to be open.
17 17
  * @param  {string} mime MIME type string
18 18
  * @return {Promise}
19 19
  */

+ 1
- 0
android/build.gradle View File

@@ -34,5 +34,6 @@ android {
34 34
 
35 35
 dependencies {
36 36
     compile 'com.facebook.react:react-native:+'
37
+    //compile 'com.squareup.okhttp3:okhttp:+'
37 38
     //{RNFetchBlob_PRE_0.28_DEPDENDENCY}
38 39
 }

+ 1
- 1
android/gradle/wrapper/gradle-wrapper.properties View File

@@ -1,4 +1,4 @@
1
-#Wed May 18 12:33:41 CST 2016
1
+#Sat Aug 12 07:48:35 CEST 2017
2 2
 distributionBase=GRADLE_USER_HOME
3 3
 distributionPath=wrapper/dists
4 4
 zipStoreBase=GRADLE_USER_HOME

+ 38
- 20
android/src/main/java/com/RNFetchBlob/RNFetchBlob.java View File

@@ -4,6 +4,7 @@ import android.app.Activity;
4 4
 import android.app.DownloadManager;
5 5
 import android.content.Intent;
6 6
 import android.net.Uri;
7
+import android.util.SparseArray;
7 8
 
8 9
 import com.facebook.react.bridge.ActivityEventListener;
9 10
 import com.facebook.react.bridge.Callback;
@@ -34,26 +35,23 @@ import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT;
34 35
 
35 36
 public class RNFetchBlob extends ReactContextBaseJavaModule {
36 37
 
37
-    // Cookies
38
-    private final ForwardingCookieHandler mCookieHandler;
39
-    private final CookieJarContainer mCookieJarContainer;
40 38
     private final OkHttpClient mClient;
41 39
 
42 40
     static ReactApplicationContext RCTContext;
43
-    static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
44
-    static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
41
+    private static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
42
+    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
45 43
     static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
46
-    static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
47
-    static public boolean ActionViewVisible = false;
48
-    static HashMap<Integer, Promise> promiseTable = new HashMap<>();
44
+    private static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
45
+    private static boolean ActionViewVisible = false;
46
+    private static SparseArray<Promise> promiseTable = new SparseArray<>();
49 47
 
50 48
     public RNFetchBlob(ReactApplicationContext reactContext) {
51 49
 
52 50
         super(reactContext);
53 51
 
54 52
         mClient = OkHttpClientProvider.getOkHttpClient();
55
-        mCookieHandler = new ForwardingCookieHandler(reactContext);
56
-        mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
53
+        ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler(reactContext);
54
+        CookieJarContainer mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
57 55
         mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
58 56
 
59 57
         RCTContext = reactContext;
@@ -85,11 +83,21 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
85 83
     }
86 84
 
87 85
     @ReactMethod
88
-    public void createFile(final String path, final String content, final String encode, final Callback callback) {
86
+    public void createFile(final String path, final String content, final String encode, final Promise promise) {
89 87
         threadPool.execute(new Runnable() {
90 88
             @Override
91 89
             public void run() {
92
-                RNFetchBlobFS.createFile(path, content, encode, callback);
90
+                RNFetchBlobFS.createFile(path, content, encode, promise);
91
+            }
92
+        });
93
+    }
94
+
95
+    @ReactMethod
96
+    public void createFileASCII(final String path, final ReadableArray dataArray, final Promise promise) {
97
+        threadPool.execute(new Runnable() {
98
+            @Override
99
+            public void run() {
100
+                RNFetchBlobFS.createFileASCII(path, dataArray, promise);
93 101
             }
94 102
         });
95 103
     }
@@ -123,7 +131,7 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
123 131
             };
124 132
             RCTContext.addLifecycleEventListener(listener);
125 133
         } catch(Exception ex) {
126
-            promise.reject(ex.getLocalizedMessage());
134
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
127 135
         }
128 136
     }
129 137
 
@@ -148,8 +156,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
148 156
     }
149 157
 
150 158
     @ReactMethod
151
-    public void mkdir(String path, Callback callback) {
152
-        RNFetchBlobFS.mkdir(path, callback);
159
+    public void mkdir(String path, Promise promise) {
160
+        RNFetchBlobFS.mkdir(path, promise);
153 161
     }
154 162
 
155 163
     @ReactMethod
@@ -173,8 +181,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
173 181
     }
174 182
 
175 183
     @ReactMethod
176
-    public void ls(String path, Callback callback) {
177
-        RNFetchBlobFS.ls(path, callback);
184
+    public void ls(String path, Promise promise) {
185
+        RNFetchBlobFS.ls(path, promise);
178 186
     }
179 187
 
180 188
     @ReactMethod
@@ -262,11 +270,21 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
262 270
     }
263 271
 
264 272
     @ReactMethod
273
+    public void hash(final String path, final String algorithm, final Promise promise) {
274
+        threadPool.execute(new Runnable() {
275
+            @Override
276
+            public void run() {
277
+                RNFetchBlobFS.hash(path, algorithm, promise);
278
+            }
279
+        });
280
+    }
281
+
265 282
     /**
266 283
      * @param path Stream file path
267 284
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
268 285
      * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
269 286
      */
287
+    @ReactMethod
270 288
     public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
271 289
         final ReactApplicationContext ctx = this.getReactApplicationContext();
272 290
         fsThreadPool.execute(new Runnable() {
@@ -340,10 +358,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
340 358
 
341 359
     @ReactMethod
342 360
     public void addCompleteDownload (ReadableMap config, Promise promise) {
343
-        DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
361
+        DownloadManager dm = (DownloadManager) RCTContext.getSystemService(RCTContext.DOWNLOAD_SERVICE);
344 362
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
345 363
         if(path == null) {
346
-            promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path);
364
+            promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"));
347 365
             return;
348 366
         }
349 367
         try {
@@ -360,7 +378,7 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
360 378
             promise.resolve(null);
361 379
         }
362 380
         catch(Exception ex) {
363
-            promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString());
381
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
364 382
         }
365 383
 
366 384
     }

+ 28
- 32
android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

@@ -1,5 +1,6 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
+import android.support.annotation.NonNull;
3 4
 import android.util.Base64;
4 5
 
5 6
 import com.facebook.react.bridge.Arguments;
@@ -21,21 +22,20 @@ import okhttp3.MediaType;
21 22
 import okhttp3.RequestBody;
22 23
 import okio.BufferedSink;
23 24
 
24
-public class RNFetchBlobBody extends RequestBody{
25
+class RNFetchBlobBody extends RequestBody{
25 26
 
26
-    InputStream requestStream;
27
-    long contentLength = 0;
28
-    ReadableArray form;
29
-    String mTaskId;
30
-    String rawBody;
31
-    RNFetchBlobReq.RequestType requestType;
32
-    MediaType mime;
33
-    File bodyCache;
27
+    private InputStream requestStream;
28
+    private long contentLength = 0;
29
+    private ReadableArray form;
30
+    private String mTaskId;
31
+    private String rawBody;
32
+    private RNFetchBlobReq.RequestType requestType;
33
+    private MediaType mime;
34
+    private File bodyCache;
34 35
     int reported = 0;
35
-    Boolean chunkedEncoding = false;
36
+    private Boolean chunkedEncoding = false;
36 37
 
37
-
38
-    public RNFetchBlobBody(String taskId) {
38
+    RNFetchBlobBody(String taskId) {
39 39
         this.mTaskId = taskId;
40 40
     }
41 41
 
@@ -49,7 +49,7 @@ public class RNFetchBlobBody extends RequestBody{
49 49
         return this;
50 50
     }
51 51
 
52
-    RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) {
52
+    RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) {
53 53
         this.requestType = type;
54 54
         return this;
55 55
     }
@@ -114,7 +114,7 @@ public class RNFetchBlobBody extends RequestBody{
114 114
     }
115 115
 
116 116
     @Override
117
-    public void writeTo(BufferedSink sink) {
117
+    public void writeTo(@NonNull BufferedSink sink) {
118 118
         try {
119 119
             pipeStreamToSink(requestStream, sink);
120 120
         } catch(Exception ex) {
@@ -186,8 +186,7 @@ public class RNFetchBlobBody extends RequestBody{
186 186
         ArrayList<FormField> fields = countFormDataLength();
187 187
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
188 188
 
189
-        for(int i = 0;i < fields.size(); i++) {
190
-            FormField field = fields.get(i);
189
+        for(FormField field : fields) {
191 190
             String data = field.data;
192 191
             String name = field.name;
193 192
             // skip invalid fields
@@ -258,17 +257,14 @@ public class RNFetchBlobBody extends RequestBody{
258 257
      * @param sink      The request body buffer sink
259 258
      * @throws IOException
260 259
      */
261
-    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws Exception {
262
-
263
-        byte [] chunk = new byte[10240];
260
+    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
261
+        byte[] chunk = new byte[10240];
264 262
         int totalWritten = 0;
265 263
         int read;
266 264
         while((read = stream.read(chunk, 0, 10240)) > 0) {
267
-            if(read > 0) {
268
-                sink.write(chunk, 0, read);
269
-                totalWritten += read;
270
-                emitUploadProgress(totalWritten);
271
-            }
265
+            sink.write(chunk, 0, read);
266
+            totalWritten += read;
267
+            emitUploadProgress(totalWritten);
272 268
         }
273 269
         stream.close();
274 270
     }
@@ -291,7 +287,7 @@ public class RNFetchBlobBody extends RequestBody{
291 287
 
292 288
     /**
293 289
      * Compute approximate content length for form data
294
-     * @return
290
+     * @return ArrayList<FormField>
295 291
      */
296 292
     private ArrayList<FormField> countFormDataLength() {
297 293
         long total = 0;
@@ -300,11 +296,11 @@ public class RNFetchBlobBody extends RequestBody{
300 296
         for(int i = 0;i < form.size(); i++) {
301 297
             FormField field = new FormField(form.getMap(i));
302 298
             list.add(field);
303
-            String data = field.data;
304
-            if(data == null) {
299
+            if(field.data == null) {
305 300
                 RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
306 301
             }
307 302
             else if (field.filename != null) {
303
+                String data = field.data;
308 304
                 // upload from storage
309 305
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
310 306
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
@@ -333,7 +329,7 @@ public class RNFetchBlobBody extends RequestBody{
333 329
             }
334 330
             // data field
335 331
             else {
336
-                total += field.data != null ? field.data.getBytes().length : 0;
332
+                total += field.data.getBytes().length;
337 333
             }
338 334
         }
339 335
         contentLength = total;
@@ -346,11 +342,11 @@ public class RNFetchBlobBody extends RequestBody{
346 342
      */
347 343
     private class FormField {
348 344
         public String name;
349
-        public String filename;
350
-        public String mime;
345
+        String filename;
346
+        String mime;
351 347
         public String data;
352 348
 
353
-        public FormField(ReadableMap rawData) {
349
+        FormField(ReadableMap rawData) {
354 350
             if(rawData.hasKey("name"))
355 351
                 name = rawData.getString("name");
356 352
             if(rawData.hasKey("filename"))
@@ -368,7 +364,7 @@ public class RNFetchBlobBody extends RequestBody{
368 364
 
369 365
     /**
370 366
      * Emit progress event
371
-     * @param written
367
+     * @param written  Integer
372 368
      */
373 369
     private void emitUploadProgress(int written) {
374 370
         RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);

+ 1
- 4
android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java View File

@@ -3,10 +3,7 @@ package com.RNFetchBlob;
3 3
 import com.facebook.react.bridge.ReadableArray;
4 4
 import com.facebook.react.bridge.ReadableMap;
5 5
 
6
-import java.util.HashMap;
7
-
8
-
9
-public class RNFetchBlobConfig {
6
+class RNFetchBlobConfig {
10 7
 
11 8
     public Boolean fileCache;
12 9
     public String path;

+ 343
- 186
android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

@@ -1,7 +1,5 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
-import android.content.pm.PackageInfo;
4
-import android.content.pm.PackageManager;
5 3
 import android.content.res.AssetFileDescriptor;
6 4
 import android.media.MediaScannerConnection;
7 5
 import android.net.Uri;
@@ -22,42 +20,29 @@ import com.facebook.react.bridge.WritableArray;
22 20
 import com.facebook.react.bridge.WritableMap;
23 21
 import com.facebook.react.modules.core.DeviceEventManagerModule;
24 22
 
25
-import java.io.File;
26
-import java.io.FileInputStream;
27
-import java.io.FileOutputStream;
28
-import java.io.IOException;
29
-import java.io.InputStream;
30
-import java.io.OutputStream;
23
+import java.io.*;
31 24
 import java.nio.ByteBuffer;
32 25
 import java.nio.charset.Charset;
33 26
 import java.nio.charset.CharsetEncoder;
27
+import java.security.MessageDigest;
28
+import java.util.ArrayList;
34 29
 import java.util.HashMap;
35 30
 import java.util.Map;
36 31
 import java.util.UUID;
37 32
 
38
-public class RNFetchBlobFS {
33
+class RNFetchBlobFS {
39 34
 
40
-    ReactApplicationContext mCtx;
41
-    DeviceEventManagerModule.RCTDeviceEventEmitter emitter;
42
-    String encoding = "base64";
43
-    boolean append = false;
44
-    OutputStream writeStreamInstance = null;
45
-    static HashMap<String, RNFetchBlobFS> fileStreams = new HashMap<>();
35
+    private ReactApplicationContext mCtx;
36
+    private DeviceEventManagerModule.RCTDeviceEventEmitter emitter;
37
+    private String encoding = "base64";
38
+    private OutputStream writeStreamInstance = null;
39
+    private static HashMap<String, RNFetchBlobFS> fileStreams = new HashMap<>();
46 40
 
47 41
     RNFetchBlobFS(ReactApplicationContext ctx) {
48 42
         this.mCtx = ctx;
49 43
         this.emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
50 44
     }
51 45
 
52
-    static String getExternalFilePath(ReactApplicationContext ctx, String taskId, RNFetchBlobConfig config) {
53
-        if(config.path != null)
54
-            return config.path;
55
-        else if(config.fileCache && config.appendExt != null)
56
-            return RNFetchBlobFS.getTmpPath(ctx, taskId) + "." + config.appendExt;
57
-        else
58
-            return RNFetchBlobFS.getTmpPath(ctx, taskId);
59
-    }
60
-
61 46
     /**
62 47
      * Write string with encoding to file
63 48
      * @param path Destination file path.
@@ -65,25 +50,37 @@ public class RNFetchBlobFS {
65 50
      * @param data Array passed from JS context.
66 51
      * @param promise RCT Promise
67 52
      */
68
-    static public void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) {
53
+    static void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) {
69 54
         try {
70
-            int written = 0;
55
+            int written;
71 56
             File f = new File(path);
72 57
             File dir = f.getParentFile();
73
-            if(!dir.exists())
74
-                dir.mkdirs();
58
+
59
+            if(!f.exists()) {
60
+                if(dir != null && !dir.exists()) {
61
+                    if (!dir.mkdirs()) {
62
+                        promise.reject("EUNSPECIFIED", "Failed to create parent directory of '" + path + "'");
63
+                        return;
64
+                    }
65
+                }
66
+                if(!f.createNewFile()) {
67
+                    promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created");
68
+                    return;
69
+                }
70
+            }
71
+
75 72
             FileOutputStream fout = new FileOutputStream(f, append);
76 73
             // write data from a file
77 74
             if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
78
-                data = normalizePath(data);
79
-                File src = new File(data);
80
-                if(!src.exists()) {
81
-                    promise.reject("RNfetchBlob writeFileError", "source file : " + data + "not exists");
75
+                String normalizedData = normalizePath(data);
76
+                File src = new File(normalizedData);
77
+                if (!src.exists()) {
78
+                    promise.reject("ENOENT", "No such file '" + path + "' " + "('" + normalizedData + "')");
82 79
                     fout.close();
83
-                    return ;
80
+                    return;
84 81
                 }
85 82
                 FileInputStream fin = new FileInputStream(src);
86
-                byte [] buffer = new byte [10240];
83
+                byte[] buffer = new byte [10240];
87 84
                 int read;
88 85
                 written = 0;
89 86
                 while((read = fin.read(buffer)) > 0) {
@@ -99,8 +96,11 @@ public class RNFetchBlobFS {
99 96
             }
100 97
             fout.close();
101 98
             promise.resolve(written);
99
+        } catch (FileNotFoundException e) {
100
+            // According to https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html
101
+            promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created, or it is a directory");
102 102
         } catch (Exception e) {
103
-            promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage());
103
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
104 104
         }
105 105
     }
106 106
 
@@ -110,24 +110,37 @@ public class RNFetchBlobFS {
110 110
      * @param data Array passed from JS context.
111 111
      * @param promise RCT Promise
112 112
      */
113
-    static public void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
114
-
113
+    static void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
115 114
         try {
116
-
117 115
             File f = new File(path);
118 116
             File dir = f.getParentFile();
119
-            if(!dir.exists())
120
-                dir.mkdirs();
117
+
118
+            if(!f.exists()) {
119
+                if(dir != null && !dir.exists()) {
120
+                    if (!dir.mkdirs()) {
121
+                        promise.reject("ENOTDIR", "Failed to create parent directory of '" + path + "'");
122
+                        return;
123
+                    }
124
+                }
125
+                if(!f.createNewFile()) {
126
+                    promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created");
127
+                    return;
128
+                }
129
+            }
130
+
121 131
             FileOutputStream os = new FileOutputStream(f, append);
122
-            byte [] bytes = new byte[data.size()];
132
+            byte[] bytes = new byte[data.size()];
123 133
             for(int i=0;i<data.size();i++) {
124 134
                 bytes[i] = (byte) data.getInt(i);
125 135
             }
126 136
             os.write(bytes);
127 137
             os.close();
128 138
             promise.resolve(data.size());
139
+        } catch (FileNotFoundException e) {
140
+            // According to https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html
141
+            promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created");
129 142
         } catch (Exception e) {
130
-            promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage());
143
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
131 144
         }
132 145
     }
133 146
 
@@ -135,48 +148,60 @@ public class RNFetchBlobFS {
135 148
      * Read file with a buffer that has the same size as the target file.
136 149
      * @param path  Path of the file.
137 150
      * @param encoding  Encoding of read stream.
138
-     * @param promise
151
+     * @param promise  JS promise
139 152
      */
140
-    static public void readFile(String path, String encoding, final Promise promise ) {
153
+    static void readFile(String path, String encoding, final Promise promise) {
141 154
         String resolved = normalizePath(path);
142 155
         if(resolved != null)
143 156
             path = resolved;
144 157
         try {
145 158
             byte[] bytes;
159
+            int bytesRead;
160
+            int length;  // max. array length limited to "int", also see https://stackoverflow.com/a/10787175/544779
146 161
 
147 162
             if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
148 163
                 String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
149
-                long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
150
-                bytes = new byte[(int) length];
164
+                // This fails should an asset file be >2GB
165
+                length = (int) RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
166
+                bytes = new byte[length];
151 167
                 InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
152
-                in.read(bytes, 0, (int) length);
168
+                bytesRead = in.read(bytes, 0, length);
153 169
                 in.close();
154 170
             }
155 171
             // issue 287
156 172
             else if(resolved == null) {
157 173
                 InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
158
-                int length = (int) in.available();
174
+                // TODO See https://developer.android.com/reference/java/io/InputStream.html#available()
175
+                // Quote: "Note that while some implementations of InputStream will return the total number of bytes
176
+                // in the stream, many will not. It is never correct to use the return value of this method to
177
+                // allocate a buffer intended to hold all data in this stream."
178
+                length = in.available();
159 179
                 bytes = new byte[length];
160
-                in.read(bytes);
180
+                bytesRead = in.read(bytes);
161 181
                 in.close();
162 182
             }
163 183
             else {
164 184
                 File f = new File(path);
165
-                int length = (int) f.length();
185
+                length = (int) f.length();
166 186
                 bytes = new byte[length];
167 187
                 FileInputStream in = new FileInputStream(f);
168
-                in.read(bytes);
188
+                bytesRead = in.read(bytes);
169 189
                 in.close();
170 190
             }
171 191
 
192
+            if (bytesRead < length) {
193
+                promise.reject("EUNSPECIFIED", "Read only " + bytesRead + " bytes of " + length);
194
+                return;
195
+            }
196
+
172 197
             switch (encoding.toLowerCase()) {
173 198
                 case "base64" :
174 199
                     promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
175 200
                     break;
176 201
                 case "ascii" :
177 202
                     WritableArray asciiResult = Arguments.createArray();
178
-                    for(byte b : bytes) {
179
-                        asciiResult.pushInt((int)b);
203
+                    for (byte b : bytes) {
204
+                        asciiResult.pushInt((int) b);
180 205
                     }
181 206
                     promise.resolve(asciiResult);
182 207
                     break;
@@ -188,8 +213,16 @@ public class RNFetchBlobFS {
188 213
                     break;
189 214
             }
190 215
         }
216
+        catch(FileNotFoundException err) {
217
+            String msg = err.getLocalizedMessage();
218
+            if (msg.contains("EISDIR")) {
219
+                promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory; " +  msg);
220
+            } else {
221
+                promise.reject("ENOENT", "No such file '" + path + "'; " + msg);
222
+            }
223
+        }
191 224
         catch(Exception err) {
192
-            promise.reject("ReadFile Error", err.getLocalizedMessage());
225
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
193 226
         }
194 227
 
195 228
     }
@@ -198,7 +231,7 @@ public class RNFetchBlobFS {
198 231
      * Static method that returns system folders to JS context
199 232
      * @param ctx   React Native application context
200 233
      */
201
-    static public Map<String, Object> getSystemfolders(ReactApplicationContext ctx) {
234
+    static Map<String, Object> getSystemfolders(ReactApplicationContext ctx) {
202 235
         Map<String, Object> res = new HashMap<>();
203 236
 
204 237
         res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
@@ -247,11 +280,10 @@ public class RNFetchBlobFS {
247 280
 
248 281
     /**
249 282
      * Static method that returns a temp file path
250
-     * @param ctx   React Native application context
251 283
      * @param taskId    An unique string for identify
252
-     * @return
284
+     * @return String
253 285
      */
254
-    static public String getTmpPath(ReactApplicationContext ctx, String taskId) {
286
+    static String getTmpPath(String taskId) {
255 287
         return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId;
256 288
     }
257 289
 
@@ -261,12 +293,12 @@ public class RNFetchBlobFS {
261 293
      * @param encoding  File stream decoder, should be one of `base64`, `utf8`, `ascii`
262 294
      * @param bufferSize    Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
263 295
      */
264
-    public void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
296
+    void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
265 297
         String resolved = normalizePath(path);
266 298
         if(resolved != null)
267 299
             path = resolved;
268
-        try {
269 300
 
301
+        try {
270 302
             int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
271 303
             if(bufferSize > 0)
272 304
                 chunkSize = bufferSize;
@@ -275,7 +307,6 @@ public class RNFetchBlobFS {
275 307
 
276 308
             if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
277 309
                 fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
278
-
279 310
             }
280 311
             // fix issue 287
281 312
             else if(resolved == null) {
@@ -312,10 +343,8 @@ public class RNFetchBlobFS {
312 343
             } else if (encoding.equalsIgnoreCase("base64")) {
313 344
                 while ((cursor = fs.read(buffer)) != -1) {
314 345
                     if(cursor < chunkSize) {
315
-                        byte [] copy = new byte[cursor];
316
-                        for(int i =0;i<cursor;i++) {
317
-                            copy[i] = buffer[i];
318
-                        }
346
+                        byte[] copy = new byte[cursor];
347
+                        System.arraycopy(buffer, 0, copy, 0, cursor);
319 348
                         emitStreamEvent(streamId, "data", Base64.encodeToString(copy, Base64.NO_WRAP));
320 349
                     }
321 350
                     else
@@ -324,8 +353,12 @@ public class RNFetchBlobFS {
324 353
                         SystemClock.sleep(tick);
325 354
                 }
326 355
             } else {
327
-                String msg = "unrecognized encoding `" + encoding + "`";
328
-                emitStreamEvent(streamId, "error", msg);
356
+                emitStreamEvent(
357
+                        streamId,
358
+                        "error",
359
+                        "EINVAL",
360
+                        "Unrecognized encoding `" + encoding + "`, should be one of `base64`, `utf8`, `ascii`"
361
+                );
329 362
                 error = true;
330 363
             }
331 364
 
@@ -333,9 +366,20 @@ public class RNFetchBlobFS {
333 366
                 emitStreamEvent(streamId, "end", "");
334 367
             fs.close();
335 368
             buffer = null;
336
-
369
+        } catch (FileNotFoundException err) {
370
+            emitStreamEvent(
371
+                    streamId,
372
+                    "error",
373
+                    "ENOENT",
374
+                    "No such file '" + path + "'"
375
+            );
337 376
         } catch (Exception err) {
338
-            emitStreamEvent(streamId, "warn", "Failed to convert data to "+encoding+" encoded string, this might due to the source data is not able to convert using this encoding.");
377
+            emitStreamEvent(
378
+                    streamId,
379
+                    "error",
380
+                    "EUNSPECIFIED",
381
+                    "Failed to convert data to " + encoding + " encoded string. This might be because this encoding cannot be used for this data."
382
+            );
339 383
             err.printStackTrace();
340 384
         }
341 385
     }
@@ -343,28 +387,40 @@ public class RNFetchBlobFS {
343 387
     /**
344 388
      * Create a write stream and store its instance in RNFetchBlobFS.fileStreams
345 389
      * @param path  Target file path
346
-     * @param encoding Should be one of `base64`, `utf8`, `ascii`
347
-     * @param append    Flag represents if the file stream overwrite existing content
348
-     * @param callback
390
+     * @param encoding  Should be one of `base64`, `utf8`, `ascii`
391
+     * @param append  Flag represents if the file stream overwrite existing content
392
+     * @param callback  Callback
349 393
      */
350
-    public void writeStream(String path, String encoding, boolean append, Callback callback) {
351
-        File dest = new File(path);
352
-        if(!dest.exists() || dest.isDirectory()) {
353
-            callback.invoke("write stream error: target path `" + path + "` may not exists or it's a folder");
354
-            return;
355
-        }
394
+    void writeStream(String path, String encoding, boolean append, Callback callback) {
356 395
         try {
396
+            File dest = new File(path);
397
+            File dir = dest.getParentFile();
398
+
399
+            if(!dest.exists()) {
400
+                if(dir != null && !dir.exists()) {
401
+                    if (!dir.mkdirs()) {
402
+                        callback.invoke("ENOTDIR", "Failed to create parent directory of '" + path + "'");
403
+                        return;
404
+                    }
405
+                }
406
+                if(!dest.createNewFile()) {
407
+                    callback.invoke("ENOENT", "File '" + path + "' does not exist and could not be created");
408
+                    return;
409
+                }
410
+            } else if(dest.isDirectory()) {
411
+                callback.invoke("EISDIR", "Expecting a file but '" + path + "' is a directory");
412
+                return;
413
+            }
414
+
357 415
             OutputStream fs = new FileOutputStream(path, append);
358 416
             this.encoding = encoding;
359
-            this.append = append;
360 417
             String streamId = UUID.randomUUID().toString();
361 418
             RNFetchBlobFS.fileStreams.put(streamId, this);
362 419
             this.writeStreamInstance = fs;
363
-            callback.invoke(null, streamId);
420
+            callback.invoke(null, null, streamId);
364 421
         } catch(Exception err) {
365
-            callback.invoke("write stream error: failed to create write stream at path `"+path+"` "+ err.getLocalizedMessage());
422
+            callback.invoke("EUNSPECIFIED", "Failed to create write stream at path `" + path + "`; " + err.getLocalizedMessage());
366 423
         }
367
-
368 424
     }
369 425
 
370 426
     /**
@@ -374,11 +430,9 @@ public class RNFetchBlobFS {
374 430
      * @param callback JS context callback
375 431
      */
376 432
     static void writeChunk(String streamId, String data, Callback callback) {
377
-
378 433
         RNFetchBlobFS fs = fileStreams.get(streamId);
379 434
         OutputStream stream = fs.writeStreamInstance;
380
-        byte [] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
381
-
435
+        byte[] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
382 436
         try {
383 437
             stream.write(chunk);
384 438
             callback.invoke();
@@ -394,11 +448,10 @@ public class RNFetchBlobFS {
394 448
      * @param callback JS context callback
395 449
      */
396 450
     static void writeArrayChunk(String streamId, ReadableArray data, Callback callback) {
397
-
398 451
         try {
399 452
             RNFetchBlobFS fs = fileStreams.get(streamId);
400 453
             OutputStream stream = fs.writeStreamInstance;
401
-            byte [] chunk = new byte[data.size()];
454
+            byte[] chunk = new byte[data.size()];
402 455
             for(int i =0; i< data.size();i++) {
403 456
                 chunk[i] = (byte) data.getInt(i);
404 457
             }
@@ -436,35 +489,51 @@ public class RNFetchBlobFS {
436 489
             RNFetchBlobFS.deleteRecursive(new File(path));
437 490
             callback.invoke(null, true);
438 491
         } catch(Exception err) {
439
-            if(err != null)
440 492
             callback.invoke(err.getLocalizedMessage(), false);
441 493
         }
442 494
     }
443 495
 
444
-    static void deleteRecursive(File fileOrDirectory) {
445
-
496
+    private static void deleteRecursive(File fileOrDirectory) throws IOException {
446 497
         if (fileOrDirectory.isDirectory()) {
447
-            for (File child : fileOrDirectory.listFiles()) {
448
-                deleteRecursive(child);
498
+            File[] files = fileOrDirectory.listFiles();
499
+            if (files == null) {
500
+                throw new NullPointerException("Received null trying to list files of directory '" + fileOrDirectory + "'");
501
+            } else {
502
+                for (File child : files) {
503
+                    deleteRecursive(child);
504
+                }
449 505
             }
450 506
         }
451
-        fileOrDirectory.delete();
507
+        boolean result = fileOrDirectory.delete();
508
+        if (!result) {
509
+            throw new IOException("Failed to delete '" + fileOrDirectory + "'");
510
+        }
452 511
     }
453 512
 
454 513
     /**
455 514
      * Make a folder
456
-     * @param path Source path
457
-     * @param callback  JS context callback
515
+     * @param path  Source path
516
+     * @param promise  JS promise
458 517
      */
459
-    static void mkdir(String path, Callback callback) {
518
+    static void mkdir(String path, Promise promise) {
460 519
         File dest = new File(path);
461 520
         if(dest.exists()) {
462
-            callback.invoke("mkdir error: failed to create folder at `" + path + "` folder already exists");
521
+            promise.reject("EEXIST", dest.isDirectory() ? "Folder" : "File" + " '" + path + "' already exists");
463 522
             return;
464 523
         }
465
-        dest.mkdirs();
466
-        callback.invoke();
524
+        try {
525
+            boolean result = dest.mkdirs();
526
+            if (!result) {
527
+                promise.reject("EUNSPECIFIED", "mkdir failed to create some or all directories in '" + path + "'");
528
+                return;
529
+            }
530
+        } catch (Exception e) {
531
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
532
+            return;
533
+        }
534
+        promise.resolve(true);
467 535
     }
536
+
468 537
     /**
469 538
      * Copy file to destination path
470 539
      * @param path Source path
@@ -472,19 +541,22 @@ public class RNFetchBlobFS {
472 541
      * @param callback  JS context callback
473 542
      */
474 543
     static void cp(String path, String dest, Callback callback) {
475
-
476 544
         path = normalizePath(path);
477 545
         InputStream in = null;
478 546
         OutputStream out = null;
479 547
 
480 548
         try {
481
-
482 549
             if(!isPathExists(path)) {
483
-                callback.invoke("cp error: source file at path`" + path + "` not exists");
550
+                callback.invoke("Source file at path`" + path + "` does not exist");
484 551
                 return;
485 552
             }
486
-            if(!new File(dest).exists())
487
-                new File(dest).createNewFile();
553
+            if(!new File(dest).exists()) {
554
+                boolean result = new File(dest).createNewFile();
555
+                if (!result) {
556
+                    callback.invoke("Destination file at '" + dest + "' already exists");
557
+                    return;
558
+                }
559
+            }
488 560
 
489 561
             in = inputStreamFromPath(path);
490 562
             out = new FileOutputStream(dest);
@@ -494,7 +566,6 @@ public class RNFetchBlobFS {
494 566
             while ((len = in.read(buf)) > 0) {
495 567
                 out.write(buf, 0, len);
496 568
             }
497
-
498 569
         } catch (Exception err) {
499 570
             callback.invoke(err.getLocalizedMessage());
500 571
         } finally {
@@ -521,10 +592,19 @@ public class RNFetchBlobFS {
521 592
     static void mv(String path, String dest, Callback callback) {
522 593
         File src = new File(path);
523 594
         if(!src.exists()) {
524
-            callback.invoke("mv error: source file at path `" + path + "` does not exists");
595
+            callback.invoke("Source file at path `" + path + "` does not exist");
596
+            return;
597
+        }
598
+        try {
599
+            boolean result = src.renameTo(new File(dest));
600
+            if (!result) {
601
+                callback.invoke("mv failed for unknown reasons");
602
+                return;
603
+            }
604
+        } catch (Exception e) {
605
+            callback.invoke(e.getLocalizedMessage());
525 606
             return;
526 607
         }
527
-        src.renameTo(new File(dest));
528 608
         callback.invoke();
529 609
     }
530 610
 
@@ -534,11 +614,10 @@ public class RNFetchBlobFS {
534 614
      * @param callback  JS context callback
535 615
      */
536 616
     static void exists(String path, Callback callback) {
537
-
538 617
         if(isAsset(path)) {
539 618
             try {
540 619
                 String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
541
-                AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(filename);
620
+                com.RNFetchBlob.RNFetchBlob.RCTContext.getAssets().openFd(filename);
542 621
                 callback.invoke(true, false);
543 622
             } catch (IOException e) {
544 623
                 callback.invoke(false, false);
@@ -557,48 +636,67 @@ public class RNFetchBlobFS {
557 636
      * @param path Target folder
558 637
      * @param callback  JS context callback
559 638
      */
560
-    static void ls(String path, Callback callback) {
561
-        path = normalizePath(path);
562
-        File src = new File(path);
563
-        if (!src.exists() || !src.isDirectory()) {
564
-            callback.invoke("ls error: failed to list path `" + path + "` for it is not exist or it is not a folder");
565
-            return;
566
-        }
567
-        String[] files = new File(path).list();
568
-        WritableArray arg = Arguments.createArray();
569
-        for (String i : files) {
570
-            arg.pushString(i);
639
+    static void ls(String path, Promise promise) {
640
+        try {
641
+            path = normalizePath(path);
642
+            File src = new File(path);
643
+            if (!src.exists()) {
644
+                promise.reject("ENOENT", "No such file '" + path + "'");
645
+                return;
646
+            }
647
+            if (!src.isDirectory()) {
648
+                promise.reject("ENOTDIR", "Not a directory '" + path + "'");
649
+                return;
650
+            }
651
+            String[] files = new File(path).list();
652
+            WritableArray arg = Arguments.createArray();
653
+            // File => list(): "If this abstract pathname does not denote a directory, then this method returns null."
654
+            // We excluded that possibility above - ignore the "can produce NullPointerException" warning of the IDE.
655
+            for (String i : files) {
656
+                arg.pushString(i);
657
+            }
658
+            promise.resolve(arg);
659
+        } catch (Exception e) {
660
+            e.printStackTrace();
661
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
571 662
         }
572
-        callback.invoke(null, arg);
573 663
     }
574 664
 
575 665
     /**
576 666
      * Create a file by slicing given file path
577
-     * @param src   Source file path
667
+     * @param path   Source file path
578 668
      * @param dest  Destination of created file
579 669
      * @param start Start byte offset in source file
580 670
      * @param end   End byte offset
581 671
      * @param encode NOT IMPLEMENTED
582 672
      */
583
-    public static void slice(String src, String dest, int start, int end, String encode, Promise promise) {
673
+    static void slice(String path, String dest, int start, int end, String encode, Promise promise) {
584 674
         try {
585
-            src = normalizePath(src);
586
-            File source = new File(src);
587
-            if(!source.exists()) {
588
-                promise.reject("RNFetchBlob.slice error", "source file : " + src + " not exists");
675
+            path = normalizePath(path);
676
+            File source = new File(path);
677
+            if(source.isDirectory()){
678
+                promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory");
589 679
                 return;
590 680
             }
591
-            long size = source.length();
592
-            long max = Math.min(size, end);
593
-            long expected = max - start;
594
-            long now = 0;
595
-            FileInputStream in = new FileInputStream(new File(src));
681
+            if(!source.exists()){
682
+                promise.reject("ENOENT", "No such file '" + path + "'");
683
+                return;
684
+            }
685
+            int size = (int) source.length();
686
+            int max = Math.min(size, end);
687
+            int expected = max - start;
688
+            int now = 0;
689
+            FileInputStream in = new FileInputStream(new File(path));
596 690
             FileOutputStream out = new FileOutputStream(new File(dest));
597
-            in.skip(start);
598
-            byte [] buffer = new byte[10240];
691
+            int skipped = (int) in.skip(start);
692
+            if (skipped != start) {
693
+                promise.reject("EUNSPECIFIED", "Skipped " + skipped + " instead of the specified " + start + " bytes, size is " + size);
694
+                return;
695
+            }
696
+            byte[] buffer = new byte[10240];
599 697
             while(now < expected) {
600
-                long read = in.read(buffer, 0, 10240);
601
-                long remain = expected - now;
698
+                int read = in.read(buffer, 0, 10240);
699
+                int remain = expected - now;
602 700
                 if(read <= 0) {
603 701
                     break;
604 702
                 }
@@ -611,7 +709,7 @@ public class RNFetchBlobFS {
611 709
             promise.resolve(dest);
612 710
         } catch (Exception e) {
613 711
             e.printStackTrace();
614
-            promise.reject(e.getLocalizedMessage());
712
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
615 713
         }
616 714
     }
617 715
 
@@ -623,18 +721,20 @@ public class RNFetchBlobFS {
623 721
             protected Integer doInBackground(String ...args) {
624 722
                 WritableArray res = Arguments.createArray();
625 723
                 if(args[0] == null) {
626
-                    callback.invoke("lstat error: the path specified for lstat is either `null` or `undefined`.");
724
+                    callback.invoke("the path specified for lstat is either `null` or `undefined`.");
627 725
                     return 0;
628 726
                 }
629 727
                 File src = new File(args[0]);
630 728
                 if(!src.exists()) {
631
-                    callback.invoke("lstat error: failed to list path `" + args[0] + "` for it is not exist or it is not a folder");
729
+                    callback.invoke("failed to lstat path `" + args[0] + "` because it does not exist or it is not a folder");
632 730
                     return 0;
633 731
                 }
634 732
                 if(src.isDirectory()) {
635 733
                     String [] files = src.list();
734
+                    // File => list(): "If this abstract pathname does not denote a directory, then this method returns null."
735
+                    // We excluded that possibility above - ignore the "can produce NullPointerException" warning of the IDE.
636 736
                     for(String p : files) {
637
-                        res.pushMap(statFile ( src.getPath() + "/" + p));
737
+                        res.pushMap(statFile(src.getPath() + "/" + p));
638 738
                     }
639 739
                 }
640 740
                 else {
@@ -648,15 +748,15 @@ public class RNFetchBlobFS {
648 748
 
649 749
     /**
650 750
      * show status of a file or directory
651
-     * @param path
652
-     * @param callback
751
+     * @param path  Path
752
+     * @param callback  Callback
653 753
      */
654 754
     static void stat(String path, Callback callback) {
655 755
         try {
656 756
             path = normalizePath(path);
657 757
             WritableMap result = statFile(path);
658 758
             if(result == null)
659
-                callback.invoke("stat error: failed to list path `" + path + "` for it is not exist or it is not a folder", null);
759
+                callback.invoke("failed to stat path `" + path + "` because it does not exist or it is not a folder", null);
660 760
             else
661 761
                 callback.invoke(null, result);
662 762
         } catch(Exception err) {
@@ -666,8 +766,8 @@ public class RNFetchBlobFS {
666 766
 
667 767
     /**
668 768
      * Basic stat method
669
-     * @param path
670
-     * @return Stat result of a file or path
769
+     * @param path  Path
770
+     * @return Stat  Result of a file or path
671 771
      */
672 772
     static WritableMap statFile(String path) {
673 773
         try {
@@ -703,9 +803,9 @@ public class RNFetchBlobFS {
703 803
 
704 804
     /**
705 805
      * Media scanner scan file
706
-     * @param path
707
-     * @param mimes
708
-     * @param callback
806
+     * @param path  Path to file
807
+     * @param mimes  Array of MIME type strings
808
+     * @param callback  Callback for results
709 809
      */
710 810
     void scanFile(String [] path, String[] mimes, final Callback callback) {
711 811
         try {
@@ -720,14 +820,63 @@ public class RNFetchBlobFS {
720 820
         }
721 821
     }
722 822
 
823
+    static void hash(String path, String algorithm, Promise promise) {
824
+        try {
825
+            Map<String, String> algorithms = new HashMap<>();
826
+
827
+            algorithms.put("md5", "MD5");
828
+            algorithms.put("sha1", "SHA-1");
829
+            algorithms.put("sha224", "SHA-224");
830
+            algorithms.put("sha256", "SHA-256");
831
+            algorithms.put("sha384", "SHA-384");
832
+            algorithms.put("sha512", "SHA-512");
833
+
834
+            if (!algorithms.containsKey(algorithm)) {
835
+                promise.reject("EINVAL", "Invalid algorithm '" + algorithm + "', must be one of md5, sha1, sha224, sha256, sha384, sha512");
836
+                return;
837
+            }
838
+
839
+            File file = new File(path);
840
+
841
+            if (file.isDirectory()) {
842
+                promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory");
843
+                return;
844
+            }
845
+
846
+            if (!file.exists()) {
847
+                promise.reject("ENOENT", "No such file '" + path + "'");
848
+                return;
849
+            }
850
+
851
+            MessageDigest md = MessageDigest.getInstance(algorithms.get(algorithm));
852
+
853
+            FileInputStream inputStream = new FileInputStream(path);
854
+            byte[] buffer = new byte[(int)file.length()];
855
+
856
+            int read;
857
+            while ((read = inputStream.read(buffer)) != -1) {
858
+                md.update(buffer, 0, read);
859
+            }
860
+
861
+            StringBuilder hexString = new StringBuilder();
862
+            for (byte digestByte : md.digest())
863
+                hexString.append(String.format("%02x", digestByte));
864
+
865
+            promise.resolve(hexString.toString());
866
+        } catch (Exception e) {
867
+            e.printStackTrace();
868
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
869
+        }
870
+    }
871
+
723 872
     /**
724 873
      * Create new file at path
725 874
      * @param path The destination path of the new file.
726 875
      * @param data Initial data of the new file.
727 876
      * @param encoding Encoding of initial data.
728
-     * @param callback RCT bridge callback.
877
+     * @param promise Promise for Javascript
729 878
      */
730
-    static void createFile(String path, String data, String encoding, Callback callback) {
879
+    static void createFile(String path, String data, String encoding, Promise promise) {
731 880
         try {
732 881
             File dest = new File(path);
733 882
             boolean created = dest.createNewFile();
@@ -735,31 +884,30 @@ public class RNFetchBlobFS {
735 884
                 String orgPath = data.replace(RNFetchBlobConst.FILE_PREFIX, "");
736 885
                 File src = new File(orgPath);
737 886
                 if(!src.exists()) {
738
-                    callback.invoke("RNfetchBlob writeFileError", "source file : " + data + "not exists");
887
+                    promise.reject("ENOENT", "Source file : " + data + " does not exist");
739 888
                     return ;
740 889
                 }
741 890
                 FileInputStream fin = new FileInputStream(src);
742 891
                 OutputStream ostream = new FileOutputStream(dest);
743
-                byte [] buffer = new byte [10240];
892
+                byte[] buffer = new byte[10240];
744 893
                 int read = fin.read(buffer);
745
-                while(read > 0) {
894
+                while (read > 0) {
746 895
                     ostream.write(buffer, 0, read);
747 896
                     read = fin.read(buffer);
748 897
                 }
749 898
                 fin.close();
750 899
                 ostream.close();
751
-            }
752
-            else {
900
+            } else {
753 901
                 if (!created) {
754
-                    callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists, or the file already exists. If you intended to overwrite the existing file use fs.writeFile instead.");
902
+                    promise.reject("EEXIST", "File `" + path + "` already exists");
755 903
                     return;
756 904
                 }
757 905
                 OutputStream ostream = new FileOutputStream(dest);
758 906
                 ostream.write(RNFetchBlobFS.stringToBytes(data, encoding));
759 907
             }
760
-            callback.invoke(null, path);
908
+            promise.resolve(path);
761 909
         } catch(Exception err) {
762
-            callback.invoke(err.getLocalizedMessage());
910
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
763 911
         }
764 912
     }
765 913
 
@@ -767,30 +915,25 @@ public class RNFetchBlobFS {
767 915
      * Create file for ASCII encoding
768 916
      * @param path  Path of new file.
769 917
      * @param data  Content of new file
770
-     * @param callback  JS context callback
918
+     * @param promise  JS Promise
771 919
      */
772
-    static void createFileASCII(String path, ReadableArray data, Callback callback) {
920
+    static void createFileASCII(String path, ReadableArray data, Promise promise) {
773 921
         try {
774 922
             File dest = new File(path);
775
-            if(dest.exists()) {
776
-                callback.invoke("create file error: failed to create file at path `" + path + "`, file already exists.");
777
-                return;
778
-            }
779 923
             boolean created = dest.createNewFile();
780 924
             if(!created) {
781
-                callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists");
925
+                promise.reject("EEXIST", "File at path `" + path + "` already exists");
782 926
                 return;
783 927
             }
784 928
             OutputStream ostream = new FileOutputStream(dest);
785
-            byte [] chunk = new byte[data.size()];
786
-            for(int i =0; i<data.size();i++) {
929
+            byte[] chunk = new byte[data.size()];
930
+            for(int i=0; i<data.size(); i++) {
787 931
                 chunk[i] = (byte) data.getInt(i);
788 932
             }
789 933
             ostream.write(chunk);
790
-            chunk = null;
791
-            callback.invoke(null, path);
934
+            promise.resolve(path);
792 935
         } catch(Exception err) {
793
-            callback.invoke(err.getLocalizedMessage());
936
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
794 937
         }
795 938
     }
796 939
 
@@ -814,17 +957,31 @@ public class RNFetchBlobFS {
814 957
      * @param callback JS contest callback
815 958
      */
816 959
     static void removeSession(ReadableArray paths, final Callback callback) {
817
-
818 960
         AsyncTask<ReadableArray, Integer, Integer> task = new AsyncTask<ReadableArray, Integer, Integer>() {
819 961
             @Override
820 962
             protected Integer doInBackground(ReadableArray ...paths) {
821 963
                 try {
964
+                    ArrayList<String> failuresToDelete = new ArrayList<>();
822 965
                     for (int i = 0; i < paths[0].size(); i++) {
823
-                        File f = new File(paths[0].getString(i));
824
-                        if (f.exists())
825
-                            f.delete();
966
+                        String fileName = paths[0].getString(i);
967
+                        File f = new File(fileName);
968
+                        if (f.exists()) {
969
+                            boolean result = f.delete();
970
+                            if (!result) {
971
+                                failuresToDelete.add(fileName);
972
+                            }
973
+                        }
974
+                    }
975
+                    if (failuresToDelete.isEmpty()) {
976
+                        callback.invoke(null, true);
977
+                    } else {
978
+                        StringBuilder listString = new StringBuilder();
979
+                        listString.append("Failed to delete: ");
980
+                        for (String s : failuresToDelete) {
981
+                            listString.append(s).append(", ");
982
+                        }
983
+                        callback.invoke(listString.toString());
826 984
                     }
827
-                    callback.invoke(null, true);
828 985
                 } catch(Exception err) {
829 986
                     callback.invoke(err.getLocalizedMessage());
830 987
                 }
@@ -867,6 +1024,7 @@ public class RNFetchBlobFS {
867 1024
         this.emitter.emit(streamName, eventData);
868 1025
     }
869 1026
 
1027
+    // "event" always is "data"...
870 1028
     private void emitStreamEvent(String streamName, String event, WritableArray data) {
871 1029
         WritableMap eventData = Arguments.createMap();
872 1030
         eventData.putString("event", event);
@@ -874,12 +1032,13 @@ public class RNFetchBlobFS {
874 1032
         this.emitter.emit(streamName, eventData);
875 1033
     }
876 1034
 
877
-    // TODO : should we remove this ?
878
-    void emitFSData(String taskId, String event, String data) {
1035
+    // "event" always is "error"...
1036
+    private void emitStreamEvent(String streamName, String event, String code, String message) {
879 1037
         WritableMap eventData = Arguments.createMap();
880 1038
         eventData.putString("event", event);
881
-        eventData.putString("detail", data);
882
-        this.emitter.emit("RNFetchBlobStream" + taskId, eventData);
1039
+        eventData.putString("code", code);
1040
+        eventData.putString("detail", message);
1041
+        this.emitter.emit(streamName, eventData);
883 1042
     }
884 1043
 
885 1044
     /**
@@ -887,9 +1046,9 @@ public class RNFetchBlobFS {
887 1046
      * the stream is created by Assets Manager, otherwise use FileInputStream.
888 1047
      * @param path The file to open stream
889 1048
      * @return InputStream instance
890
-     * @throws IOException
1049
+     * @throws IOException If the given file does not exist or is a directory FileInputStream will throw a FileNotFoundException
891 1050
      */
892
-    static InputStream inputStreamFromPath(String path) throws IOException {
1051
+    private static InputStream inputStreamFromPath(String path) throws IOException {
893 1052
         if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
894 1053
             return RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
895 1054
         }
@@ -901,10 +1060,10 @@ public class RNFetchBlobFS {
901 1060
      * @param path A file path URI string
902 1061
      * @return A boolean value represents if the path exists.
903 1062
      */
904
-    static boolean isPathExists(String path) {
1063
+    private static boolean isPathExists(String path) {
905 1064
         if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
906 1065
             try {
907
-                RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
1066
+                RNFetchBlob.RCTContext.getAssets().open(path.replace(com.RNFetchBlob.RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
908 1067
             } catch (IOException e) {
909 1068
                 return false;
910 1069
             }
@@ -917,9 +1076,7 @@ public class RNFetchBlobFS {
917 1076
     }
918 1077
 
919 1078
     static boolean isAsset(String path) {
920
-        if(path != null)
921
-            return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
922
-        return false;
1079
+        return path != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
923 1080
     }
924 1081
 
925 1082
     /**

+ 7
- 7
android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java View File

@@ -5,17 +5,17 @@ package com.RNFetchBlob;
5 5
  */
6 6
 public class RNFetchBlobProgressConfig {
7 7
 
8
-    public enum ReportType {
8
+    enum ReportType {
9 9
         Upload,
10 10
         Download
11 11
     };
12 12
 
13
-    long lastTick = 0;
14
-    int tick = 0;
15
-    int count = -1;
16
-    public int interval = -1;
17
-    public boolean enable = false;
18
-    public ReportType type = ReportType.Download;
13
+    private long lastTick = 0;
14
+    private int tick = 0;
15
+    private int count = -1;
16
+    private int interval = -1;
17
+    private boolean enable = false;
18
+    private ReportType type = ReportType.Download;
19 19
 
20 20
     RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
21 21
         this.enable = report;

+ 46
- 14
android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -8,6 +8,7 @@ import android.content.IntentFilter;
8 8
 import android.database.Cursor;
9 9
 import android.net.Uri;
10 10
 import android.os.Build;
11
+import android.support.annotation.NonNull;
11 12
 import android.util.Base64;
12 13
 
13 14
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
@@ -22,8 +23,11 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator;
22 23
 import com.facebook.react.bridge.WritableArray;
23 24
 import com.facebook.react.bridge.WritableMap;
24 25
 import com.facebook.react.modules.core.DeviceEventManagerModule;
25
-import com.facebook.react.modules.network.OkHttpClientProvider;
26
-import com.facebook.react.modules.network.TLSSocketFactory;
26
+
27
+import javax.net.ssl.TrustManagerFactory;
28
+import javax.net.ssl.TrustManager;
29
+import javax.net.ssl.X509TrustManager;
30
+import javax.net.ssl.SSLContext;
27 31
 
28 32
 import java.io.File;
29 33
 import java.io.FileOutputStream;
@@ -37,12 +41,16 @@ import java.nio.ByteBuffer;
37 41
 import java.nio.charset.CharacterCodingException;
38 42
 import java.nio.charset.Charset;
39 43
 import java.nio.charset.CharsetEncoder;
44
+import java.security.KeyStore;
40 45
 import java.util.ArrayList;
46
+import java.util.Arrays;
41 47
 import java.util.List;
42 48
 import java.util.HashMap;
43 49
 
44 50
 import java.util.concurrent.TimeUnit;
45 51
 
52
+import 	javax.net.ssl.SSLSocketFactory;
53
+
46 54
 import okhttp3.Call;
47 55
 import okhttp3.ConnectionPool;
48 56
 import okhttp3.ConnectionSpec;
@@ -79,11 +87,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
79 87
     }
80 88
 
81 89
     public static HashMap<String, Call> taskTable = new HashMap<>();
90
+    public static HashMap<String, Long> androidDownloadManagerTaskTable = new HashMap<>();
82 91
     static HashMap<String, RNFetchBlobProgressConfig> progressReport = new HashMap<>();
83 92
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
84 93
     static ConnectionPool pool = new ConnectionPool();
85 94
 
86
-    ReactApplicationContext ctx;
87 95
     RNFetchBlobConfig options;
88 96
     String taskId;
89 97
     String method;
@@ -135,6 +143,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
135 143
             call.cancel();
136 144
             taskTable.remove(taskId);
137 145
         }
146
+
147
+        if (androidDownloadManagerTaskTable.containsKey(taskId)) {
148
+            long downloadManagerIdForTaskId = androidDownloadManagerTaskTable.get(taskId).longValue();
149
+            Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
150
+            DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
151
+            dm.remove(downloadManagerIdForTaskId);
152
+        }
138 153
     }
139 154
 
140 155
     @Override
@@ -162,7 +177,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
162 177
                 }
163 178
                 // set headers
164 179
                 ReadableMapKeySetIterator it = headers.keySetIterator();
165
-                if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable") == true ) {
180
+                if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable")) {
166 181
                     req.allowScanningByMediaScanner();
167 182
                 }
168 183
                 while (it.hasNextKey()) {
@@ -172,6 +187,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
172 187
                 Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
173 188
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
174 189
                 downloadManagerId = dm.enqueue(req);
190
+                androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
175 191
                 appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
176 192
                 return;
177 193
             }
@@ -188,7 +204,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
188 204
                 cacheKey = this.taskId;
189 205
             }
190 206
 
191
-            File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext);
207
+            File file = new File(RNFetchBlobFS.getTmpPath(cacheKey) + ext);
192 208
 
193 209
             if (file.exists()) {
194 210
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
@@ -199,7 +215,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
199 215
         if(this.options.path != null)
200 216
             this.destPath = this.options.path;
201 217
         else if(this.options.fileCache)
202
-            this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
218
+            this.destPath = RNFetchBlobFS.getTmpPath(cacheKey) + ext;
203 219
 
204 220
 
205 221
         OkHttpClient.Builder clientBuilder;
@@ -321,7 +337,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
321 337
             // Add request interceptor for upload progress event
322 338
             clientBuilder.addInterceptor(new Interceptor() {
323 339
                 @Override
324
-                public Response intercept(Chain chain) throws IOException {
340
+                public Response intercept(@NonNull Chain chain) throws IOException {
325 341
                     try {
326 342
                         Response originalResponse = chain.proceed(req);
327 343
                         ResponseBody extended;
@@ -383,7 +399,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
383 399
             call.enqueue(new okhttp3.Callback() {
384 400
 
385 401
                 @Override
386
-                public void onFailure(Call call, IOException e) {
402
+                public void onFailure(@NonNull Call call, IOException e) {
387 403
                     cancelTask(taskId);
388 404
                     if(respInfo == null) {
389 405
                         respInfo = Arguments.createMap();
@@ -400,7 +416,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
400 416
                 }
401 417
 
402 418
                 @Override
403
-                public void onResponse(Call call, Response response) throws IOException {
419
+                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
404 420
                     ReadableMap notifyConfig = options.addAndroidDownloads;
405 421
                     // Download manager settings
406 422
                     if(notifyConfig != null ) {
@@ -438,6 +454,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
438 454
     private void releaseTaskResource() {
439 455
         if(taskTable.containsKey(taskId))
440 456
             taskTable.remove(taskId);
457
+        if(androidDownloadManagerTaskTable.containsKey(taskId))
458
+            androidDownloadManagerTaskTable.remove(taskId);
441 459
         if(uploadProgressReport.containsKey(taskId))
442 460
             uploadProgressReport.remove(taskId);
443 461
         if(progressReport.containsKey(taskId))
@@ -459,11 +477,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
459 477
                     // For XMLHttpRequest, automatic response data storing strategy, when response
460 478
                     // data is considered as binary data, write it to file system
461 479
                     if(isBlobResp && options.auto) {
462
-                        String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
480
+                        String dest = RNFetchBlobFS.getTmpPath(taskId);
463 481
                         InputStream ins = resp.body().byteStream();
464 482
                         FileOutputStream os = new FileOutputStream(new File(dest));
465 483
                         int read;
466
-                        byte [] buffer = new byte[10240];
484
+                        byte[] buffer = new byte[10240];
467 485
                         while ((read = ins.read(buffer)) != -1) {
468 486
                             os.write(buffer, 0, read);
469 487
                         }
@@ -635,6 +653,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
635 653
             Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
636 654
             long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
637 655
             if (id == this.downloadManagerId) {
656
+                releaseTaskResource(); // remove task ID from task map
657
+
638 658
                 DownloadManager.Query query = new DownloadManager.Query();
639 659
                 query.setFilterById(downloadManagerId);
640 660
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
@@ -657,11 +677,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
657 677
                             options.addAndroidDownloads.getString("mime").contains("image")) {
658 678
                         Uri uri = Uri.parse(contentUri);
659 679
                         Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
660
-
661
-                            // use default destination of DownloadManager
680
+                        // use default destination of DownloadManager
662 681
                         if (cursor != null) {
663 682
                             cursor.moveToFirst();
664 683
                             filePath = cursor.getString(0);
684
+                            cursor.close();
665 685
                         }
666 686
                     }
667 687
                 }
@@ -695,7 +715,19 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
695 715
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
696 716
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
697 717
             try {
698
-                client.sslSocketFactory(new TLSSocketFactory());
718
+                // Code from https://stackoverflow.com/a/40874952/544779
719
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
720
+                trustManagerFactory.init((KeyStore) null);
721
+                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
722
+                if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
723
+                    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
724
+                }
725
+                X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
726
+                SSLContext sslContext = SSLContext.getInstance("SSL");
727
+                sslContext.init(null, new TrustManager[] { trustManager }, null);
728
+                SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
729
+
730
+                client.sslSocketFactory(sslSocketFactory, trustManager);
699 731
 
700 732
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
701 733
                         .tlsVersions(TlsVersion.TLS_1_2)

+ 2
- 1
android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java View File

@@ -27,7 +27,7 @@ public class RNFetchBlobUtils {
27 27
             md.update(input.getBytes());
28 28
             byte[] digest = md.digest();
29 29
 
30
-            StringBuffer sb = new StringBuffer();
30
+            StringBuilder sb = new StringBuilder();
31 31
 
32 32
             for (byte b : digest) {
33 33
                 sb.append(String.format("%02x", b & 0xff));
@@ -37,6 +37,7 @@ public class RNFetchBlobUtils {
37 37
         } catch (Exception ex) {
38 38
             ex.printStackTrace();
39 39
         } finally {
40
+            // TODO: Is discarding errors the intent? (https://www.owasp.org/index.php/Return_Inside_Finally_Block)
40 41
             return result;
41 42
         }
42 43
 

+ 4
- 3
android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java View File

@@ -1,5 +1,6 @@
1 1
 package com.RNFetchBlob.Response;
2 2
 
3
+import android.support.annotation.NonNull;
3 4
 import android.util.Log;
4 5
 
5 6
 import com.RNFetchBlob.RNFetchBlobConst;
@@ -48,11 +49,11 @@ public class RNFetchBlobFileResp extends ResponseBody {
48 49
             File f = new File(path);
49 50
 
50 51
             File parent = f.getParentFile();
51
-            if(!parent.exists() && !parent.mkdirs()){
52
+            if(parent != null && !parent.exists() && !parent.mkdirs()){
52 53
                 throw new IllegalStateException("Couldn't create dir: " + parent);
53 54
             }
54 55
 
55
-            if(f.exists() == false)
56
+            if(!f.exists())
56 57
                 f.createNewFile();
57 58
             ofStream = new FileOutputStream(new File(path), appendToExistingFile);
58 59
         }
@@ -76,7 +77,7 @@ public class RNFetchBlobFileResp extends ResponseBody {
76 77
 
77 78
     private class ProgressReportingSource implements Source {
78 79
         @Override
79
-        public long read(Buffer sink, long byteCount) throws IOException {
80
+        public long read(@NonNull Buffer sink, long byteCount) throws IOException {
80 81
             try {
81 82
                 byte[] bytes = new byte[(int) byteCount];
82 83
                 long read = originalBody.byteStream().read(bytes, 0, (int) byteCount);

+ 5
- 3
class/RNFetchBlobReadStream.js View File

@@ -35,7 +35,7 @@ export default class RNFetchBlobReadStream {
35 35
 
36 36
     // register for file stream event
37 37
     let subscription = emitter.addListener(this.streamId, (e) => {
38
-      let {event, detail} = e
38
+      let {event, code, detail} = e
39 39
       if(this._onData && event === 'data') {
40 40
         this._onData(detail)
41 41
         return
@@ -44,10 +44,12 @@ export default class RNFetchBlobReadStream {
44 44
         this._onEnd(detail)
45 45
       }
46 46
       else {
47
+        const err = new Error(detail)
48
+        err.code = code || 'EUNSPECIFIED'
47 49
         if(this._onError)
48
-          this._onError(detail)
50
+          this._onError(err)
49 51
         else
50
-          throw new Error(detail)
52
+          throw err
51 53
       }
52 54
       // when stream closed or error, remove event handler
53 55
       if (event === 'error' || event === 'end') {

+ 2
- 7
class/RNFetchBlobSession.js View File

@@ -9,16 +9,11 @@ import {
9 9
 } from 'react-native'
10 10
 
11 11
 const RNFetchBlob = NativeModules.RNFetchBlob
12
-const emitter = DeviceEventEmitter
13 12
 
14 13
 let sessions = {}
15 14
 
16 15
 export default class RNFetchBlobSession {
17 16
 
18
-  add : (path:string) => RNFetchBlobSession;
19
-  remove : (path:string) => RNFetchBlobSession;
20
-  dispose : () => Promise;
21
-  list : () => Array<string>;
22 17
   name : string;
23 18
 
24 19
   static getSession(name:string):any {
@@ -50,7 +45,7 @@ export default class RNFetchBlobSession {
50 45
 
51 46
   remove(path:string):RNFetchBlobSession {
52 47
     let list = sessions[this.name]
53
-    for(let i in list) {
48
+    for(let i of list) {
54 49
       if(list[i] === path) {
55 50
         sessions[this.name].splice(i, 1)
56 51
         break;
@@ -67,7 +62,7 @@ export default class RNFetchBlobSession {
67 62
     return new Promise((resolve, reject) => {
68 63
       RNFetchBlob.removeSession(sessions[this.name], (err) => {
69 64
         if(err)
70
-          reject(err)
65
+          reject(new Error(err))
71 66
         else {
72 67
           delete sessions[this.name]
73 68
           resolve()

+ 8
- 9
class/RNFetchBlobWriteStream.js View File

@@ -9,36 +9,35 @@ import {
9 9
 } from 'react-native'
10 10
 
11 11
 const RNFetchBlob = NativeModules.RNFetchBlob
12
-const emitter = DeviceEventEmitter
13 12
 
14 13
 export default class RNFetchBlobWriteStream {
15 14
 
16 15
   id : string;
17 16
   encoding : string;
18
-  append : bool;
17
+  append : boolean;
19 18
 
20
-  constructor(streamId:string, encoding:string, append:string) {
19
+  constructor(streamId:string, encoding:string, append:boolean) {
21 20
     this.id = streamId
22 21
     this.encoding = encoding
23 22
     this.append = append
24 23
   }
25 24
 
26
-  write(data:string) {
25
+  write(data:string): Promise<RNFetchBlobWriteStream> {
27 26
     return new Promise((resolve, reject) => {
28 27
       try {
29 28
         let method = this.encoding === 'ascii' ? 'writeArrayChunk' : 'writeChunk'
30 29
         if(this.encoding.toLocaleLowerCase() === 'ascii' && !Array.isArray(data)) {
31
-            reject('ascii input data must be an Array')
30
+            reject(new Error('ascii input data must be an Array'))
32 31
             return
33 32
         }
34 33
         RNFetchBlob[method](this.id, data, (error) => {
35 34
           if(error)
36
-            reject(error)
35
+            reject(new Error(error))
37 36
           else
38
-            resolve()
37
+            resolve(this)
39 38
         })
40 39
       } catch(err) {
41
-        reject(err)
40
+        reject(new Error(err))
42 41
       }
43 42
     })
44 43
   }
@@ -50,7 +49,7 @@ export default class RNFetchBlobWriteStream {
50 49
           resolve()
51 50
         })
52 51
       } catch (err) {
53
-        reject(err)
52
+        reject(new Error(err))
54 53
       }
55 54
     })
56 55
   }

+ 191
- 166
fs.js View File

@@ -2,45 +2,42 @@
2 2
 // Use of this source code is governed by a MIT-style license that can be
3 3
 // found in the LICENSE file.
4 4
 
5
-import {
6
-  NativeModules,
7
-  DeviceEventEmitter,
8
-  Platform,
9
-  NativeAppEventEmitter,
10
-} from 'react-native'
5
+// import type {RNFetchBlobConfig, RNFetchBlobNative, RNFetchBlobStream} from './types'
6
+
7
+import {NativeModules, Platform} from 'react-native'
11 8
 import RNFetchBlobSession from './class/RNFetchBlobSession'
12 9
 import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream'
13 10
 import RNFetchBlobReadStream from './class/RNFetchBlobReadStream'
14 11
 import RNFetchBlobFile from './class/RNFetchBlobFile'
15
-import type {
16
-  RNFetchBlobNative,
17
-  RNFetchBlobConfig,
18
-  RNFetchBlobStream
19
-} from './types'
20
-
21
-const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
22
-const emitter = DeviceEventEmitter
12
+
13
+const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob
14
+
23 15
 const dirs = {
24
-    DocumentDir :  RNFetchBlob.DocumentDir,
25
-    CacheDir : RNFetchBlob.CacheDir,
26
-    PictureDir : RNFetchBlob.PictureDir,
27
-    MusicDir : RNFetchBlob.MusicDir,
28
-    MovieDir : RNFetchBlob.MovieDir,
29
-    DownloadDir : RNFetchBlob.DownloadDir,
30
-    DCIMDir : RNFetchBlob.DCIMDir,
31
-    get SDCardDir() {
32
-      console.warn('SDCardDir as a constant is deprecated and will be removed in feature release. ' +
33
-                   'Use RNFetchBlob.android.getSDCardDir():Promise instead.');
34
-      return RNFetchBlob.SDCardDir;
35
-    },
36
-    get SDCardApplicationDir() {
37
-      console.warn('SDCardApplicationDir as a constant is deprecated and will be removed in feature release. ' +
38
-                   'Use RNFetchBlob.android.getSDCardApplicationDir():Promise instead. ' +
39
-                   'This variable can be empty on error in native code.');
40
-      return RNFetchBlob.SDCardApplicationDir;
41
-    },
42
-    MainBundleDir : RNFetchBlob.MainBundleDir,
43
-    LibraryDir : RNFetchBlob.LibraryDir
16
+  DocumentDir :  RNFetchBlob.DocumentDir,
17
+  CacheDir : RNFetchBlob.CacheDir,
18
+  PictureDir : RNFetchBlob.PictureDir,
19
+  MusicDir : RNFetchBlob.MusicDir,
20
+  MovieDir : RNFetchBlob.MovieDir,
21
+  DownloadDir : RNFetchBlob.DownloadDir,
22
+  DCIMDir : RNFetchBlob.DCIMDir,
23
+  get SDCardDir() {
24
+    console.warn('SDCardDir as a constant is deprecated and will be removed in feature release. ' +
25
+                 'Use RNFetchBlob.android.getSDCardDir():Promise instead.');
26
+    return RNFetchBlob.SDCardDir;
27
+  },
28
+  get SDCardApplicationDir() {
29
+    console.warn('SDCardApplicationDir as a constant is deprecated and will be removed in feature release. ' +
30
+                 'Use RNFetchBlob.android.getSDCardApplicationDir():Promise instead. ' +
31
+                 'This variable can be empty on error in native code.');
32
+    return RNFetchBlob.SDCardApplicationDir;
33
+  },
34
+  MainBundleDir : RNFetchBlob.MainBundleDir,
35
+  LibraryDir : RNFetchBlob.LibraryDir
36
+}
37
+
38
+function addCode(code: string, error: Error): Error {
39
+  error.code = code
40
+  return error
44 41
 }
45 42
 
46 43
 /**
@@ -48,9 +45,9 @@ const dirs = {
48 45
  * @param  {string} name Stream ID
49 46
  * @return {RNFetchBlobSession}
50 47
  */
51
-function session(name:string):RNFetchBlobSession {
48
+function session(name: string): RNFetchBlobSession {
52 49
   let s = RNFetchBlobSession.getSession(name)
53
-  if(s)
50
+  if (s)
54 51
     return new RNFetchBlobSession(name)
55 52
   else {
56 53
     RNFetchBlobSession.setSession(name, [])
@@ -58,56 +55,48 @@ function session(name:string):RNFetchBlobSession {
58 55
   }
59 56
 }
60 57
 
61
-function asset(path:string):string {
62
-  if(Platform.OS === 'ios') {
58
+function asset(path: string): string {
59
+  if (Platform.OS === 'ios') {
63 60
     // path from camera roll
64
-    if(/^assets-library\:\/\//.test(path))
61
+    if (/^assets-library\:\/\//.test(path))
65 62
       return path
66 63
   }
67 64
   return 'bundle-assets://' + path
68 65
 }
69 66
 
70
-function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise {
71
-  encoding = encoding || 'utf8'
72
-  return new Promise((resolve, reject) => {
73
-    let handler = (err) => {
74
-      if(err)
75
-        reject(new Error(err))
76
-      else
77
-        resolve()
78
-    }
79
-    if(encoding.toLowerCase() === 'ascii') {
80
-      if(Array.isArray(data))
81
-        RNFetchBlob.createFileASCII(path, data, handler)
82
-      else
83
-        reject(new Error('`data` of ASCII file must be an array contains numbers'))
84
-    }
85
-    else {
86
-      RNFetchBlob.createFile(path, data, encoding, handler)
87
-    }
88
-  })
67
+function createFile(path: string, data: string, encoding: 'base64' | 'ascii' | 'utf8' = 'utf8'): Promise<string> {
68
+  if (encoding.toLowerCase() === 'ascii') {
69
+    return Array.isArray(data) ?
70
+      RNFetchBlob.createFileASCII(path, data) :
71
+      Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers')))
72
+  }
73
+  else {
74
+    return RNFetchBlob.createFile(path, data, encoding)
75
+  }
89 76
 }
90 77
 
91 78
 /**
92 79
  * Create write stream to a file.
93 80
  * @param  {string} path Target path of file stream.
94 81
  * @param  {string} encoding Encoding of input data.
95
- * @param  {bool} append  A flag represent if data append to existing ones.
96
- * @return {Promise<WriteStream>} A promise resolves a `WriteStream` object.
82
+ * @param  {boolean} [append]  A flag represent if data append to existing ones.
83
+ * @return {Promise<RNFetchBlobWriteStream>} A promise resolves a `WriteStream` object.
97 84
  */
98 85
 function writeStream(
99
-  path : string,
100
-  encoding : 'utf8' | 'ascii' | 'base64',
101
-  append? : ?bool,
102
-):Promise<RNFetchBlobWriteStream> {
103
-  if(!path)
104
-    throw Error('RNFetchBlob could not open file stream with empty `path`')
105
-  encoding = encoding || 'utf8'
106
-  append = append || false
86
+  path: string,
87
+  encoding?: 'utf8' | 'ascii' | 'base64' = 'utf8',
88
+  append?: boolean = false,
89
+): Promise<RNFetchBlobWriteStream> {
90
+  if (typeof path !== 'string') {
91
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
92
+  }
107 93
   return new Promise((resolve, reject) => {
108
-    RNFetchBlob.writeStream(path, encoding || 'base64', append || false, (err, streamId:string) => {
109
-      if(err)
110
-        reject(new Error(err))
94
+    RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => {
95
+      if (errMsg) {
96
+        const err = new Error(errMsg)
97
+        err.code = errCode
98
+        reject(err)
99
+      }
111 100
       else
112 101
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
113 102
     })
@@ -119,14 +108,18 @@ function writeStream(
119 108
  * @param  {string} path   The file path.
120 109
  * @param  {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii`
121 110
  * @param  {boolean} bufferSize Size of stream buffer.
111
+ * @param  {number} [tick=10] Interval in milliseconds between reading chunks of data
122 112
  * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
123 113
  */
124 114
 function readStream(
125
-  path : string,
126
-  encoding : 'utf8' | 'ascii' | 'base64',
127
-  bufferSize? : ?number,
128
-  tick : ?number = 10
129
-):Promise<RNFetchBlobReadStream> {
115
+  path: string,
116
+  encoding: 'utf8' | 'ascii' | 'base64' = 'utf8',
117
+  bufferSize?: number,
118
+  tick?: number = 10
119
+): Promise<RNFetchBlobReadStream> {
120
+  if (typeof path !== 'string') {
121
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
122
+  }
130 123
   return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
131 124
 }
132 125
 
@@ -135,17 +128,11 @@ function readStream(
135 128
  * @param  {string} path Path of directory to be created
136 129
  * @return {Promise}
137 130
  */
138
-function mkdir(path:string):Promise {
139
-
140
-  return new Promise((resolve, reject) => {
141
-    RNFetchBlob.mkdir(path, (err, res) => {
142
-      if(err)
143
-        reject(new Error(err))
144
-      else
145
-        resolve()
146
-    })
147
-  })
148
-
131
+function mkdir(path: string): Promise {
132
+  if (typeof path !== 'string') {
133
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
134
+  }
135
+  return RNFetchBlob.mkdir(path)
149 136
 }
150 137
 
151 138
 /**
@@ -153,8 +140,8 @@ function mkdir(path:string):Promise {
153 140
  * @param  {string} groupName Name of app group
154 141
  * @return {Promise}
155 142
  */
156
-function pathForAppGroup(groupName:string):Promise {
157
-  return RNFetchBlob.pathForAppGroup(groupName);
143
+function pathForAppGroup(groupName: string): Promise {
144
+  return RNFetchBlob.pathForAppGroup(groupName)
158 145
 }
159 146
 
160 147
 /**
@@ -163,9 +150,10 @@ function pathForAppGroup(groupName:string):Promise {
163 150
  * @param  {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream.
164 151
  * @return {Promise<Array<number> | string>}
165 152
  */
166
-function readFile(path:string, encoding:string, bufferSize:?number):Promise<any> {
167
-  if(typeof path !== 'string')
168
-    return Promise.reject(new Error('Invalid argument "path" '))
153
+function readFile(path: string, encoding: string = 'utf8'): Promise<any> {
154
+  if (typeof path !== 'string') {
155
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
156
+  }
169 157
   return RNFetchBlob.readFile(path, encoding)
170 158
 }
171 159
 
@@ -176,37 +164,43 @@ function readFile(path:string, encoding:string, bufferSize:?number):Promise<any>
176 164
  * @param  {string} encoding Encoding of data (Optional).
177 165
  * @return {Promise}
178 166
  */
179
-function writeFile(path:string, data:string | Array<number>, encoding:?string):Promise {
180
-  encoding = encoding || 'utf8'
181
-  if(typeof path !== 'string')
182
-    return Promise.reject('Invalid argument "path" ')
183
-  if(encoding.toLocaleLowerCase() === 'ascii') {
184
-    if(!Array.isArray(data))
185
-      return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`))
167
+function writeFile(path: string, data: string | Array<number>, encoding: ?string = 'utf8'): Promise {
168
+  if (typeof path !== 'string') {
169
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
170
+  }
171
+  if (encoding.toLocaleLowerCase() === 'ascii') {
172
+    if (!Array.isArray(data)) {
173
+      return Promise.reject(addCode('EINVAL', new TypeError('"data" must be an Array when encoding is "ascii"')))
174
+    }
186 175
     else
187
-      return RNFetchBlob.writeFileArray(path, data, false);
188
-  } else {
189
-    if(typeof data !== 'string')
190
-      return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`))
176
+      return RNFetchBlob.writeFileArray(path, data, false)
177
+  }
178
+  else {
179
+    if (typeof data !== 'string') {
180
+      return Promise.reject(addCode('EINVAL', new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`)))
181
+    }
191 182
     else
192
-      return RNFetchBlob.writeFile(path, encoding, data, false);
183
+      return RNFetchBlob.writeFile(path, encoding, data, false)
193 184
   }
194 185
 }
195 186
 
196
-function appendFile(path:string, data:string | Array<number>, encoding:?string):Promise {
197
-  encoding = encoding || 'utf8'
198
-  if(typeof path !== 'string')
199
-    return Promise.reject('Invalid argument "path" ')
200
-  if(encoding.toLocaleLowerCase() === 'ascii') {
201
-    if(!Array.isArray(data))
202
-      return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`))
187
+function appendFile(path: string, data: string | Array<number>, encoding?: string = 'utf8'): Promise<number> {
188
+  if (typeof path !== 'string') {
189
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
190
+  }
191
+  if (encoding.toLocaleLowerCase() === 'ascii') {
192
+    if (!Array.isArray(data)) {
193
+      return Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers')))
194
+    }
203 195
     else
204
-      return RNFetchBlob.writeFileArray(path, data, true);
205
-  } else {
206
-    if(typeof data !== 'string')
207
-      return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`))
196
+      return RNFetchBlob.writeFileArray(path, data, true)
197
+  }
198
+  else {
199
+    if (typeof data !== 'string') {
200
+      return Promise.reject(addCode('EINVAL'), new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`))
201
+    }
208 202
     else
209
-      return RNFetchBlob.writeFile(path, encoding, data, true);
203
+      return RNFetchBlob.writeFile(path, encoding, data, true)
210 204
   }
211 205
 }
212 206
 
@@ -215,13 +209,16 @@ function appendFile(path:string, data:string | Array<number>, encoding:?string):
215 209
  * @param  {string} path Target path
216 210
  * @return {RNFetchBlobFile}
217 211
  */
218
-function stat(path:string):Promise<RNFetchBlobFile> {
212
+function stat(path: string): Promise<RNFetchBlobFile> {
219 213
   return new Promise((resolve, reject) => {
214
+    if (typeof path !== 'string') {
215
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
216
+    }
220 217
     RNFetchBlob.stat(path, (err, stat) => {
221
-      if(err)
218
+      if (err)
222 219
         reject(new Error(err))
223 220
       else {
224
-        if(stat) {
221
+        if (stat) {
225 222
           stat.size = parseInt(stat.size)
226 223
           stat.lastModified = parseInt(stat.lastModified)
227 224
         }
@@ -233,62 +230,77 @@ function stat(path:string):Promise<RNFetchBlobFile> {
233 230
 
234 231
 /**
235 232
  * Android only method, request media scanner to scan the file.
236
- * @param  {Array<Object<string, string>>} Array contains Key value pairs with key `path` and `mime`.
233
+ * @param  {Array<Object<string, string>>} pairs Array contains Key value pairs with key `path` and `mime`.
237 234
  * @return {Promise}
238 235
  */
239
-function scanFile(pairs:any):Promise {
236
+function scanFile(pairs: any): Promise {
240 237
   return new Promise((resolve, reject) => {
238
+    if (pairs === undefined) {
239
+      return reject(addCode('EINVAL', new TypeError('Missing argument')))
240
+    }
241 241
     RNFetchBlob.scanFile(pairs, (err) => {
242
-      if(err)
243
-        reject(new Error(err))
242
+      if (err)
243
+        reject(addCode('EUNSPECIFIED', new Error(err)))
244 244
       else
245 245
         resolve()
246 246
     })
247 247
   })
248 248
 }
249 249
 
250
-function cp(path:string, dest:string):Promise<boolean> {
250
+function hash(path: string, algorithm: string): Promise<string> {
251
+  if (typeof path !== 'string' || typeof algorithm !== 'string') {
252
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "algorithm"')))
253
+  }
254
+  return RNFetchBlob.hash(path, algorithm)
255
+}
256
+
257
+function cp(path: string, dest: string): Promise<boolean> {
251 258
   return new Promise((resolve, reject) => {
259
+    if (typeof path !== 'string' || typeof dest !== 'string') {
260
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"')))
261
+    }
252 262
     RNFetchBlob.cp(path, dest, (err, res) => {
253
-      if(err)
254
-        reject(new Error(err))
263
+      if (err)
264
+        reject(addCode('EUNSPECIFIED', new Error(err)))
255 265
       else
256 266
         resolve(res)
257 267
     })
258 268
   })
259 269
 }
260 270
 
261
-function mv(path:string, dest:string):Promise<boolean> {
271
+function mv(path: string, dest: string): Promise<boolean> {
262 272
   return new Promise((resolve, reject) => {
273
+    if (typeof path !== 'string' || typeof dest !== 'string') {
274
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"')))
275
+    }
263 276
     RNFetchBlob.mv(path, dest, (err, res) => {
264
-      if(err)
265
-        reject(new Error(err))
277
+      if (err)
278
+        reject(addCode('EUNSPECIFIED', new Error(err)))
266 279
       else
267 280
         resolve(res)
268 281
     })
269 282
   })
270 283
 }
271 284
 
272
-function lstat(path:string):Promise<Array<RNFetchBlobFile>> {
285
+function lstat(path: string): Promise<Array<RNFetchBlobFile>> {
273 286
   return new Promise((resolve, reject) => {
287
+    if (typeof path !== 'string') {
288
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
289
+    }
274 290
     RNFetchBlob.lstat(path, (err, stat) => {
275
-      if(err)
276
-        reject(new Error(err))
291
+      if (err)
292
+        reject(addCode('EUNSPECIFIED', new Error(err)))
277 293
       else
278 294
         resolve(stat)
279 295
     })
280 296
   })
281 297
 }
282 298
 
283
-function ls(path:string):Promise<Array<String>> {
284
-  return new Promise((resolve, reject) => {
285
-    RNFetchBlob.ls(path, (err, res) => {
286
-      if(err)
287
-        reject(new Error(err))
288
-      else
289
-        resolve(res)
290
-    })
291
-  })
299
+function ls(path: string): Promise<Array<String>> {
300
+  if (typeof path !== 'string') {
301
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
302
+  }
303
+  return RNFetchBlob.ls(path)
292 304
 }
293 305
 
294 306
 /**
@@ -296,11 +308,14 @@ function ls(path:string):Promise<Array<String>> {
296 308
  * @param  {string}   path:string Path of target file.
297 309
  * @return {Promise}
298 310
  */
299
-function unlink(path:string):Promise {
311
+function unlink(path: string): Promise {
300 312
   return new Promise((resolve, reject) => {
313
+    if (typeof path !== 'string') {
314
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
315
+    }
301 316
     RNFetchBlob.unlink(path, (err) => {
302
-      if(err) {
303
-        reject(new Error(err))
317
+      if (err) {
318
+        reject(addCode('EUNSPECIFIED', new Error(err)))
304 319
       }
305 320
       else
306 321
         resolve()
@@ -311,63 +326,72 @@ function unlink(path:string):Promise {
311 326
 /**
312 327
  * Check if file exists and if it is a folder.
313 328
  * @param  {string} path Path to check
314
- * @return {Promise<bool, bool>}
329
+ * @return {Promise<boolean>}
315 330
  */
316
-function exists(path:string):Promise<bool, bool> {
317
-
331
+function exists(path: string): Promise<boolean> {
318 332
   return new Promise((resolve, reject) => {
333
+    if (typeof path !== 'string') {
334
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
335
+    }
319 336
     try {
320 337
       RNFetchBlob.exists(path, (exist) => {
321 338
         resolve(exist)
322 339
       })
323
-    } catch(err) {
324
-      reject(new Error(err))
340
+    }catch (err){
341
+      reject(addCode('EUNSPECIFIED', new Error(err)))
325 342
     }
326 343
   })
327 344
 
328 345
 }
329 346
 
330
-function slice(src:string, dest:string, start:number, end:number):Promise {
347
+function slice(src: string, dest: string, start: number, end: number): Promise {
348
+  if (typeof src !== 'string' || typeof dest !== 'string') {
349
+    return reject(addCode('EINVAL', new TypeError('Missing argument "src" and/or "destination"')))
350
+  }
351
+
331 352
   let p = Promise.resolve()
332 353
   let size = 0
354
+
333 355
   function normalize(num, size) {
334
-    if(num < 0)
356
+    if (num < 0)
335 357
       return Math.max(0, size + num)
336
-    if(!num && num !== 0)
358
+    if (!num && num !== 0)
337 359
       return size
338 360
     return num
339 361
   }
340
-  if(start < 0 || end < 0 || !start || !end) {
362
+
363
+  if (start < 0 || end < 0 || !start || !end) {
341 364
     p = p.then(() => stat(src))
342
-         .then((stat) => {
343
-           size = Math.floor(stat.size)
344
-           start = normalize(start || 0, size)
345
-           end = normalize(end, size)
346
-           return Promise.resolve()
347
-         })
365
+      .then((stat) => {
366
+        size = Math.floor(stat.size)
367
+        start = normalize(start || 0, size)
368
+        end = normalize(end, size)
369
+      })
348 370
   }
349 371
   return p.then(() => RNFetchBlob.slice(src, dest, start, end))
350 372
 }
351 373
 
352
-function isDir(path:string):Promise<bool, bool> {
353
-
374
+function isDir(path: string): Promise<bool> {
354 375
   return new Promise((resolve, reject) => {
376
+    if (typeof path !== 'string') {
377
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
378
+    }
355 379
     try {
356 380
       RNFetchBlob.exists(path, (exist, isDir) => {
357 381
         resolve(isDir)
358 382
       })
359
-    } catch(err) {
360
-      reject(new Error(err))
383
+    }catch (err){
384
+      reject(addCode('EUNSPECIFIED', new Error(err)))
361 385
     }
362 386
   })
363 387
 
364 388
 }
365 389
 
366
-function df():Promise<{ free : number, total : number }> {
390
+function df(): Promise<{ free: number, total: number }> {
367 391
   return new Promise((resolve, reject) => {
368 392
     RNFetchBlob.df((err, stat) => {
369
-      if(err)
370
-        reject(err)
393
+      if (err)
394
+        reject(addCode('EUNSPECIFIED', new Error(err)))
371 395
       else
372 396
         resolve(stat)
373 397
     })
@@ -388,6 +412,7 @@ export default {
388 412
   appendFile,
389 413
   pathForAppGroup,
390 414
   readFile,
415
+  hash,
391 416
   exists,
392 417
   createFile,
393 418
   isDir,

+ 7
- 9
index.js View File

@@ -17,7 +17,7 @@ import type {
17 17
   RNFetchBlobResponseInfo
18 18
 } from './types'
19 19
 import URIUtil from './utils/uri'
20
-import StatefulPromise from './class/StatefulPromise.js'
20
+//import StatefulPromise from './class/StatefulPromise.js'
21 21
 import fs from './fs'
22 22
 import getUUID from './utils/uuid'
23 23
 import base64 from 'base-64'
@@ -118,7 +118,7 @@ function config (options:RNFetchBlobConfig) {
118 118
  * @param  {string} method     Should be one of `get`, `post`, `put`
119 119
  * @param  {string} url        A file URI string
120 120
  * @param  {string} headers    Arguments of file system API
121
- * @param  {any} body       Data to put or post to file systen.
121
+ * @param  {any}    body       Data to put or post to file systen.
122 122
  * @return {Promise}
123 123
  */
124 124
 function fetchFile(options = {}, method, url, headers = {}, body):Promise {
@@ -520,13 +520,12 @@ class FetchBlobResponse {
520 520
     }
521 521
     /**
522 522
      * Start read stream from cached file
523
-     * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
524
-     * @param  {Function} fn On data event handler
523
+     * @param  {String} encoding Encode type, should be one of `base64`, `ascii`, `utf8`.
525 524
      * @return {void}
526 525
      */
527
-    this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
526
+    this.readStream = (encoding: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
528 527
       if(this.type === 'path') {
529
-        return readStream(this.data, encode)
528
+        return readStream(this.data, encoding)
530 529
       }
531 530
       else {
532 531
         console.warn('RNFetchblob', 'this response data does not contains any available stream')
@@ -539,10 +538,9 @@ class FetchBlobResponse {
539 538
      * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
540 539
      * @return {String}
541 540
      */
542
-    this.readFile = (encode: 'base64' | 'utf8' | 'ascii') => {
541
+    this.readFile = (encoding: 'base64' | 'utf8' | 'ascii') => {
543 542
       if(this.type === 'path') {
544
-        encode = encode || 'utf8'
545
-        return readFile(this.data, encode)
543
+        return readFile(this.data, encoding)
546 544
       }
547 545
       else {
548 546
         console.warn('RNFetchblob', 'this response does not contains a readable file')

+ 2
- 2
ios.js View File

@@ -13,7 +13,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
13 13
 
14 14
 /**
15 15
  * Open a file using UIDocumentInteractionController
16
- * @param  {string]} path Path of the file to be open.
16
+ * @param  {string} path Path of the file to be open.
17 17
  * @param  {string} scheme URI scheme that needs to support, optional
18 18
  * @return {Promise}
19 19
  */
@@ -26,7 +26,7 @@ function previewDocument(path:string, scheme:string) {
26 26
 
27 27
 /**
28 28
  * Preview a file using UIDocumentInteractionController
29
- * @param  {string]} path Path of the file to be open.
29
+ * @param  {string} path Path of the file to be open.
30 30
  * @param  {string} scheme URI scheme that needs to support, optional
31 31
  * @return {Promise}
32 32
  */

+ 84
- 54
ios/RNFetchBlob/RNFetchBlob.m View File

@@ -143,8 +143,12 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
143 143
 }
144 144
 
145 145
 #pragma mark - fs.createFile
146
-RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) {
147
-
146
+RCT_EXPORT_METHOD(createFile:(NSString *)path
147
+                  data:(NSString *)data
148
+                  encoding:(NSString *)encoding
149
+                  resolver:(RCTPromiseResolveBlock)resolve
150
+                  rejecter:(RCTPromiseRejectBlock)reject)
151
+{
148 152
     NSFileManager * fm = [NSFileManager defaultManager];
149 153
     NSData * fileContent = nil;
150 154
 
@@ -162,18 +166,25 @@ RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NS
162 166
         fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
163 167
     }
164 168
 
165
-    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
166
-    if(success == YES)
167
-        callback(@[[NSNull null]]);
168
-    else
169
-        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
170
-
169
+    if ([fm fileExistsAtPath:path]) {
170
+        reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil);
171
+    }
172
+    else {
173
+        BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
174
+        if(success == YES)
175
+            resolve(@[[NSNull null]]);
176
+        else
177
+            reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create new file at path '%@', please ensure the folder exists", path], nil);
178
+    }
171 179
 }
172 180
 
173 181
 #pragma mark - fs.createFileASCII
174 182
 // method for create file with ASCII content
175
-RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray callback:(RCTResponseSenderBlock)callback) {
176
-
183
+RCT_EXPORT_METHOD(createFileASCII:(NSString *)path
184
+                  data:(NSArray *)dataArray
185
+                  resolver:(RCTPromiseResolveBlock)resolve
186
+                  rejecter:(RCTPromiseRejectBlock)reject)
187
+{
177 188
     NSFileManager * fm = [NSFileManager defaultManager];
178 189
     NSMutableData * fileContent = [NSMutableData alloc];
179 190
     // prevent stack overflow, alloc on heap
@@ -182,14 +193,21 @@ RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray cal
182 193
     for(int i = 0; i < dataArray.count; i++) {
183 194
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
184 195
     }
196
+
185 197
     [fileContent appendBytes:bytes length:dataArray.count];
186
-    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
187
-    free(bytes);
188
-    if(success == YES)
189
-        callback(@[[NSNull null]]);
190
-    else
191
-        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
192 198
 
199
+    if ([fm fileExistsAtPath:path]) {
200
+        reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil);
201
+    }
202
+    else {
203
+        BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
204
+        if(success == YES)
205
+            resolve(@[[NSNull null]]);
206
+        else
207
+            reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"failed to create new file at path '%@', please ensure the folder exists", path], nil);
208
+    }
209
+
210
+    free(bytes);
193 211
 }
194 212
 
195 213
 #pragma mark - fs.pathForAppGroup
@@ -202,7 +220,7 @@ RCT_EXPORT_METHOD(pathForAppGroup:(NSString *)groupName
202 220
     if(path) {
203 221
         resolve(path);
204 222
     } else {
205
-        reject(@"RNFetchBlob file not found", @"could not find path for app group", nil);
223
+        reject(@"EUNSPECIFIED", @"could not find path for app group", nil);
206 224
     }
207 225
 }
208 226
 
@@ -228,14 +246,26 @@ RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding
228 246
 {
229 247
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
230 248
     NSFileManager * fm = [NSFileManager defaultManager];
231
-    BOOL isDir = nil;
232
-    BOOL exist = [fm fileExistsAtPath:path isDirectory:&isDir];
233
-    if( exist == NO || isDir == YES) {
234
-        callback(@[[NSString stringWithFormat:@"target path `%@` may not exists or it's a folder", path]]);
235
-        return;
249
+    NSString * folder = [path stringByDeletingLastPathComponent];
250
+    NSError* err = nil;
251
+    BOOL isDir = NO;
252
+    BOOL exists = [fm fileExistsAtPath:path isDirectory: &isDir];
253
+
254
+    if(!exists) {
255
+        [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
256
+        if(err != nil) {
257
+            callback(@[@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]]]);
258
+        }
259
+        if(![fm createFileAtPath:path contents:nil attributes:nil]) {
260
+            callback(@[@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path]]);
261
+        }
262
+    }
263
+    else if(isDir) {
264
+        callback(@[@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]]);
236 265
     }
266
+
237 267
     NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append];
238
-    callback(@[[NSNull null], streamId]);
268
+    callback(@[[NSNull null], [NSNull null], streamId]);
239 269
 }
240 270
 
241 271
 #pragma mark - fs.writeArrayChunk
@@ -299,24 +329,25 @@ RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBloc
299 329
 }
300 330
 
301 331
 #pragma mark - fs.ls
302
-RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback)
332
+RCT_EXPORT_METHOD(ls:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
303 333
 {
304 334
     NSFileManager* fm = [NSFileManager defaultManager];
305 335
     BOOL exist = nil;
306 336
     BOOL isDir = nil;
307 337
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
308
-    if(exist == NO || isDir == NO) {
309
-        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not a folder", path]]);
310
-        return ;
338
+    if(exist == NO) {
339
+        return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil);
340
+    }
341
+    if(isDir == NO) {
342
+        return reject(@"ENOTDIR", [NSString stringWithFormat:@"Not a directory '%@'", path], nil);
311 343
     }
312 344
     NSError * error = nil;
313 345
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
314 346
 
315 347
     if(error == nil)
316
-        callback(@[[NSNull null], result == nil ? [NSNull null] :result ]);
348
+        resolve(result);
317 349
     else
318
-        callback(@[[error localizedDescription], [NSNull null]]);
319
-
350
+        reject(@"EUNSPECIFIED", [error description], nil);
320 351
 }
321 352
 
322 353
 #pragma mark - fs.stat
@@ -334,7 +365,7 @@ RCT_EXPORT_METHOD(stat:(NSString *)target callback:(RCTResponseSenderBlock) call
334 365
 
335 366
             exist = [fm fileExistsAtPath:path isDirectory:&isDir];
336 367
             if(exist == NO) {
337
-                callback(@[[NSString stringWithFormat:@"failed to stat path `%@` for it is not exist or it is not exist", path]]);
368
+                callback(@[[NSString stringWithFormat:@"failed to stat path `%@` because it does not exist or it is not a folder", path]]);
338 369
                 return ;
339 370
             }
340 371
             result = [RNFetchBlobFS stat:path error:&error];
@@ -370,7 +401,7 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
370 401
 
371 402
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
372 403
     if(exist == NO) {
373
-        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not exist", path]]);
404
+        callback(@[[NSString stringWithFormat:@"failed to lstat path `%@` because it does not exist or it is not a folder", path]]);
374 405
         return ;
375 406
     }
376 407
     NSError * error = nil;
@@ -397,7 +428,6 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
397 428
 #pragma mark - fs.cp
398 429
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
399 430
 {
400
-
401 431
 //    path = [RNFetchBlobFS getPathOfAsset:path];
402 432
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
403 433
         NSError * error = nil;
@@ -408,6 +438,7 @@ RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTRespons
408 438
         }
409 439
         else
410 440
         {
441
+            // If the destination exists there will be an error
411 442
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
412 443
 
413 444
             if(error == nil)
@@ -416,7 +447,6 @@ RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTRespons
416 447
                 callback(@[[error localizedDescription], @NO]);
417 448
         }
418 449
     }];
419
-
420 450
 }
421 451
 
422 452
 
@@ -434,15 +464,9 @@ RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTRespo
434 464
 }
435 465
 
436 466
 #pragma mark - fs.mkdir
437
-RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback)
467
+RCT_EXPORT_METHOD(mkdir:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
438 468
 {
439
-    if([[NSFileManager defaultManager] fileExistsAtPath:path]) {
440
-        callback(@[@"mkdir failed, folder already exists"]);
441
-        return;
442
-    }
443
-    else
444
-        [RNFetchBlobFS mkdir:path];
445
-    callback(@[[NSNull null]]);
469
+    [RNFetchBlobFS mkdir:path resolver:resolve rejecter:reject];
446 470
 }
447 471
 
448 472
 #pragma mark - fs.readFile
@@ -451,24 +475,30 @@ RCT_EXPORT_METHOD(readFile:(NSString *)path
451 475
                   resolver:(RCTPromiseResolveBlock)resolve
452 476
                   rejecter:(RCTPromiseRejectBlock)reject)
453 477
 {
454
-
455
-    [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(id content, NSString * err) {
456
-        if(err != nil)
457
-        {
458
-            reject(@"RNFetchBlob failed to read file", err, nil);
478
+    
479
+    [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(NSData * content, NSString * code, NSString * err) {
480
+        if(err != nil) {
481
+            reject(code, err, nil);
459 482
             return;
460 483
         }
461
-        if(encoding == @"ascii")
462
-        {
484
+        if(encoding == @"ascii") {
463 485
             resolve((NSMutableArray *)content);
464 486
         }
465
-        else
466
-        {
487
+        else {
467 488
             resolve((NSString *)content);
468 489
         }
469 490
     }];
470 491
 }
471 492
 
493
+#pragma mark - fs.hash
494
+RCT_EXPORT_METHOD(hash:(NSString *)path
495
+                  algorithm:(NSString *)algorithm
496
+                  resolver:(RCTPromiseResolveBlock)resolve
497
+                  rejecter:(RCTPromiseRejectBlock)reject)
498
+{
499
+    [RNFetchBlobFS hash:path algorithm:[NSString stringWithString:algorithm] resolver:resolve rejecter:reject];
500
+}
501
+
472 502
 #pragma mark - fs.readStream
473 503
 RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId)
474 504
 {
@@ -537,7 +567,7 @@ RCT_EXPORT_METHOD(previewDocument:(NSString*)uri scheme:(NSString *)scheme resol
537 567
       });
538 568
         resolve(@[[NSNull null]]);
539 569
     } else {
540
-        reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil);
570
+        reject(@"EINVAL", @"scheme is not supported", nil);
541 571
     }
542 572
 }
543 573
 
@@ -557,7 +587,7 @@ RCT_EXPORT_METHOD(openDocument:(NSString*)uri scheme:(NSString *)scheme resolver
557 587
         });
558 588
         resolve(@[[NSNull null]]);
559 589
     } else {
560
-        reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil);
590
+        reject(@"EINVAL", @"scheme is not supported", nil);
561 591
     }
562 592
 }
563 593
 
@@ -571,7 +601,7 @@ RCT_EXPORT_METHOD(excludeFromBackupKey:(NSString *)url resolver:(RCTPromiseResol
571 601
     {
572 602
         resolve(@[[NSNull null]]);
573 603
     } else {
574
-        reject(@"RNFetchBlob could not open document", [error description], nil);
604
+        reject(@"EUNSPECIFIED", [error description], nil);
575 605
     }
576 606
 
577 607
 }

+ 6
- 1
ios/RNFetchBlobFS.h View File

@@ -58,11 +58,16 @@
58 58
 // fs methods
59 59
 + (RNFetchBlobFS *) getFileStreams;
60 60
 + (BOOL) mkdir:(NSString *) path;
61
++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
62
++ (void) hash:(NSString *)path
63
+    algorithm:(NSString *)algorithm
64
+     resolver:(RCTPromiseResolveBlock)resolve
65
+     rejecter:(RCTPromiseRejectBlock)reject;
61 66
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
62 67
 + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
63 68
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
64 69
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
65
-+ (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString * errMsg))onComplete;
70
++ (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString* code, NSString * errMsg))onComplete;
66 71
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
67 72
 + (void) slice:(NSString *)path
68 73
          dest:(NSString *)dest

+ 190
- 56
ios/RNFetchBlobFS.m View File

@@ -13,6 +13,8 @@
13 13
 #import "IOS7Polyfill.h"
14 14
 @import AssetsLibrary;
15 15
 
16
+#import <CommonCrypto/CommonDigest.h>
17
+
16 18
 #if __has_include(<React/RCTAssert.h>)
17 19
 #import <React/RCTBridge.h>
18 20
 #import <React/RCTEventDispatcher.h>
@@ -163,8 +165,8 @@ NSMutableDictionary *fileStreams = nil;
163 165
             {
164 166
                 if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
165 167
                 {
166
-                    NSString * message = [NSString stringWithFormat:@"File not exists at path %@", path];
167
-                    NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": message };
168
+                    NSString * message = [NSString stringWithFormat:@"File does not exist at path %@", path];
169
+                    NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"ENOENT", @"detail": message };
168 170
                     [event sendDeviceEventWithName:streamId body:payload];
169 171
                     free(buffer);
170 172
                     return ;
@@ -197,7 +199,7 @@ NSMutableDictionary *fileStreams = nil;
197 199
             }
198 200
             else
199 201
             {
200
-                NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": @"RNFetchBlob.readStream unable to resolve URI" };
202
+                NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EINVAL", @"detail": @"Unable to resolve URI" };
201 203
                 [event sendDeviceEventWithName:streamId body:payload];
202 204
             }
203 205
             // release buffer
@@ -207,7 +209,7 @@ NSMutableDictionary *fileStreams = nil;
207 209
         }
208 210
         @catch (NSError * err)
209 211
         {
210
-            NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": [NSString stringWithFormat:@"RNFetchBlob.readStream error %@", [err description]] };
212
+            NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EUNSPECIFIED", @"detail": [err description] };
211 213
             [event sendDeviceEventWithName:streamId body:payload];
212 214
         }
213 215
         @finally
@@ -254,11 +256,11 @@ NSMutableDictionary *fileStreams = nil;
254 256
                     [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
255 257
                 }
256 258
             }
257
-            
259
+
258 260
             NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : asciiArray };
259 261
             [event sendDeviceEventWithName:streamId body:payload];
260 262
         }
261
-        
263
+
262 264
     }
263 265
     @catch (NSException * ex)
264 266
     {
@@ -330,23 +332,39 @@ NSMutableDictionary *fileStreams = nil;
330 332
 
331 333
 # pragma mark - write file
332 334
 
333
-+ (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
335
++ (void) writeFile:(NSString *)path
336
+                    encoding:(NSString *)encoding
337
+                    data:(NSString *)data
338
+                    append:(BOOL)append
339
+                    resolver:(RCTPromiseResolveBlock)resolve
340
+                    rejecter:(RCTPromiseRejectBlock)reject
334 341
 {
335 342
     @try {
336 343
         NSFileManager * fm = [NSFileManager defaultManager];
337 344
         NSError * err = nil;
338
-        // check if the folder exists, if not exists, create folders recursively
345
+        // check if the folder exists, if it does not exist create folders recursively
339 346
         // after the folders created, write data into the file
340 347
         NSString * folder = [path stringByDeletingLastPathComponent];
341 348
         encoding = [encoding lowercaseString];
342
-        if(![fm fileExistsAtPath:folder]) {
343
-            [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
344
-            [fm createFileAtPath:path contents:nil attributes:nil];
349
+
350
+        BOOL isDir = NO;
351
+        BOOL exists = NO;
352
+        exists = [fm fileExistsAtPath:path isDirectory: &isDir];
353
+
354
+        if (isDir) {
355
+            return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
345 356
         }
346
-        if(err != nil) {
347
-            reject(@"RNFetchBlob writeFile Error", @"could not create file at path", nil);
348
-            return;
357
+
358
+        if(!exists) {
359
+            [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
360
+            if(err != nil) {
361
+                return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
362
+            }
363
+            if(![fm createFileAtPath:path contents:nil attributes:nil]) {
364
+                return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
365
+            }
349 366
         }
367
+
350 368
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
351 369
         NSData * content = nil;
352 370
         if([encoding RNFBContainsString:@"base64"]) {
@@ -355,7 +373,7 @@ NSMutableDictionary *fileStreams = nil;
355 373
         else if([encoding isEqualToString:@"uri"]) {
356 374
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
357 375
                 if(errMsg != nil)
358
-                    reject(@"RNFetchBlob writeFile Error", errMsg, nil);
376
+                    reject(@"EUNSPECIFIED", errMsg, nil);
359 377
                 else
360 378
                     resolve(size);
361 379
             }];
@@ -378,22 +396,40 @@ NSMutableDictionary *fileStreams = nil;
378 396
     }
379 397
     @catch (NSException * e)
380 398
     {
381
-        reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
399
+        reject(@"EUNSPECIFIED", [e description], nil);
382 400
     }
383 401
 }
384 402
 
385 403
 # pragma mark - write file (array)
386 404
 
387
-+ (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
405
++ (void) writeFileArray:(NSString *)path
406
+                         data:(NSArray *)data
407
+                         append:(BOOL)append
408
+                         resolver:(RCTPromiseResolveBlock)resolve
409
+                         rejecter:(RCTPromiseRejectBlock)reject
410
+{
388 411
     @try {
389 412
         NSFileManager * fm = [NSFileManager defaultManager];
390 413
         NSError * err = nil;
391 414
         // check if the folder exists, if not exists, create folders recursively
392 415
         // after the folders created, write data into the file
393 416
         NSString * folder = [path stringByDeletingLastPathComponent];
394
-        if(![fm fileExistsAtPath:folder]) {
417
+
418
+        BOOL isDir = NO;
419
+        BOOL exists = NO;
420
+        exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
421
+
422
+        if (isDir) {
423
+            return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
424
+        }
425
+
426
+        if(!exists) {
395 427
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
428
+            if(err != nil) {
429
+                return reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
430
+            }
396 431
         }
432
+
397 433
         NSMutableData * fileContent = [NSMutableData alloc];
398 434
         // prevent stack overflow, alloc on heap
399 435
         char * bytes = (char*) malloc([data count]);
@@ -401,8 +437,11 @@ NSMutableDictionary *fileStreams = nil;
401 437
             bytes[i] = [[data objectAtIndex:i] charValue];
402 438
         }
403 439
         [fileContent appendBytes:bytes length:data.count];
404
-        if(![fm fileExistsAtPath:path]) {
405
-            [fm createFileAtPath:path contents:fileContent attributes:NULL];
440
+
441
+        if(!exists) {
442
+            if(![fm createFileAtPath:path contents:fileContent attributes:NULL]) {
443
+                return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
444
+            }
406 445
         }
407 446
         // if file exists, write file
408 447
         else {
@@ -422,7 +461,7 @@ NSMutableDictionary *fileStreams = nil;
422 461
     }
423 462
     @catch (NSException * e)
424 463
     {
425
-        reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
464
+        reject(@"EUNSPECIFIED", [e description], nil);
426 465
     }
427 466
 }
428 467
 
@@ -430,7 +469,7 @@ NSMutableDictionary *fileStreams = nil;
430 469
 
431 470
 + (void) readFile:(NSString *)path
432 471
          encoding:(NSString *)encoding
433
-       onComplete:(void (^)(id content, NSString * errMsg))onComplete
472
+       onComplete:(void (^)(NSData * content, NSString * codeStr, NSString * errMsg))onComplete
434 473
 {
435 474
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
436 475
         __block NSData * fileContent;
@@ -442,7 +481,7 @@ NSMutableDictionary *fileStreams = nil;
442 481
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
443 482
             if(err != nil)
444 483
             {
445
-                onComplete(nil, [err description]);
484
+                onComplete(nil, @"EUNSPECIFIED", [err description]);
446 485
                 free(buffer);
447 486
                 return;
448 487
             }
@@ -451,26 +490,31 @@ NSMutableDictionary *fileStreams = nil;
451 490
         }
452 491
         else
453 492
         {
454
-            if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {
455
-                onComplete(nil, @"file not exists");
493
+            BOOL isDir = NO;
494
+            if(![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir]) {
495
+                if (isDir) {
496
+                    onComplete(nil, @"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]);
497
+                } else {
498
+                    onComplete(nil, @"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path]);
499
+                }
456 500
                 return;
457 501
             }
458 502
             fileContent = [NSData dataWithContentsOfFile:path];
459
-            
503
+
460 504
         }
461
-        
505
+
462 506
         if(encoding != nil)
463 507
         {
464 508
             if([[encoding lowercaseString] isEqualToString:@"utf8"])
465 509
             {
466 510
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
467 511
                 if(utf8 == nil)
468
-                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil);
512
+                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil, nil);
469 513
                 else
470
-                    onComplete(utf8, nil);
514
+                    onComplete(utf8, nil, nil);
471 515
             }
472 516
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
473
-                onComplete([fileContent base64EncodedStringWithOptions:0], nil);
517
+                onComplete([fileContent base64EncodedStringWithOptions:0], nil, nil);
474 518
             }
475 519
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
476 520
                 NSMutableArray * resultArray = [NSMutableArray array];
@@ -478,28 +522,115 @@ NSMutableDictionary *fileStreams = nil;
478 522
                 for(int i=0;i<[fileContent length];i++) {
479 523
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
480 524
                 }
481
-                onComplete(resultArray, nil);
525
+                onComplete(resultArray, nil, nil);
482 526
             }
483 527
         }
484 528
         else
485 529
         {
486
-            onComplete(fileContent, nil);
530
+            onComplete(fileContent, nil, nil);
487 531
         }
488
-        
532
+
489 533
     }];
490 534
 }
491 535
 
536
+# pragma mark - hash
537
+
538
++ (void) hash:(NSString *)path
539
+                  algorithm:(NSString *)algorithm
540
+                  resolver:(RCTPromiseResolveBlock)resolve
541
+                  rejecter:(RCTPromiseRejectBlock)reject
542
+{
543
+    BOOL isDir = NO;
544
+    BOOL exists = NO;
545
+    exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
546
+
547
+    if (isDir) {
548
+        return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
549
+    }
550
+    if (!exists) {
551
+        return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil);
552
+    }
553
+
554
+    NSError *error = nil;
555
+
556
+    NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
557
+
558
+    if (error) {
559
+        reject(@"EUNKNOWN", [error description], nil);
560
+        return;
561
+    }
562
+
563
+    if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) {
564
+        reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
565
+        return;
566
+    }
567
+
568
+    NSData *content = [[NSFileManager defaultManager] contentsAtPath:path];
569
+
570
+    NSArray *keys = [NSArray arrayWithObjects:@"md5", @"sha1", @"sha224", @"sha256", @"sha384", @"sha512", nil];
571
+
572
+    NSArray *digestLengths = [NSArray arrayWithObjects:
573
+        @CC_MD5_DIGEST_LENGTH,
574
+        @CC_SHA1_DIGEST_LENGTH,
575
+        @CC_SHA224_DIGEST_LENGTH,
576
+        @CC_SHA256_DIGEST_LENGTH,
577
+        @CC_SHA384_DIGEST_LENGTH,
578
+        @CC_SHA512_DIGEST_LENGTH,
579
+        nil];
580
+
581
+    NSDictionary *keysToDigestLengths = [NSDictionary dictionaryWithObjects:digestLengths forKeys:keys];
582
+
583
+    int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue];
584
+
585
+    if (!digestLength) {
586
+      return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil);
587
+    }
588
+
589
+    unsigned char buffer[digestLength];
590
+
591
+    if ([algorithm isEqualToString:@"md5"]) {
592
+        CC_MD5(content.bytes, (CC_LONG)content.length, buffer);
593
+    } else if ([algorithm isEqualToString:@"sha1"]) {
594
+        CC_SHA1(content.bytes, (CC_LONG)content.length, buffer);
595
+    } else if ([algorithm isEqualToString:@"sha224"]) {
596
+        CC_SHA224(content.bytes, (CC_LONG)content.length, buffer);
597
+    } else if ([algorithm isEqualToString:@"sha256"]) {
598
+        CC_SHA256(content.bytes, (CC_LONG)content.length, buffer);
599
+    } else if ([algorithm isEqualToString:@"sha384"]) {
600
+        CC_SHA384(content.bytes, (CC_LONG)content.length, buffer);
601
+    } else if ([algorithm isEqualToString:@"sha512"]) {
602
+        CC_SHA512(content.bytes, (CC_LONG)content.length, buffer);
603
+    } else {
604
+        reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil);
605
+        return;
606
+    }
607
+
608
+    NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2];
609
+    for(int i = 0; i < digestLength; i++)
610
+        [output appendFormat:@"%02x",buffer[i]];
611
+
612
+    resolve(output);
613
+}
492 614
 
493 615
 # pragma mark - mkdir
494 616
 
495
-+ (BOOL) mkdir:(NSString *) path {
496
-    BOOL isDir;
617
++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
618
+{
619
+    BOOL isDir = NO;
497 620
     NSError * err = nil;
498
-    // if temp folder not exists, create one
499
-    if(![[NSFileManager defaultManager] fileExistsAtPath: path isDirectory:&isDir]) {
621
+    if([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
622
+        reject(@"EEXIST", [NSString stringWithFormat:@"%@ '%@' already exists", isDir ? @"Directory" : @"File", path], nil);
623
+        return;
624
+    }
625
+    else {
500 626
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
501 627
     }
502
-    return err == nil;
628
+    if(err == nil) {
629
+        resolve(@YES);
630
+    }
631
+    else {
632
+        reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Error creating folder '%@', error: %@", path, [err description]], nil);
633
+    }
503 634
 }
504 635
 
505 636
 # pragma mark - stat
@@ -571,7 +702,7 @@ NSMutableDictionary *fileStreams = nil;
571 702
     NSData * decodedData = nil;
572 703
     if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
573 704
         decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters];
574
-    } 
705
+    }
575 706
     else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
576 707
         decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
577 708
     }
@@ -632,22 +763,28 @@ NSMutableDictionary *fileStreams = nil;
632 763
             NSFileManager * fm = [NSFileManager defaultManager];
633 764
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
634 765
             [os open];
635
-            // abort for the source file not exists
636
-            if([fm fileExistsAtPath:path] == NO)
637
-            {
638
-                reject(@"RNFetchBlob slice failed : the file does not exists", path, nil);
639
-                return;
766
+
767
+            BOOL isDir = NO;
768
+            BOOL exists = NO;
769
+            exists = [fm fileExistsAtPath:path isDirectory: &isDir];
770
+
771
+            if (isDir) {
772
+                return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
773
+            }
774
+            if(!exists) {
775
+                return reject(@"ENOENT", [NSString stringWithFormat: @"No such file '%@'", path ], nil);
640 776
             }
777
+
641 778
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
642 779
             long max = MIN(size, [end longValue]);
643 780
 
644 781
             if(![fm fileExistsAtPath:dest]) {
645
-                [fm createFileAtPath:dest contents:@"" attributes:nil];
782
+                if(![fm createFileAtPath:dest contents:@"" attributes:nil]) {
783
+                    return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
784
+                }
646 785
             }
647 786
             [handle seekToFileOffset:[start longValue]];
648
-            while(read < expected)
649
-            {
650
-
787
+            while(read < expected) {
651 788
                 NSData * chunk;
652 789
                 long chunkSize = 0;
653 790
                 if([start longValue] + read + 10240 > max)
@@ -683,9 +820,7 @@ NSMutableDictionary *fileStreams = nil;
683 820
             long size = asset.size;
684 821
             long max = MIN(size, [end longValue]);
685 822
 
686
-            while(read < expected)
687
-            {
688
-
823
+            while(read < expected) {
689 824
                 uint8_t * chunk[10240];
690 825
                 long chunkSize = 0;
691 826
                 if([start longValue] + read + 10240 > max)
@@ -710,9 +845,8 @@ NSMutableDictionary *fileStreams = nil;
710 845
             [os close];
711 846
             resolve(dest);
712 847
         }
713
-        else
714
-        {
715
-            reject(@"slice error",  [NSString stringWithFormat: @"could not resolve URI %@", path ], nil);
848
+        else {
849
+            reject(@"EINVAL", [NSString stringWithFormat: @"Could not resolve URI %@", path ], nil);
716 850
         }
717 851
 
718 852
     }];
@@ -765,7 +899,7 @@ NSMutableDictionary *fileStreams = nil;
765 899
     if (dictionary) {
766 900
         NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
767 901
         NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
768
-        
902
+
769 903
         callback(@[[NSNull null], @{
770 904
                   @"free" : freeFileSystemSizeInBytes,
771 905
                   @"total" : fileSystemSizeInBytes,
@@ -793,4 +927,4 @@ NSMutableDictionary *fileStreams = nil;
793 927
     return;
794 928
 }
795 929
 
796
-@end
930
+@end

+ 4
- 4
ios/RNFetchBlobReqBuilder.m View File

@@ -118,17 +118,17 @@
118 118
                     if([orgPath hasPrefix:AL_PREFIX])
119 119
                     {
120 120
                         
121
-                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) {
121
+                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(id content, NSString* code, NSString * err) {
122 122
                             if(err != nil)
123 123
                             {
124 124
                                 onComplete(nil, nil);
125 125
                             }
126 126
                             else
127 127
                             {
128
-                                [request setHTTPBody:content];
128
+                                [request setHTTPBody:((NSData *)content)];
129 129
                                 [request setHTTPMethod: method];
130 130
                                 [request setAllHTTPHeaderFields:mheaders];
131
-                                onComplete(request, [content length]);
131
+                                onComplete(request, [((NSData *)content) length]);
132 132
                             }
133 133
                         }];
134 134
                         
@@ -222,7 +222,7 @@
222 222
                         NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
223 223
                         orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
224 224
 
225
-                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) {
225
+                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString* code, NSString * err) {
226 226
                             if(err != nil)
227 227
                             {
228 228
                                 onComplete(formData, YES);

+ 5
- 2
json-stream.js View File

@@ -2,14 +2,17 @@ import Oboe from './lib/oboe-browser.min.js'
2 2
 import XMLHttpRequest from './polyfill/XMLHttpRequest'
3 3
 import URIUtil from './utils/uri'
4 4
 
5
-const OboeExtended = (arg: string | object) => {
5
+const OboeExtended = (arg: string | Object) => {
6 6
 
7 7
 
8 8
   window.location = ''
9 9
 
10 10
   if(!window.XMLHttpRequest.isRNFBPolyfill ) {
11 11
     window.XMLHttpRequest = XMLHttpRequest
12
-    console.warn('Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. You are seeing this warning because you did not replace it maually.')
12
+    console.warn(
13
+        'Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. ' +
14
+        'You are seeing this warning because you did not replace it manually.'
15
+    )
13 16
   }
14 17
 
15 18
   if(typeof arg === 'string') {

+ 3
- 1
polyfill/File.js View File

@@ -2,7 +2,6 @@
2 2
 // Use of this source code is governed by a MIT-style license that can be
3 3
 // found in the LICENSE file.
4 4
 
5
-import fs from '../fs.js'
6 5
 import Blob from './Blob.js'
7 6
 
8 7
 export default class File extends Blob {
@@ -11,6 +10,9 @@ export default class File extends Blob {
11 10
 
12 11
   static build(name:string, data:any, cType:string):Promise<File> {
13 12
     return new Promise((resolve, reject) => {
13
+      if (data === undefined) {
14
+        reject(new TypeError('data is undefined'))
15
+      }
14 16
       new File(data, cType).onCreated((f) => {
15 17
         f.name = name
16 18
         resolve(f)

+ 2
- 2
polyfill/XMLHttpRequest.js View File

@@ -224,8 +224,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
224 224
       /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
225 225
       /tt/
226 226
     ]
227
-    for(let i in invalidPatterns) {
228
-      if(invalidPatterns[i].test(name) || typeof name !== 'string') {
227
+    for(let pattern of invalidPatterns) {
228
+      if(pattern.test(name) || typeof name !== 'string') {
229 229
         throw `SyntaxError : Invalid header field name ${name}`
230 230
       }
231 231
     }