Browse Source

My proposed 0.10.9 changes (#489)

* fs.js: forgot one more error refactoring

* Fix path argument in iOS excludeFromBackupKey (#473)

* Fix link to fs.readStream() and to fs.writeStream() and insert link to new function fs.hash()

* Fix the documentation part of https://github.com/wkh237/react-native-fetch-blob/issues/467 "Example code for writeStream ignores that stream.write() returns a promise?"

* More fixes for issue https://github.com/wkh237/react-native-fetch-blob/issues/460 "Error normalization"

IMPORTANT: I wrote the iOS code BLIND (not even syntax highlighting) - this needs to be tested.

- Two or three methods that used callbacks to return results were changed to RN promises
- All methods using promises now use a Unix like code string for the first parameter, e.g. "ENOENT" for "File does not exist" (http://www.alorelang.org/doc/errno.html). The React Native bridge code itself uses this schema: it inserts "EUNSPECIFIED" when the "code" it gets back from Android/iOS code is undefined (null, nil). The RN bridge assigns the code (or "EUNSPECIFIED") to the "code" property of the error object it returns to Javascript, following the node.js example (the "code" property is not part of "standard" Javascript Error objects)
- Important errors like "No such file" are reported (instead of a general error), using the code property.
- I added a few extra error checks that the IDE suggested, mostly for Android (for which I have an IDE), if it seemed important I tried to do the same for teh iOS equivalent function
- I followed IDE suggestions on some of the Java code, like making fields private
- RNFetchBlobFS.java removeSession(): Added reporting of all failures to delete - IS THIS DESIRABLE (or do we not care)?
- readStream: The same schema is used for the emitted events when they are error events
- iOS: added an import for the crypto-digest headers - they are needed for the hash() function submitted in an earlier commit
- Fixed a link in the README.md - unfortunately the anchor-links change whenever even one character of the linked headline in the Wiki page changes

* Fix one issue raised in https://github.com/wkh237/react-native-fetch-blob/issues/477 by using code from https://stackoverflow.com/a/40874952/544779

* fix some access rights, remove unused items

* update gradle version setting in build.gradle

* Revert gradle settings to previous values :-(

* add a missing closing ")"

* Removed the part of an obsolete callback function parameter that I had left in when I converted mkdir to promises (low-level code)

* let mkdir resolve with "undefined" instead of "null" (my mistake)

* mkdir: normalize iOS and Android error if something already exists (file OR folder); return "true" (boolean) on success (failure is rejected promise) - it is not possibel to return "undefined" from a React Native promise from Java

* fix a long/int issue

* my mistake - according to https://facebook.github.io/react-native/docs/native-modules-android.html#argument-types "long" is not possible as argument type of an exported RN native module function

* Adde "utf8" as default encoding for fs.readFile - fixes #450 and #484

* follow my IDEA IDE's recommendations - SparseArray instead of HashMap, and make some fields private

* polyfill/File.js: add a parameter===undefined? check (this happened silently in the test suite)

* make var static again

* Normalized errors for fs.ls()

* forgot one parameter

* more parameter checks

* forgot to resolve the promise

* Forgot ;

* add more error parameter checks

* change readStream()/writeStream() default encoding to utf8 to match the tests in react-native-fetch-blob-dev

* default encoding is set in fs.js (now), no need to do it twice

* ReadStream error events: Set a default error code "EUNSPECIFIED" if no code is returned (should not happen, actually)

* writeFile() Android and iOS: improve errors; ReadStream: Add "ENOENT" (no such file) error event to Android version and add the thus far missing "code" parameter to iOS version

* oops - one "}" too many - removed

* add EISDIR error to readFile()s error vocabulary (iOS and Android)

* "or directory" is misplaced in a "no such file" error message for readFile()

* Android: two reject() calls did not have a code, iOS: slice() did not have code EISDIR, and "could not resolve URI" now is EINVAL everywhere

* writeStream: return ENOENT, EISDIR and EUNSPECIFIED according to the normalized schema (#460); Open a new question about behavior on ENOENT (#491)

* "+ +" was one plus sign too many

* this if has a whole block (that ois why I prefer a style where {} are mandatory even for single-statement blocks)

* I renamed this variable

* 1) #491 "writeStream() does not create file if it doesn't exist?"
2) I had gone overboard with the "@[..]" in the ios code, making some error strings arrays
3) fix typos: rename all ENODIR => ENOTDIR

* Java: getParentFolder() may return null - prevent a NullPointerException by adding one more check

* Relating to #298 -- looping through an array is not supposed to be done with for...in

*  Fix IOS syntax errors in #489

* #489 Fix typo and missing return statement

* fix error code
KittenWithHerbs 6 years ago
parent
commit
7fa5761b50

+ 49
- 4
README.md View File

@@ -590,10 +590,11 @@ 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)
597 598
 - [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
598 599
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
599 600
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
@@ -644,6 +645,45 @@ RNFetchBlob.fs.readStream(
644 645
 
645 646
 When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`.
646 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
+
647 687
 ```js
648 688
 RNFetchBlob.fs.writeStream(
649 689
     PATH_TO_FILE,
@@ -652,13 +692,18 @@ RNFetchBlob.fs.writeStream(
652 692
     // should data append to existing content ?
653 693
     true)
654 694
 .then((ofstream) => {
695
+    // BAD IDEA - Don't do this, those writes are unchecked:
655 696
     ofstream.write('foo')
656 697
     ofstream.write('bar')
657 698
     ofstream.close()
658 699
 })
659
-
700
+.catch(console.error)  // Cannot catch any write() errors!
660 701
 ```
661 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
+
662 707
 ### Cache File Management
663 708
 
664 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
- 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

+ 28
- 31
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
 
@@ -124,21 +132,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
124 132
             };
125 133
             RCTContext.addLifecycleEventListener(listener);
126 134
         } catch(Exception ex) {
127
-            promise.reject(ex.getLocalizedMessage());
135
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
128 136
         }
129 137
     }
130 138
 
131
-    @ReactMethod
132
-    public void createFileASCII(final String path, final ReadableArray dataArray, final Callback callback) {
133
-        threadPool.execute(new Runnable() {
134
-            @Override
135
-            public void run() {
136
-                RNFetchBlobFS.createFileASCII(path, dataArray, callback);
137
-            }
138
-        });
139
-
140
-    }
141
-
142 139
     @ReactMethod
143 140
     public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
144 141
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
@@ -150,8 +147,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
150 147
     }
151 148
 
152 149
     @ReactMethod
153
-    public void mkdir(String path, Callback callback) {
154
-        RNFetchBlobFS.mkdir(path, callback);
150
+    public void mkdir(String path, Promise promise) {
151
+        RNFetchBlobFS.mkdir(path, promise);
155 152
     }
156 153
 
157 154
     @ReactMethod
@@ -176,8 +173,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
176 173
     }
177 174
 
178 175
     @ReactMethod
179
-    public void ls(String path, Callback callback) {
180
-        RNFetchBlobFS.ls(path, callback);
176
+    public void ls(String path, Promise promise) {
177
+        RNFetchBlobFS.ls(path, promise);
181 178
     }
182 179
 
183 180
     @ReactMethod
@@ -355,10 +352,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
355 352
 
356 353
     @ReactMethod
357 354
     public void addCompleteDownload (ReadableMap config, Promise promise) {
358
-        DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
355
+        DownloadManager dm = (DownloadManager) RCTContext.getSystemService(RCTContext.DOWNLOAD_SERVICE);
359 356
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
360 357
         if(path == null) {
361
-            promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path);
358
+            promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"));
362 359
             return;
363 360
         }
364 361
         try {
@@ -375,7 +372,7 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
375 372
             promise.resolve(null);
376 373
         }
377 374
         catch(Exception ex) {
378
-            promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString());
375
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
379 376
         }
380 377
 
381 378
     }

+ 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;

+ 294
- 187
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,43 +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;
34 27
 import java.security.MessageDigest;
28
+import java.util.ArrayList;
35 29
 import java.util.HashMap;
36 30
 import java.util.Map;
37 31
 import java.util.UUID;
38 32
 
39
-public class RNFetchBlobFS {
33
+class RNFetchBlobFS {
40 34
 
41
-    ReactApplicationContext mCtx;
42
-    DeviceEventManagerModule.RCTDeviceEventEmitter emitter;
43
-    String encoding = "base64";
44
-    boolean append = false;
45
-    OutputStream writeStreamInstance = null;
46
-    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<>();
47 40
 
48 41
     RNFetchBlobFS(ReactApplicationContext ctx) {
49 42
         this.mCtx = ctx;
50 43
         this.emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
51 44
     }
52 45
 
53
-    static String getExternalFilePath(ReactApplicationContext ctx, String taskId, RNFetchBlobConfig config) {
54
-        if(config.path != null)
55
-            return config.path;
56
-        else if(config.fileCache && config.appendExt != null)
57
-            return RNFetchBlobFS.getTmpPath(ctx, taskId) + "." + config.appendExt;
58
-        else
59
-            return RNFetchBlobFS.getTmpPath(ctx, taskId);
60
-    }
61
-
62 46
     /**
63 47
      * Write string with encoding to file
64 48
      * @param path Destination file path.
@@ -66,25 +50,37 @@ public class RNFetchBlobFS {
66 50
      * @param data Array passed from JS context.
67 51
      * @param promise RCT Promise
68 52
      */
69
-    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) {
70 54
         try {
71
-            int written = 0;
55
+            int written;
72 56
             File f = new File(path);
73 57
             File dir = f.getParentFile();
74
-            if(!dir.exists())
75
-                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
+
76 72
             FileOutputStream fout = new FileOutputStream(f, append);
77 73
             // write data from a file
78 74
             if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
79
-                data = normalizePath(data);
80
-                File src = new File(data);
81
-                if(!src.exists()) {
82
-                    promise.reject("RNfetchBlob writeFile error", "source file : " + data + " does not exist");
75
+                String normalizedData = normalizePath(data);
76
+                File src = new File(normalizedData);
77
+                if (!src.exists()) {
78
+                    promise.reject("ENOENT", "No such file '" + path + "' " + "('" + normalizedData + "')");
83 79
                     fout.close();
84
-                    return ;
80
+                    return;
85 81
                 }
86 82
                 FileInputStream fin = new FileInputStream(src);
87
-                byte [] buffer = new byte [10240];
83
+                byte[] buffer = new byte [10240];
88 84
                 int read;
89 85
                 written = 0;
90 86
                 while((read = fin.read(buffer)) > 0) {
@@ -100,8 +96,11 @@ public class RNFetchBlobFS {
100 96
             }
101 97
             fout.close();
102 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");
103 102
         } catch (Exception e) {
104
-            promise.reject("RNFetchBlob writeFile error", e.getLocalizedMessage());
103
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
105 104
         }
106 105
     }
107 106
 
@@ -111,24 +110,37 @@ public class RNFetchBlobFS {
111 110
      * @param data Array passed from JS context.
112 111
      * @param promise RCT Promise
113 112
      */
114
-    static public void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
115
-
113
+    static void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
116 114
         try {
117
-
118 115
             File f = new File(path);
119 116
             File dir = f.getParentFile();
120
-            if(!dir.exists())
121
-                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
+
122 131
             FileOutputStream os = new FileOutputStream(f, append);
123
-            byte [] bytes = new byte[data.size()];
132
+            byte[] bytes = new byte[data.size()];
124 133
             for(int i=0;i<data.size();i++) {
125 134
                 bytes[i] = (byte) data.getInt(i);
126 135
             }
127 136
             os.write(bytes);
128 137
             os.close();
129 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");
130 142
         } catch (Exception e) {
131
-            promise.reject("RNFetchBlob writeFile error", e.getLocalizedMessage());
143
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
132 144
         }
133 145
     }
134 146
 
@@ -136,48 +148,60 @@ public class RNFetchBlobFS {
136 148
      * Read file with a buffer that has the same size as the target file.
137 149
      * @param path  Path of the file.
138 150
      * @param encoding  Encoding of read stream.
139
-     * @param promise
151
+     * @param promise  JS promise
140 152
      */
141
-    static public void readFile(String path, String encoding, final Promise promise ) {
153
+    static void readFile(String path, String encoding, final Promise promise) {
142 154
         String resolved = normalizePath(path);
143 155
         if(resolved != null)
144 156
             path = resolved;
145 157
         try {
146 158
             byte[] bytes;
159
+            int bytesRead;
160
+            int length;  // max. array length limited to "int", also see https://stackoverflow.com/a/10787175/544779
147 161
 
148 162
             if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
149 163
                 String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
150
-                long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
151
-                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];
152 167
                 InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
153
-                in.read(bytes, 0, (int) length);
168
+                bytesRead = in.read(bytes, 0, length);
154 169
                 in.close();
155 170
             }
156 171
             // issue 287
157 172
             else if(resolved == null) {
158 173
                 InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
159
-                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();
160 179
                 bytes = new byte[length];
161
-                in.read(bytes);
180
+                bytesRead = in.read(bytes);
162 181
                 in.close();
163 182
             }
164 183
             else {
165 184
                 File f = new File(path);
166
-                int length = (int) f.length();
185
+                length = (int) f.length();
167 186
                 bytes = new byte[length];
168 187
                 FileInputStream in = new FileInputStream(f);
169
-                in.read(bytes);
188
+                bytesRead = in.read(bytes);
170 189
                 in.close();
171 190
             }
172 191
 
192
+            if (bytesRead < length) {
193
+                promise.reject("EUNSPECIFIED", "Read only " + bytesRead + " bytes of " + length);
194
+                return;
195
+            }
196
+
173 197
             switch (encoding.toLowerCase()) {
174 198
                 case "base64" :
175 199
                     promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
176 200
                     break;
177 201
                 case "ascii" :
178 202
                     WritableArray asciiResult = Arguments.createArray();
179
-                    for(byte b : bytes) {
180
-                        asciiResult.pushInt((int)b);
203
+                    for (byte b : bytes) {
204
+                        asciiResult.pushInt((int) b);
181 205
                     }
182 206
                     promise.resolve(asciiResult);
183 207
                     break;
@@ -189,8 +213,16 @@ public class RNFetchBlobFS {
189 213
                     break;
190 214
             }
191 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
+        }
192 224
         catch(Exception err) {
193
-            promise.reject("RNFetchBlob readFile error", err.getLocalizedMessage());
225
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
194 226
         }
195 227
 
196 228
     }
@@ -199,7 +231,7 @@ public class RNFetchBlobFS {
199 231
      * Static method that returns system folders to JS context
200 232
      * @param ctx   React Native application context
201 233
      */
202
-    static public Map<String, Object> getSystemfolders(ReactApplicationContext ctx) {
234
+    static Map<String, Object> getSystemfolders(ReactApplicationContext ctx) {
203 235
         Map<String, Object> res = new HashMap<>();
204 236
 
205 237
         res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
@@ -222,11 +254,10 @@ public class RNFetchBlobFS {
222 254
 
223 255
     /**
224 256
      * Static method that returns a temp file path
225
-     * @param ctx   React Native application context
226 257
      * @param taskId    An unique string for identify
227
-     * @return
258
+     * @return String
228 259
      */
229
-    static public String getTmpPath(ReactApplicationContext ctx, String taskId) {
260
+    static String getTmpPath(String taskId) {
230 261
         return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId;
231 262
     }
232 263
 
@@ -236,12 +267,12 @@ public class RNFetchBlobFS {
236 267
      * @param encoding  File stream decoder, should be one of `base64`, `utf8`, `ascii`
237 268
      * @param bufferSize    Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
238 269
      */
239
-    public void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
270
+    void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
240 271
         String resolved = normalizePath(path);
241 272
         if(resolved != null)
242 273
             path = resolved;
243
-        try {
244 274
 
275
+        try {
245 276
             int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
246 277
             if(bufferSize > 0)
247 278
                 chunkSize = bufferSize;
@@ -250,7 +281,6 @@ public class RNFetchBlobFS {
250 281
 
251 282
             if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
252 283
                 fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
253
-
254 284
             }
255 285
             // fix issue 287
256 286
             else if(resolved == null) {
@@ -287,10 +317,8 @@ public class RNFetchBlobFS {
287 317
             } else if (encoding.equalsIgnoreCase("base64")) {
288 318
                 while ((cursor = fs.read(buffer)) != -1) {
289 319
                     if(cursor < chunkSize) {
290
-                        byte [] copy = new byte[cursor];
291
-                        for(int i =0;i<cursor;i++) {
292
-                            copy[i] = buffer[i];
293
-                        }
320
+                        byte[] copy = new byte[cursor];
321
+                        System.arraycopy(buffer, 0, copy, 0, cursor);
294 322
                         emitStreamEvent(streamId, "data", Base64.encodeToString(copy, Base64.NO_WRAP));
295 323
                     }
296 324
                     else
@@ -299,8 +327,12 @@ public class RNFetchBlobFS {
299 327
                         SystemClock.sleep(tick);
300 328
                 }
301 329
             } else {
302
-                String msg = "unrecognized encoding `" + encoding + "`";
303
-                emitStreamEvent(streamId, "error", msg);
330
+                emitStreamEvent(
331
+                        streamId,
332
+                        "error",
333
+                        "EINVAL",
334
+                        "Unrecognized encoding `" + encoding + "`, should be one of `base64`, `utf8`, `ascii`"
335
+                );
304 336
                 error = true;
305 337
             }
306 338
 
@@ -308,10 +340,20 @@ public class RNFetchBlobFS {
308 340
                 emitStreamEvent(streamId, "end", "");
309 341
             fs.close();
310 342
             buffer = null;
311
-
343
+        } catch (FileNotFoundException err) {
344
+            emitStreamEvent(
345
+                    streamId,
346
+                    "error",
347
+                    "ENOENT",
348
+                    "No such file '" + path + "'"
349
+            );
312 350
         } catch (Exception err) {
313
-            emitStreamEvent(streamId, "warn", "Failed to convert data to " + encoding +
314
-                    " encoded string, this might due to the source data is not able to convert using this encoding.");
351
+            emitStreamEvent(
352
+                    streamId,
353
+                    "error",
354
+                    "EUNSPECIFIED",
355
+                    "Failed to convert data to " + encoding + " encoded string. This might be because this encoding cannot be used for this data."
356
+            );
315 357
             err.printStackTrace();
316 358
         }
317 359
     }
@@ -319,28 +361,40 @@ public class RNFetchBlobFS {
319 361
     /**
320 362
      * Create a write stream and store its instance in RNFetchBlobFS.fileStreams
321 363
      * @param path  Target file path
322
-     * @param encoding Should be one of `base64`, `utf8`, `ascii`
323
-     * @param append    Flag represents if the file stream overwrite existing content
324
-     * @param callback
364
+     * @param encoding  Should be one of `base64`, `utf8`, `ascii`
365
+     * @param append  Flag represents if the file stream overwrite existing content
366
+     * @param callback  Callback
325 367
      */
326
-    public void writeStream(String path, String encoding, boolean append, Callback callback) {
327
-        File dest = new File(path);
328
-        if(!dest.exists() || dest.isDirectory()) {
329
-            callback.invoke("target path `" + path + "` may not exist or it is a folder");
330
-            return;
331
-        }
368
+    void writeStream(String path, String encoding, boolean append, Callback callback) {
332 369
         try {
370
+            File dest = new File(path);
371
+            File dir = dest.getParentFile();
372
+
373
+            if(!dest.exists()) {
374
+                if(dir != null && !dir.exists()) {
375
+                    if (!dir.mkdirs()) {
376
+                        callback.invoke("ENOTDIR", "Failed to create parent directory of '" + path + "'");
377
+                        return;
378
+                    }
379
+                }
380
+                if(!dest.createNewFile()) {
381
+                    callback.invoke("ENOENT", "File '" + path + "' does not exist and could not be created");
382
+                    return;
383
+                }
384
+            } else if(dest.isDirectory()) {
385
+                callback.invoke("EISDIR", "Expecting a file but '" + path + "' is a directory");
386
+                return;
387
+            }
388
+
333 389
             OutputStream fs = new FileOutputStream(path, append);
334 390
             this.encoding = encoding;
335
-            this.append = append;
336 391
             String streamId = UUID.randomUUID().toString();
337 392
             RNFetchBlobFS.fileStreams.put(streamId, this);
338 393
             this.writeStreamInstance = fs;
339
-            callback.invoke(null, streamId);
394
+            callback.invoke(null, null, streamId);
340 395
         } catch(Exception err) {
341
-            callback.invoke("failed to create write stream at path `" + path + "` " + err.getLocalizedMessage());
396
+            callback.invoke("EUNSPECIFIED", "Failed to create write stream at path `" + path + "`; " + err.getLocalizedMessage());
342 397
         }
343
-
344 398
     }
345 399
 
346 400
     /**
@@ -350,11 +404,9 @@ public class RNFetchBlobFS {
350 404
      * @param callback JS context callback
351 405
      */
352 406
     static void writeChunk(String streamId, String data, Callback callback) {
353
-
354 407
         RNFetchBlobFS fs = fileStreams.get(streamId);
355 408
         OutputStream stream = fs.writeStreamInstance;
356
-        byte [] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
357
-
409
+        byte[] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
358 410
         try {
359 411
             stream.write(chunk);
360 412
             callback.invoke();
@@ -370,11 +422,10 @@ public class RNFetchBlobFS {
370 422
      * @param callback JS context callback
371 423
      */
372 424
     static void writeArrayChunk(String streamId, ReadableArray data, Callback callback) {
373
-
374 425
         try {
375 426
             RNFetchBlobFS fs = fileStreams.get(streamId);
376 427
             OutputStream stream = fs.writeStreamInstance;
377
-            byte [] chunk = new byte[data.size()];
428
+            byte[] chunk = new byte[data.size()];
378 429
             for(int i =0; i< data.size();i++) {
379 430
                 chunk[i] = (byte) data.getInt(i);
380 431
             }
@@ -412,34 +463,49 @@ public class RNFetchBlobFS {
412 463
             RNFetchBlobFS.deleteRecursive(new File(path));
413 464
             callback.invoke(null, true);
414 465
         } catch(Exception err) {
415
-            if(err != null)
416 466
             callback.invoke(err.getLocalizedMessage(), false);
417 467
         }
418 468
     }
419 469
 
420
-    static void deleteRecursive(File fileOrDirectory) {
421
-
470
+    private static void deleteRecursive(File fileOrDirectory) throws IOException {
422 471
         if (fileOrDirectory.isDirectory()) {
423
-            for (File child : fileOrDirectory.listFiles()) {
424
-                deleteRecursive(child);
472
+            File[] files = fileOrDirectory.listFiles();
473
+            if (files == null) {
474
+                throw new NullPointerException("Received null trying to list files of directory '" + fileOrDirectory + "'");
475
+            } else {
476
+                for (File child : files) {
477
+                    deleteRecursive(child);
478
+                }
425 479
             }
426 480
         }
427
-        fileOrDirectory.delete();
481
+        boolean result = fileOrDirectory.delete();
482
+        if (!result) {
483
+            throw new IOException("Failed to delete '" + fileOrDirectory + "'");
484
+        }
428 485
     }
429 486
 
430 487
     /**
431 488
      * Make a folder
432
-     * @param path Source path
433
-     * @param callback  JS context callback
489
+     * @param path  Source path
490
+     * @param promise  JS promise
434 491
      */
435
-    static void mkdir(String path, Callback callback) {
492
+    static void mkdir(String path, Promise promise) {
436 493
         File dest = new File(path);
437 494
         if(dest.exists()) {
438
-            callback.invoke("mkdir failed, folder already exists at " + path);
495
+            promise.reject("EEXIST", dest.isDirectory() ? "Folder" : "File" + " '" + path + "' already exists");
439 496
             return;
440 497
         }
441
-        dest.mkdirs();
442
-        callback.invoke();
498
+        try {
499
+            boolean result = dest.mkdirs();
500
+            if (!result) {
501
+                promise.reject("EUNSPECIFIED", "mkdir failed to create some or all directories in '" + path + "'");
502
+                return;
503
+            }
504
+        } catch (Exception e) {
505
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
506
+            return;
507
+        }
508
+        promise.resolve(true);
443 509
     }
444 510
 
445 511
     /**
@@ -449,19 +515,22 @@ public class RNFetchBlobFS {
449 515
      * @param callback  JS context callback
450 516
      */
451 517
     static void cp(String path, String dest, Callback callback) {
452
-
453 518
         path = normalizePath(path);
454 519
         InputStream in = null;
455 520
         OutputStream out = null;
456 521
 
457 522
         try {
458
-
459 523
             if(!isPathExists(path)) {
460
-                callback.invoke("source file at path`" + path + "` does not exist");
524
+                callback.invoke("Source file at path`" + path + "` does not exist");
461 525
                 return;
462 526
             }
463
-            if(!new File(dest).exists())
464
-                new File(dest).createNewFile();
527
+            if(!new File(dest).exists()) {
528
+                boolean result = new File(dest).createNewFile();
529
+                if (!result) {
530
+                    callback.invoke("Destination file at '" + dest + "' already exists");
531
+                    return;
532
+                }
533
+            }
465 534
 
466 535
             in = inputStreamFromPath(path);
467 536
             out = new FileOutputStream(dest);
@@ -471,7 +540,6 @@ public class RNFetchBlobFS {
471 540
             while ((len = in.read(buf)) > 0) {
472 541
                 out.write(buf, 0, len);
473 542
             }
474
-
475 543
         } catch (Exception err) {
476 544
             callback.invoke(err.getLocalizedMessage());
477 545
         } finally {
@@ -498,10 +566,19 @@ public class RNFetchBlobFS {
498 566
     static void mv(String path, String dest, Callback callback) {
499 567
         File src = new File(path);
500 568
         if(!src.exists()) {
501
-            callback.invoke("source file at path `" + path + "` does not exist");
569
+            callback.invoke("Source file at path `" + path + "` does not exist");
570
+            return;
571
+        }
572
+        try {
573
+            boolean result = src.renameTo(new File(dest));
574
+            if (!result) {
575
+                callback.invoke("mv failed for unknown reasons");
576
+                return;
577
+            }
578
+        } catch (Exception e) {
579
+            callback.invoke(e.getLocalizedMessage());
502 580
             return;
503 581
         }
504
-        src.renameTo(new File(dest));
505 582
         callback.invoke();
506 583
     }
507 584
 
@@ -511,11 +588,10 @@ public class RNFetchBlobFS {
511 588
      * @param callback  JS context callback
512 589
      */
513 590
     static void exists(String path, Callback callback) {
514
-
515 591
         if(isAsset(path)) {
516 592
             try {
517 593
                 String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
518
-                AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(filename);
594
+                com.RNFetchBlob.RNFetchBlob.RCTContext.getAssets().openFd(filename);
519 595
                 callback.invoke(true, false);
520 596
             } catch (IOException e) {
521 597
                 callback.invoke(false, false);
@@ -534,48 +610,67 @@ public class RNFetchBlobFS {
534 610
      * @param path Target folder
535 611
      * @param callback  JS context callback
536 612
      */
537
-    static void ls(String path, Callback callback) {
538
-        path = normalizePath(path);
539
-        File src = new File(path);
540
-        if (!src.exists() || !src.isDirectory()) {
541
-            callback.invoke("failed to list path `" + path + "` for it is not exist or it is not a folder");
542
-            return;
543
-        }
544
-        String[] files = new File(path).list();
545
-        WritableArray arg = Arguments.createArray();
546
-        for (String i : files) {
547
-            arg.pushString(i);
613
+    static void ls(String path, Promise promise) {
614
+        try {
615
+            path = normalizePath(path);
616
+            File src = new File(path);
617
+            if (!src.exists()) {
618
+                promise.reject("ENOENT", "No such file '" + path + "'");
619
+                return;
620
+            }
621
+            if (!src.isDirectory()) {
622
+                promise.reject("ENOTDIR", "Not a directory '" + path + "'");
623
+                return;
624
+            }
625
+            String[] files = new File(path).list();
626
+            WritableArray arg = Arguments.createArray();
627
+            // File => list(): "If this abstract pathname does not denote a directory, then this method returns null."
628
+            // We excluded that possibility above - ignore the "can produce NullPointerException" warning of the IDE.
629
+            for (String i : files) {
630
+                arg.pushString(i);
631
+            }
632
+            promise.resolve(arg);
633
+        } catch (Exception e) {
634
+            e.printStackTrace();
635
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
548 636
         }
549
-        callback.invoke(null, arg);
550 637
     }
551 638
 
552 639
     /**
553 640
      * Create a file by slicing given file path
554
-     * @param src   Source file path
641
+     * @param path   Source file path
555 642
      * @param dest  Destination of created file
556 643
      * @param start Start byte offset in source file
557 644
      * @param end   End byte offset
558 645
      * @param encode NOT IMPLEMENTED
559 646
      */
560
-    public static void slice(String src, String dest, int start, int end, String encode, Promise promise) {
647
+    static void slice(String path, String dest, int start, int end, String encode, Promise promise) {
561 648
         try {
562
-            src = normalizePath(src);
563
-            File source = new File(src);
564
-            if(!source.exists()) {
565
-                promise.reject("RNFetchBlob slice error", "source file : " + src + " does not exist");
649
+            path = normalizePath(path);
650
+            File source = new File(path);
651
+            if(source.isDirectory()){
652
+                promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory");
653
+                return;
654
+            }
655
+            if(!source.exists()){
656
+                promise.reject("ENOENT", "No such file '" + path + "'");
566 657
                 return;
567 658
             }
568
-            long size = source.length();
569
-            long max = Math.min(size, end);
570
-            long expected = max - start;
571
-            long now = 0;
572
-            FileInputStream in = new FileInputStream(new File(src));
659
+            int size = (int) source.length();
660
+            int max = Math.min(size, end);
661
+            int expected = max - start;
662
+            int now = 0;
663
+            FileInputStream in = new FileInputStream(new File(path));
573 664
             FileOutputStream out = new FileOutputStream(new File(dest));
574
-            in.skip(start);
575
-            byte [] buffer = new byte[10240];
665
+            int skipped = (int) in.skip(start);
666
+            if (skipped != start) {
667
+                promise.reject("EUNSPECIFIED", "Skipped " + skipped + " instead of the specified " + start + " bytes, size is " + size);
668
+                return;
669
+            }
670
+            byte[] buffer = new byte[10240];
576 671
             while(now < expected) {
577
-                long read = in.read(buffer, 0, 10240);
578
-                long remain = expected - now;
672
+                int read = in.read(buffer, 0, 10240);
673
+                int remain = expected - now;
579 674
                 if(read <= 0) {
580 675
                     break;
581 676
                 }
@@ -588,7 +683,7 @@ public class RNFetchBlobFS {
588 683
             promise.resolve(dest);
589 684
         } catch (Exception e) {
590 685
             e.printStackTrace();
591
-            promise.reject("RNFetchBlob slice error", e.getLocalizedMessage());
686
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
592 687
         }
593 688
     }
594 689
 
@@ -610,6 +705,8 @@ public class RNFetchBlobFS {
610 705
                 }
611 706
                 if(src.isDirectory()) {
612 707
                     String [] files = src.list();
708
+                    // File => list(): "If this abstract pathname does not denote a directory, then this method returns null."
709
+                    // We excluded that possibility above - ignore the "can produce NullPointerException" warning of the IDE.
613 710
                     for(String p : files) {
614 711
                         res.pushMap(statFile(src.getPath() + "/" + p));
615 712
                     }
@@ -625,8 +722,8 @@ public class RNFetchBlobFS {
625 722
 
626 723
     /**
627 724
      * show status of a file or directory
628
-     * @param path
629
-     * @param callback
725
+     * @param path  Path
726
+     * @param callback  Callback
630 727
      */
631 728
     static void stat(String path, Callback callback) {
632 729
         try {
@@ -643,8 +740,8 @@ public class RNFetchBlobFS {
643 740
 
644 741
     /**
645 742
      * Basic stat method
646
-     * @param path
647
-     * @return Stat result of a file or path
743
+     * @param path  Path
744
+     * @return Stat  Result of a file or path
648 745
      */
649 746
     static WritableMap statFile(String path) {
650 747
         try {
@@ -680,9 +777,9 @@ public class RNFetchBlobFS {
680 777
 
681 778
     /**
682 779
      * Media scanner scan file
683
-     * @param path
684
-     * @param mimes
685
-     * @param callback
780
+     * @param path  Path to file
781
+     * @param mimes  Array of MIME type strings
782
+     * @param callback  Callback for results
686 783
      */
687 784
     void scanFile(String [] path, String[] mimes, final Callback callback) {
688 785
         try {
@@ -708,17 +805,20 @@ public class RNFetchBlobFS {
708 805
             algorithms.put("sha384", "SHA-384");
709 806
             algorithms.put("sha512", "SHA-512");
710 807
 
711
-            if (!algorithms.containsKey(algorithm)) throw new Exception("Invalid hash algorithm");
808
+            if (!algorithms.containsKey(algorithm)) {
809
+                promise.reject("EINVAL", "Invalid algorithm '" + algorithm + "', must be one of md5, sha1, sha224, sha256, sha384, sha512");
810
+                return;
811
+            }
712 812
 
713 813
             File file = new File(path);
714 814
 
715 815
             if (file.isDirectory()) {
716
-                promise.reject("hash error", "EISDIR: illegal operation on a directory, read");
816
+                promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory");
717 817
                 return;
718 818
             }
719 819
 
720 820
             if (!file.exists()) {
721
-                promise.reject("hash error", "ENOENT: no such file or directory, open '" + path + "'");
821
+                promise.reject("ENOENT", "No such file '" + path + "'");
722 822
                 return;
723 823
             }
724 824
 
@@ -737,9 +837,9 @@ public class RNFetchBlobFS {
737 837
                 hexString.append(String.format("%02x", digestByte));
738 838
 
739 839
             promise.resolve(hexString.toString());
740
-        } catch (Exception ex) {
741
-            ex.printStackTrace();
742
-            promise.reject("hash error", ex.getLocalizedMessage());
840
+        } catch (Exception e) {
841
+            e.printStackTrace();
842
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
743 843
         }
744 844
     }
745 845
 
@@ -748,9 +848,9 @@ public class RNFetchBlobFS {
748 848
      * @param path The destination path of the new file.
749 849
      * @param data Initial data of the new file.
750 850
      * @param encoding Encoding of initial data.
751
-     * @param callback RCT bridge callback.
851
+     * @param promise Promise for Javascript
752 852
      */
753
-    static void createFile(String path, String data, String encoding, Callback callback) {
853
+    static void createFile(String path, String data, String encoding, Promise promise) {
754 854
         try {
755 855
             File dest = new File(path);
756 856
             boolean created = dest.createNewFile();
@@ -758,7 +858,7 @@ public class RNFetchBlobFS {
758 858
                 String orgPath = data.replace(RNFetchBlobConst.FILE_PREFIX, "");
759 859
                 File src = new File(orgPath);
760 860
                 if(!src.exists()) {
761
-                    callback.invoke("source file : " + data + " does not exist");
861
+                    promise.reject("ENOENT", "Source file : " + data + " does not exist");
762 862
                     return ;
763 863
                 }
764 864
                 FileInputStream fin = new FileInputStream(src);
@@ -773,17 +873,15 @@ public class RNFetchBlobFS {
773 873
                 ostream.close();
774 874
             } else {
775 875
                 if (!created) {
776
-                    callback.invoke("failed to create new file at path `" + path + "` because its parent path " +
777
-                            "may not exist, or the file already exists. If you intended to overwrite the " +
778
-                            "existing file use fs.writeFile instead.");
876
+                    promise.reject("EEXIST", "File `" + path + "` already exists");
779 877
                     return;
780 878
                 }
781 879
                 OutputStream ostream = new FileOutputStream(dest);
782 880
                 ostream.write(RNFetchBlobFS.stringToBytes(data, encoding));
783 881
             }
784
-            callback.invoke(null, path);
882
+            promise.resolve(path);
785 883
         } catch(Exception err) {
786
-            callback.invoke(err.getLocalizedMessage());
884
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
787 885
         }
788 886
     }
789 887
 
@@ -791,30 +889,25 @@ public class RNFetchBlobFS {
791 889
      * Create file for ASCII encoding
792 890
      * @param path  Path of new file.
793 891
      * @param data  Content of new file
794
-     * @param callback  JS context callback
892
+     * @param promise  JS Promise
795 893
      */
796
-    static void createFileASCII(String path, ReadableArray data, Callback callback) {
894
+    static void createFileASCII(String path, ReadableArray data, Promise promise) {
797 895
         try {
798 896
             File dest = new File(path);
799
-            if(dest.exists()) {
800
-                callback.invoke("failed to create new file at path `" + path + "`, file already exists.");
801
-                return;
802
-            }
803 897
             boolean created = dest.createNewFile();
804 898
             if(!created) {
805
-                callback.invoke("failed to create new file at path `" + path + "` because its parent path may not exist");
899
+                promise.reject("EEXIST", "File at path `" + path + "` already exists");
806 900
                 return;
807 901
             }
808 902
             OutputStream ostream = new FileOutputStream(dest);
809
-            byte [] chunk = new byte[data.size()];
810
-            for(int i =0; i<data.size();i++) {
903
+            byte[] chunk = new byte[data.size()];
904
+            for(int i=0; i<data.size(); i++) {
811 905
                 chunk[i] = (byte) data.getInt(i);
812 906
             }
813 907
             ostream.write(chunk);
814
-            chunk = null;
815
-            callback.invoke(null, path);
908
+            promise.resolve(path);
816 909
         } catch(Exception err) {
817
-            callback.invoke(err.getLocalizedMessage());
910
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
818 911
         }
819 912
     }
820 913
 
@@ -838,17 +931,31 @@ public class RNFetchBlobFS {
838 931
      * @param callback JS contest callback
839 932
      */
840 933
     static void removeSession(ReadableArray paths, final Callback callback) {
841
-
842 934
         AsyncTask<ReadableArray, Integer, Integer> task = new AsyncTask<ReadableArray, Integer, Integer>() {
843 935
             @Override
844 936
             protected Integer doInBackground(ReadableArray ...paths) {
845 937
                 try {
938
+                    ArrayList<String> failuresToDelete = new ArrayList<>();
846 939
                     for (int i = 0; i < paths[0].size(); i++) {
847
-                        File f = new File(paths[0].getString(i));
848
-                        if (f.exists())
849
-                            f.delete();
940
+                        String fileName = paths[0].getString(i);
941
+                        File f = new File(fileName);
942
+                        if (f.exists()) {
943
+                            boolean result = f.delete();
944
+                            if (!result) {
945
+                                failuresToDelete.add(fileName);
946
+                            }
947
+                        }
948
+                    }
949
+                    if (failuresToDelete.isEmpty()) {
950
+                        callback.invoke(null, true);
951
+                    } else {
952
+                        StringBuilder listString = new StringBuilder();
953
+                        listString.append("Failed to delete: ");
954
+                        for (String s : failuresToDelete) {
955
+                            listString.append(s).append(", ");
956
+                        }
957
+                        callback.invoke(listString.toString());
850 958
                     }
851
-                    callback.invoke(null, true);
852 959
                 } catch(Exception err) {
853 960
                     callback.invoke(err.getLocalizedMessage());
854 961
                 }
@@ -891,6 +998,7 @@ public class RNFetchBlobFS {
891 998
         this.emitter.emit(streamName, eventData);
892 999
     }
893 1000
 
1001
+    // "event" always is "data"...
894 1002
     private void emitStreamEvent(String streamName, String event, WritableArray data) {
895 1003
         WritableMap eventData = Arguments.createMap();
896 1004
         eventData.putString("event", event);
@@ -898,12 +1006,13 @@ public class RNFetchBlobFS {
898 1006
         this.emitter.emit(streamName, eventData);
899 1007
     }
900 1008
 
901
-    // TODO : should we remove this ?
902
-    void emitFSData(String taskId, String event, String data) {
1009
+    // "event" always is "error"...
1010
+    private void emitStreamEvent(String streamName, String event, String code, String message) {
903 1011
         WritableMap eventData = Arguments.createMap();
904 1012
         eventData.putString("event", event);
905
-        eventData.putString("detail", data);
906
-        this.emitter.emit("RNFetchBlobStream" + taskId, eventData);
1013
+        eventData.putString("code", code);
1014
+        eventData.putString("detail", message);
1015
+        this.emitter.emit(streamName, eventData);
907 1016
     }
908 1017
 
909 1018
     /**
@@ -911,9 +1020,9 @@ public class RNFetchBlobFS {
911 1020
      * the stream is created by Assets Manager, otherwise use FileInputStream.
912 1021
      * @param path The file to open stream
913 1022
      * @return InputStream instance
914
-     * @throws IOException
1023
+     * @throws IOException If the given file does not exist or is a directory FileInputStream will throw a FileNotFoundException
915 1024
      */
916
-    static InputStream inputStreamFromPath(String path) throws IOException {
1025
+    private static InputStream inputStreamFromPath(String path) throws IOException {
917 1026
         if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
918 1027
             return RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
919 1028
         }
@@ -925,10 +1034,10 @@ public class RNFetchBlobFS {
925 1034
      * @param path A file path URI string
926 1035
      * @return A boolean value represents if the path exists.
927 1036
      */
928
-    static boolean isPathExists(String path) {
1037
+    private static boolean isPathExists(String path) {
929 1038
         if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
930 1039
             try {
931
-                RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
1040
+                RNFetchBlob.RCTContext.getAssets().open(path.replace(com.RNFetchBlob.RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
932 1041
             } catch (IOException e) {
933 1042
                 return false;
934 1043
             }
@@ -941,9 +1050,7 @@ public class RNFetchBlobFS {
941 1050
     }
942 1051
 
943 1052
     static boolean isAsset(String path) {
944
-        if(path != null)
945
-            return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
946
-        return false;
1053
+        return path != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
947 1054
     }
948 1055
 
949 1056
     /**

+ 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;

+ 33
- 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;
@@ -84,7 +92,6 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
84 92
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
85 93
     static ConnectionPool pool = new ConnectionPool();
86 94
 
87
-    ReactApplicationContext ctx;
88 95
     RNFetchBlobConfig options;
89 96
     String taskId;
90 97
     String method;
@@ -170,7 +177,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
170 177
                 }
171 178
                 // set headers
172 179
                 ReadableMapKeySetIterator it = headers.keySetIterator();
173
-                if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable") == true ) {
180
+                if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable")) {
174 181
                     req.allowScanningByMediaScanner();
175 182
                 }
176 183
                 while (it.hasNextKey()) {
@@ -197,7 +204,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
197 204
                 cacheKey = this.taskId;
198 205
             }
199 206
 
200
-            File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext);
207
+            File file = new File(RNFetchBlobFS.getTmpPath(cacheKey) + ext);
201 208
 
202 209
             if (file.exists()) {
203 210
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
@@ -208,7 +215,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
208 215
         if(this.options.path != null)
209 216
             this.destPath = this.options.path;
210 217
         else if(this.options.fileCache)
211
-            this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
218
+            this.destPath = RNFetchBlobFS.getTmpPath(cacheKey) + ext;
212 219
 
213 220
 
214 221
         OkHttpClient.Builder clientBuilder;
@@ -330,7 +337,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
330 337
             // Add request interceptor for upload progress event
331 338
             clientBuilder.addInterceptor(new Interceptor() {
332 339
                 @Override
333
-                public Response intercept(Chain chain) throws IOException {
340
+                public Response intercept(@NonNull Chain chain) throws IOException {
334 341
                     try {
335 342
                         Response originalResponse = chain.proceed(req);
336 343
                         ResponseBody extended;
@@ -392,7 +399,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
392 399
             call.enqueue(new okhttp3.Callback() {
393 400
 
394 401
                 @Override
395
-                public void onFailure(Call call, IOException e) {
402
+                public void onFailure(@NonNull Call call, IOException e) {
396 403
                     cancelTask(taskId);
397 404
                     if(respInfo == null) {
398 405
                         respInfo = Arguments.createMap();
@@ -409,7 +416,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
409 416
                 }
410 417
 
411 418
                 @Override
412
-                public void onResponse(Call call, Response response) throws IOException {
419
+                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
413 420
                     ReadableMap notifyConfig = options.addAndroidDownloads;
414 421
                     // Download manager settings
415 422
                     if(notifyConfig != null ) {
@@ -470,11 +477,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
470 477
                     // For XMLHttpRequest, automatic response data storing strategy, when response
471 478
                     // data is considered as binary data, write it to file system
472 479
                     if(isBlobResp && options.auto) {
473
-                        String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
480
+                        String dest = RNFetchBlobFS.getTmpPath(taskId);
474 481
                         InputStream ins = resp.body().byteStream();
475 482
                         FileOutputStream os = new FileOutputStream(new File(dest));
476 483
                         int read;
477
-                        byte [] buffer = new byte[10240];
484
+                        byte[] buffer = new byte[10240];
478 485
                         while ((read = ins.read(buffer)) != -1) {
479 486
                             os.write(buffer, 0, read);
480 487
                         }
@@ -670,11 +677,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
670 677
                             options.addAndroidDownloads.getString("mime").contains("image")) {
671 678
                         Uri uri = Uri.parse(contentUri);
672 679
                         Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
673
-
674
-                            // use default destination of DownloadManager
680
+                        // use default destination of DownloadManager
675 681
                         if (cursor != null) {
676 682
                             cursor.moveToFirst();
677 683
                             filePath = cursor.getString(0);
684
+                            cursor.close();
678 685
                         }
679 686
                     }
680 687
                 }
@@ -708,7 +715,19 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
708 715
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
709 716
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
710 717
             try {
711
-                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);
712 731
 
713 732
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
714 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') {

+ 171
- 156
fs.js View File

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

+ 0
- 1
index.js View File

@@ -540,7 +540,6 @@ class FetchBlobResponse {
540 540
      */
541 541
     this.readFile = (encoding: 'base64' | 'utf8' | 'ascii') => {
542 542
       if(this.type === 'path') {
543
-          encoding = encoding || 'utf8'
544 543
         return readFile(this.data, encoding)
545 544
       }
546 545
       else {

+ 1
- 1
ios.js View File

@@ -43,7 +43,7 @@ function openDocument(path:string, scheme:string) {
43 43
  * @param  {string} url URL of the resource, only file URL is supported
44 44
  * @return {Promise}
45 45
  */
46
-function excludeFromBackupKey(url:string) {
46
+function excludeFromBackupKey(path:string) {
47 47
   return RNFetchBlob.excludeFromBackupKey('file://' + path);
48 48
 }
49 49
 

+ 73
- 52
ios/RNFetchBlob/RNFetchBlob.m View File

@@ -135,8 +135,12 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
135 135
 }
136 136
 
137 137
 #pragma mark - fs.createFile
138
-RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) {
139
-
138
+RCT_EXPORT_METHOD(createFile:(NSString *)path
139
+                  data:(NSString *)data
140
+                  encoding:(NSString *)encoding
141
+                  resolver:(RCTPromiseResolveBlock)resolve
142
+                  rejecter:(RCTPromiseRejectBlock)reject)
143
+{
140 144
     NSFileManager * fm = [NSFileManager defaultManager];
141 145
     NSData * fileContent = nil;
142 146
 
@@ -154,18 +158,25 @@ RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NS
154 158
         fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
155 159
     }
156 160
 
157
-    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
158
-    if(success == YES)
159
-        callback(@[[NSNull null]]);
160
-    else
161
-        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
162
-
161
+    if ([fm fileExistsAtPath:path]) {
162
+        reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil);
163
+    }
164
+    else {
165
+        BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
166
+        if(success == YES)
167
+            resolve(@[[NSNull null]]);
168
+        else
169
+            reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create new file at path '%@', please ensure the folder exists", path], nil);
170
+    }
163 171
 }
164 172
 
165 173
 #pragma mark - fs.createFileASCII
166 174
 // method for create file with ASCII content
167
-RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray callback:(RCTResponseSenderBlock)callback) {
168
-
175
+RCT_EXPORT_METHOD(createFileASCII:(NSString *)path
176
+                  data:(NSArray *)dataArray
177
+                  resolver:(RCTPromiseResolveBlock)resolve
178
+                  rejecter:(RCTPromiseRejectBlock)reject)
179
+{
169 180
     NSFileManager * fm = [NSFileManager defaultManager];
170 181
     NSMutableData * fileContent = [NSMutableData alloc];
171 182
     // prevent stack overflow, alloc on heap
@@ -174,14 +185,21 @@ RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray cal
174 185
     for(int i = 0; i < dataArray.count; i++) {
175 186
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
176 187
     }
188
+
177 189
     [fileContent appendBytes:bytes length:dataArray.count];
178
-    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
179
-    free(bytes);
180
-    if(success == YES)
181
-        callback(@[[NSNull null]]);
182
-    else
183
-        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
184 190
 
191
+    if ([fm fileExistsAtPath:path]) {
192
+        reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil);
193
+    }
194
+    else {
195
+        BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
196
+        if(success == YES)
197
+            resolve(@[[NSNull null]]);
198
+        else
199
+            reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"failed to create new file at path '%@', please ensure the folder exists", path], nil);
200
+    }
201
+
202
+    free(bytes);
185 203
 }
186 204
 
187 205
 #pragma mark - fs.pathForAppGroup
@@ -194,7 +212,7 @@ RCT_EXPORT_METHOD(pathForAppGroup:(NSString *)groupName
194 212
     if(path) {
195 213
         resolve(path);
196 214
     } else {
197
-        reject(@"RNFetchBlob pathForAppGroup Error", @"could not find path for app group", nil);
215
+        reject(@"EUNSPECIFIED", @"could not find path for app group", nil);
198 216
     }
199 217
 }
200 218
 
@@ -220,14 +238,26 @@ RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding
220 238
 {
221 239
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
222 240
     NSFileManager * fm = [NSFileManager defaultManager];
223
-    BOOL isDir = nil;
224
-    BOOL exist = [fm fileExistsAtPath:path isDirectory:&isDir];
225
-    if( exist == NO || isDir == YES) {
226
-        callback(@[[NSString stringWithFormat:@"target path `%@` may not exist or it is a folder", path]]);
227
-        return;
241
+    NSString * folder = [path stringByDeletingLastPathComponent];
242
+    NSError* err = nil;
243
+    BOOL isDir = NO;
244
+    BOOL exists = [fm fileExistsAtPath:path isDirectory: &isDir];
245
+
246
+    if(!exists) {
247
+        [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
248
+        if(err != nil) {
249
+            callback(@[@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]]]);
250
+        }
251
+        if(![fm createFileAtPath:path contents:nil attributes:nil]) {
252
+            callback(@[@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path]]);
253
+        }
228 254
     }
255
+    else if(isDir) {
256
+        callback(@[@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]]);
257
+    }
258
+
229 259
     NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append];
230
-    callback(@[[NSNull null], streamId]);
260
+    callback(@[[NSNull null], [NSNull null], streamId]);
231 261
 }
232 262
 
233 263
 #pragma mark - fs.writeArrayChunk
@@ -291,24 +321,25 @@ RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBloc
291 321
 }
292 322
 
293 323
 #pragma mark - fs.ls
294
-RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback)
324
+RCT_EXPORT_METHOD(ls:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
295 325
 {
296 326
     NSFileManager* fm = [NSFileManager defaultManager];
297 327
     BOOL exist = nil;
298 328
     BOOL isDir = nil;
299 329
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
300
-    if(exist == NO || isDir == NO) {
301
-        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not a folder", path]]);
302
-        return ;
330
+    if(exist == NO) {
331
+        return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil);
332
+    }
333
+    if(isDir == NO) {
334
+        return reject(@"ENOTDIR", [NSString stringWithFormat:@"Not a directory '%@'", path], nil);
303 335
     }
304 336
     NSError * error = nil;
305 337
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
306 338
 
307 339
     if(error == nil)
308
-        callback(@[[NSNull null], result == nil ? [NSNull null] :result ]);
340
+        resolve(result);
309 341
     else
310
-        callback(@[[error localizedDescription], [NSNull null]]);
311
-
342
+        reject(@"EUNSPECIFIED", [error description], nil);
312 343
 }
313 344
 
314 345
 #pragma mark - fs.stat
@@ -389,7 +420,6 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
389 420
 #pragma mark - fs.cp
390 421
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
391 422
 {
392
-
393 423
 //    path = [RNFetchBlobFS getPathOfAsset:path];
394 424
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
395 425
         NSError * error = nil;
@@ -400,6 +430,7 @@ RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTRespons
400 430
         }
401 431
         else
402 432
         {
433
+            // If the destination exists there will be an error
403 434
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
404 435
 
405 436
             if(error == nil)
@@ -408,7 +439,6 @@ RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTRespons
408 439
                 callback(@[[error localizedDescription], @NO]);
409 440
         }
410 441
     }];
411
-
412 442
 }
413 443
 
414 444
 
@@ -426,15 +456,9 @@ RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTRespo
426 456
 }
427 457
 
428 458
 #pragma mark - fs.mkdir
429
-RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback)
459
+RCT_EXPORT_METHOD(mkdir:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
430 460
 {
431
-    if([[NSFileManager defaultManager] fileExistsAtPath:path]) {
432
-        callback(@[@"mkdir failed, folder already exists"]);
433
-        return;
434
-    }
435
-    else
436
-        [RNFetchBlobFS mkdir:path];
437
-    callback(@[[NSNull null]]);
461
+    [RNFetchBlobFS mkdir:path resolver:resolve rejecter:reject];
438 462
 }
439 463
 
440 464
 #pragma mark - fs.readFile
@@ -443,19 +467,16 @@ RCT_EXPORT_METHOD(readFile:(NSString *)path
443 467
                   resolver:(RCTPromiseResolveBlock)resolve
444 468
                   rejecter:(RCTPromiseRejectBlock)reject)
445 469
 {
446
-
447
-    [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(id content, NSString * err) {
448
-        if(err != nil)
449
-        {
450
-            reject(@"RNFetchBlob readFile Error", err, nil);
470
+    
471
+    [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(NSData * content, NSString * code, NSString * err) {
472
+        if(err != nil) {
473
+            reject(code, err, nil);
451 474
             return;
452 475
         }
453
-        if(encoding == @"ascii")
454
-        {
476
+        if(encoding == @"ascii") {
455 477
             resolve((NSMutableArray *)content);
456 478
         }
457
-        else
458
-        {
479
+        else {
459 480
             resolve((NSString *)content);
460 481
         }
461 482
     }];
@@ -538,7 +559,7 @@ RCT_EXPORT_METHOD(previewDocument:(NSString*)uri scheme:(NSString *)scheme resol
538 559
       });
539 560
         resolve(@[[NSNull null]]);
540 561
     } else {
541
-        reject(@"RNFetchBlob previewDocument Error", @"scheme is not supported", nil);
562
+        reject(@"EINVAL", @"scheme is not supported", nil);
542 563
     }
543 564
 }
544 565
 
@@ -558,7 +579,7 @@ RCT_EXPORT_METHOD(openDocument:(NSString*)uri scheme:(NSString *)scheme resolver
558 579
         });
559 580
         resolve(@[[NSNull null]]);
560 581
     } else {
561
-        reject(@"RNFetchBlob openDocument Error", @"scheme is not supported", nil);
582
+        reject(@"EINVAL", @"scheme is not supported", nil);
562 583
     }
563 584
 }
564 585
 
@@ -572,7 +593,7 @@ RCT_EXPORT_METHOD(excludeFromBackupKey:(NSString *)url resolver:(RCTPromiseResol
572 593
     {
573 594
         resolve(@[[NSNull null]]);
574 595
     } else {
575
-        reject(@"RNFetchBlob excludeFromBackupKey Error", [error description], nil);
596
+        reject(@"EUNSPECIFIED", [error description], nil);
576 597
     }
577 598
 
578 599
 }

+ 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

+ 154
- 91
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>
@@ -164,7 +166,7 @@ NSMutableDictionary *fileStreams = nil;
164 166
                 if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
165 167
                 {
166 168
                     NSString * message = [NSString stringWithFormat:@"File does not exist at path %@", path];
167
-                    NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": message };
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
@@ -330,7 +332,12 @@ 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];
@@ -339,7 +346,16 @@ NSMutableDictionary *fileStreams = nil;
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]) {
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);
356
+        }
357
+
358
+        if(!exists) {
343 359
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
344 360
             if(err != nil) {
345 361
                 return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
@@ -348,6 +364,7 @@ NSMutableDictionary *fileStreams = nil;
348 364
                 return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
349 365
             }
350 366
         }
367
+
351 368
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
352 369
         NSData * content = nil;
353 370
         if([encoding RNFBContainsString:@"base64"]) {
@@ -356,7 +373,7 @@ NSMutableDictionary *fileStreams = nil;
356 373
         else if([encoding isEqualToString:@"uri"]) {
357 374
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
358 375
                 if(errMsg != nil)
359
-                    reject(@"RNFetchBlob writeFile Error", errMsg, nil);
376
+                    reject(@"EUNSPECIFIED", errMsg, nil);
360 377
                 else
361 378
                     resolve(size);
362 379
             }];
@@ -379,22 +396,40 @@ NSMutableDictionary *fileStreams = nil;
379 396
     }
380 397
     @catch (NSException * e)
381 398
     {
382
-        reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
399
+        reject(@"EUNSPECIFIED", [e description], nil);
383 400
     }
384 401
 }
385 402
 
386 403
 # pragma mark - write file (array)
387 404
 
388
-+ (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
+{
389 411
     @try {
390 412
         NSFileManager * fm = [NSFileManager defaultManager];
391 413
         NSError * err = nil;
392 414
         // check if the folder exists, if not exists, create folders recursively
393 415
         // after the folders created, write data into the file
394 416
         NSString * folder = [path stringByDeletingLastPathComponent];
395
-        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) {
396 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
+            }
397 431
         }
432
+
398 433
         NSMutableData * fileContent = [NSMutableData alloc];
399 434
         // prevent stack overflow, alloc on heap
400 435
         char * bytes = (char*) malloc([data count]);
@@ -402,8 +437,11 @@ NSMutableDictionary *fileStreams = nil;
402 437
             bytes[i] = [[data objectAtIndex:i] charValue];
403 438
         }
404 439
         [fileContent appendBytes:bytes length:data.count];
405
-        if(![fm fileExistsAtPath:path]) {
406
-            [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
+            }
407 445
         }
408 446
         // if file exists, write file
409 447
         else {
@@ -423,7 +461,7 @@ NSMutableDictionary *fileStreams = nil;
423 461
     }
424 462
     @catch (NSException * e)
425 463
     {
426
-        reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
464
+        reject(@"EUNSPECIFIED", [e description], nil);
427 465
     }
428 466
 }
429 467
 
@@ -431,7 +469,7 @@ NSMutableDictionary *fileStreams = nil;
431 469
 
432 470
 + (void) readFile:(NSString *)path
433 471
          encoding:(NSString *)encoding
434
-       onComplete:(void (^)(id content, NSString * errMsg))onComplete
472
+       onComplete:(void (^)(NSData * content, NSString * codeStr, NSString * errMsg))onComplete
435 473
 {
436 474
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
437 475
         __block NSData * fileContent;
@@ -443,7 +481,7 @@ NSMutableDictionary *fileStreams = nil;
443 481
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
444 482
             if(err != nil)
445 483
             {
446
-                onComplete(nil, [err description]);
484
+                onComplete(nil, @"EUNSPECIFIED", [err description]);
447 485
                 free(buffer);
448 486
                 return;
449 487
             }
@@ -452,8 +490,13 @@ NSMutableDictionary *fileStreams = nil;
452 490
         }
453 491
         else
454 492
         {
455
-            if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {
456
-                onComplete(nil, @"file does not exist");
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
+                }
457 500
                 return;
458 501
             }
459 502
             fileContent = [NSData dataWithContentsOfFile:path];
@@ -466,12 +509,12 @@ NSMutableDictionary *fileStreams = nil;
466 509
             {
467 510
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
468 511
                 if(utf8 == nil)
469
-                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil);
512
+                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil, nil);
470 513
                 else
471
-                    onComplete(utf8, nil);
514
+                    onComplete(utf8, nil, nil);
472 515
             }
473 516
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
474
-                onComplete([fileContent base64EncodedStringWithOptions:0], nil);
517
+                onComplete([fileContent base64EncodedStringWithOptions:0], nil, nil);
475 518
             }
476 519
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
477 520
                 NSMutableArray * resultArray = [NSMutableArray array];
@@ -479,12 +522,12 @@ NSMutableDictionary *fileStreams = nil;
479 522
                 for(int i=0;i<[fileContent length];i++) {
480 523
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
481 524
                 }
482
-                onComplete(resultArray, nil);
525
+                onComplete(resultArray, nil, nil);
483 526
             }
484 527
         }
485 528
         else
486 529
         {
487
-            onComplete(fileContent, nil);
530
+            onComplete(fileContent, nil, nil);
488 531
         }
489 532
 
490 533
     }];
@@ -492,85 +535,102 @@ NSMutableDictionary *fileStreams = nil;
492 535
 
493 536
 # pragma mark - hash
494 537
 
495
-RCT_EXPORT_METHOD(hash:(NSString *)filepath
538
++ (void) hash:(NSString *)path
496 539
                   algorithm:(NSString *)algorithm
497 540
                   resolver:(RCTPromiseResolveBlock)resolve
498
-                  rejecter:(RCTPromiseRejectBlock)reject)
541
+                  rejecter:(RCTPromiseRejectBlock)reject
499 542
 {
500
-  BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filepath];
501
-
502
-  if (!fileExists) {
503
-    return reject(@"ENOENT", [NSString stringWithFormat:@"ENOENT: no such file or directory, open '%@'", filepath], nil);
504
-  }
543
+    BOOL isDir = NO;
544
+    BOOL exists = NO;
545
+    exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
505 546
 
506
-  NSError *error = nil;
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
+    }
507 553
 
508
-  NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filepath error:&error];
554
+    NSError *error = nil;
509 555
 
510
-  if (error) {
511
-    return [self reject:reject withError:error];
512
-  }
556
+    NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
513 557
 
514
-  if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) {
515
-    return reject(@"EISDIR", @"EISDIR: illegal operation on a directory, read", nil);
516
-  }
558
+    if (error) {
559
+        reject(@"EUNKNOWN", [error description], nil);
560
+        return;
561
+    }
517 562
 
518
-  NSData *content = [[NSFileManager defaultManager] contentsAtPath:filepath];
563
+    if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) {
564
+        reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
565
+        return;
566
+    }
519 567
 
520
-  NSArray *keys = [NSArray arrayWithObjects:@"md5", @"sha1", @"sha224", @"sha256", @"sha384", @"sha512", nil];
568
+    NSData *content = [[NSFileManager defaultManager] contentsAtPath:path];
521 569
 
522
-  NSArray *digestLengths = [NSArray arrayWithObjects:
523
-    @CC_MD5_DIGEST_LENGTH,
524
-    @CC_SHA1_DIGEST_LENGTH,
525
-    @CC_SHA224_DIGEST_LENGTH,
526
-    @CC_SHA256_DIGEST_LENGTH,
527
-    @CC_SHA384_DIGEST_LENGTH,
528
-    @CC_SHA512_DIGEST_LENGTH,
529
-    nil];
570
+    NSArray *keys = [NSArray arrayWithObjects:@"md5", @"sha1", @"sha224", @"sha256", @"sha384", @"sha512", nil];
530 571
 
531
-  NSDictionary *keysToDigestLengths = [NSDictionary dictionaryWithObjects:digestLengths forKeys:keys];
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];
532 580
 
533
-  int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue];
581
+    NSDictionary *keysToDigestLengths = [NSDictionary dictionaryWithObjects:digestLengths forKeys:keys];
534 582
 
535
-  if (!digestLength) {
536
-    return reject(@"Error", [NSString stringWithFormat:@"Invalid hash algorithm '%@'", algorithm], nil);
537
-  }
583
+    int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue];
538 584
 
539
-  unsigned char buffer[digestLength];
585
+    if (!digestLength) {
586
+      return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil);
587
+    }
540 588
 
541
-  if ([algorithm isEqualToString:@"md5"]) {
542
-    CC_MD5(content.bytes, (CC_LONG)content.length, buffer);
543
-  } else if ([algorithm isEqualToString:@"sha1"]) {
544
-    CC_SHA1(content.bytes, (CC_LONG)content.length, buffer);
545
-  } else if ([algorithm isEqualToString:@"sha224"]) {
546
-    CC_SHA224(content.bytes, (CC_LONG)content.length, buffer);
547
-  } else if ([algorithm isEqualToString:@"sha256"]) {
548
-    CC_SHA256(content.bytes, (CC_LONG)content.length, buffer);
549
-  } else if ([algorithm isEqualToString:@"sha384"]) {
550
-    CC_SHA384(content.bytes, (CC_LONG)content.length, buffer);
551
-  } else if ([algorithm isEqualToString:@"sha512"]) {
552
-    CC_SHA512(content.bytes, (CC_LONG)content.length, buffer);
553
-  } else {
554
-    return reject(@"Error", [NSString stringWithFormat:@"Invalid hash algorithm '%@'", algorithm], nil);
555
-  }
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
+    }
556 607
 
557
-  NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2];
558
-  for(int i = 0; i < digestLength; i++)
559
-    [output appendFormat:@"%02x",buffer[i]];
608
+    NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2];
609
+    for(int i = 0; i < digestLength; i++)
610
+        [output appendFormat:@"%02x",buffer[i]];
560 611
 
561
-  resolve(output);
612
+    resolve(output);
562 613
 }
563 614
 
564 615
 # pragma mark - mkdir
565 616
 
566
-+ (BOOL) mkdir:(NSString *) path {
567
-    BOOL isDir;
617
++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
618
+{
619
+    BOOL isDir = NO;
568 620
     NSError * err = nil;
569
-    // if temp folder does not exist create it
570
-    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 {
571 626
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
572 627
     }
573
-    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
+    }
574 634
 }
575 635
 
576 636
 # pragma mark - stat
@@ -703,22 +763,28 @@ RCT_EXPORT_METHOD(hash:(NSString *)filepath
703 763
             NSFileManager * fm = [NSFileManager defaultManager];
704 764
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
705 765
             [os open];
706
-            // abort because the source file does not exist
707
-            if([fm fileExistsAtPath:path] == NO)
708
-            {
709
-                reject(@"RNFetchBlob slice Error : the file does not exist", path, nil);
710
-                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);
711 776
             }
777
+
712 778
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
713 779
             long max = MIN(size, [end longValue]);
714 780
 
715 781
             if(![fm fileExistsAtPath:dest]) {
716
-                [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
+                }
717 785
             }
718 786
             [handle seekToFileOffset:[start longValue]];
719
-            while(read < expected)
720
-            {
721
-
787
+            while(read < expected) {
722 788
                 NSData * chunk;
723 789
                 long chunkSize = 0;
724 790
                 if([start longValue] + read + 10240 > max)
@@ -754,9 +820,7 @@ RCT_EXPORT_METHOD(hash:(NSString *)filepath
754 820
             long size = asset.size;
755 821
             long max = MIN(size, [end longValue]);
756 822
 
757
-            while(read < expected)
758
-            {
759
-
823
+            while(read < expected) {
760 824
                 uint8_t * chunk[10240];
761 825
                 long chunkSize = 0;
762 826
                 if([start longValue] + read + 10240 > max)
@@ -781,9 +845,8 @@ RCT_EXPORT_METHOD(hash:(NSString *)filepath
781 845
             [os close];
782 846
             resolve(dest);
783 847
         }
784
-        else
785
-        {
786
-            reject(@"RNFetchBlob slice Error",  [NSString stringWithFormat: @"could not resolve URI %@", path ], nil);
848
+        else {
849
+            reject(@"EINVAL", [NSString stringWithFormat: @"Could not resolve URI %@", path ], nil);
787 850
         }
788 851
 
789 852
     }];

+ 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);

+ 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
     }