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
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
590
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
591
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
591
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
592
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
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
 - [readFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readfilepath-encodingpromise)
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
 - [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
598
 - [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
598
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
599
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
599
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
600
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
644
 
645
 
645
 When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`.
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
 ```js
687
 ```js
648
 RNFetchBlob.fs.writeStream(
688
 RNFetchBlob.fs.writeStream(
649
     PATH_TO_FILE,
689
     PATH_TO_FILE,
652
     // should data append to existing content ?
692
     // should data append to existing content ?
653
     true)
693
     true)
654
 .then((ofstream) => {
694
 .then((ofstream) => {
695
+    // BAD IDEA - Don't do this, those writes are unchecked:
655
     ofstream.write('foo')
696
     ofstream.write('foo')
656
     ofstream.write('bar')
697
     ofstream.write('bar')
657
     ofstream.close()
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
 ### Cache File Management
707
 ### Cache File Management
663
 
708
 
664
 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
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
 
34
 
35
 dependencies {
35
 dependencies {
36
     compile 'com.facebook.react:react-native:+'
36
     compile 'com.facebook.react:react-native:+'
37
+    //compile 'com.squareup.okhttp3:okhttp:+'
37
     //{RNFetchBlob_PRE_0.28_DEPDENDENCY}
38
     //{RNFetchBlob_PRE_0.28_DEPDENDENCY}
38
 }
39
 }

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

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

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

4
 import android.app.DownloadManager;
4
 import android.app.DownloadManager;
5
 import android.content.Intent;
5
 import android.content.Intent;
6
 import android.net.Uri;
6
 import android.net.Uri;
7
+import android.util.SparseArray;
7
 
8
 
8
 import com.facebook.react.bridge.ActivityEventListener;
9
 import com.facebook.react.bridge.ActivityEventListener;
9
 import com.facebook.react.bridge.Callback;
10
 import com.facebook.react.bridge.Callback;
34
 
35
 
35
 public class RNFetchBlob extends ReactContextBaseJavaModule {
36
 public class RNFetchBlob extends ReactContextBaseJavaModule {
36
 
37
 
37
-    // Cookies
38
-    private final ForwardingCookieHandler mCookieHandler;
39
-    private final CookieJarContainer mCookieJarContainer;
40
     private final OkHttpClient mClient;
38
     private final OkHttpClient mClient;
41
 
39
 
42
     static ReactApplicationContext RCTContext;
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
     static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
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
     public RNFetchBlob(ReactApplicationContext reactContext) {
48
     public RNFetchBlob(ReactApplicationContext reactContext) {
51
 
49
 
52
         super(reactContext);
50
         super(reactContext);
53
 
51
 
54
         mClient = OkHttpClientProvider.getOkHttpClient();
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
         mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
55
         mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
58
 
56
 
59
         RCTContext = reactContext;
57
         RCTContext = reactContext;
85
     }
83
     }
86
 
84
 
87
     @ReactMethod
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
         threadPool.execute(new Runnable() {
87
         threadPool.execute(new Runnable() {
90
             @Override
88
             @Override
91
             public void run() {
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
             };
132
             };
125
             RCTContext.addLifecycleEventListener(listener);
133
             RCTContext.addLifecycleEventListener(listener);
126
         } catch(Exception ex) {
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
     @ReactMethod
139
     @ReactMethod
143
     public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
140
     public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
144
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
141
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
150
     }
147
     }
151
 
148
 
152
     @ReactMethod
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
     @ReactMethod
154
     @ReactMethod
176
     }
173
     }
177
 
174
 
178
     @ReactMethod
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
     @ReactMethod
180
     @ReactMethod
355
 
352
 
356
     @ReactMethod
353
     @ReactMethod
357
     public void addCompleteDownload (ReadableMap config, Promise promise) {
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
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
356
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
360
         if(path == null) {
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
             return;
359
             return;
363
         }
360
         }
364
         try {
361
         try {
375
             promise.resolve(null);
372
             promise.resolve(null);
376
         }
373
         }
377
         catch(Exception ex) {
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
 package com.RNFetchBlob;
1
 package com.RNFetchBlob;
2
 
2
 
3
+import android.support.annotation.NonNull;
3
 import android.util.Base64;
4
 import android.util.Base64;
4
 
5
 
5
 import com.facebook.react.bridge.Arguments;
6
 import com.facebook.react.bridge.Arguments;
21
 import okhttp3.RequestBody;
22
 import okhttp3.RequestBody;
22
 import okio.BufferedSink;
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
     int reported = 0;
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
         this.mTaskId = taskId;
39
         this.mTaskId = taskId;
40
     }
40
     }
41
 
41
 
49
         return this;
49
         return this;
50
     }
50
     }
51
 
51
 
52
-    RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) {
52
+    RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) {
53
         this.requestType = type;
53
         this.requestType = type;
54
         return this;
54
         return this;
55
     }
55
     }
114
     }
114
     }
115
 
115
 
116
     @Override
116
     @Override
117
-    public void writeTo(BufferedSink sink) {
117
+    public void writeTo(@NonNull BufferedSink sink) {
118
         try {
118
         try {
119
             pipeStreamToSink(requestStream, sink);
119
             pipeStreamToSink(requestStream, sink);
120
         } catch(Exception ex) {
120
         } catch(Exception ex) {
186
         ArrayList<FormField> fields = countFormDataLength();
186
         ArrayList<FormField> fields = countFormDataLength();
187
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
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
             String data = field.data;
190
             String data = field.data;
192
             String name = field.name;
191
             String name = field.name;
193
             // skip invalid fields
192
             // skip invalid fields
258
      * @param sink      The request body buffer sink
257
      * @param sink      The request body buffer sink
259
      * @throws IOException
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
         int totalWritten = 0;
262
         int totalWritten = 0;
265
         int read;
263
         int read;
266
         while((read = stream.read(chunk, 0, 10240)) > 0) {
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
         stream.close();
269
         stream.close();
274
     }
270
     }
291
 
287
 
292
     /**
288
     /**
293
      * Compute approximate content length for form data
289
      * Compute approximate content length for form data
294
-     * @return
290
+     * @return ArrayList<FormField>
295
      */
291
      */
296
     private ArrayList<FormField> countFormDataLength() {
292
     private ArrayList<FormField> countFormDataLength() {
297
         long total = 0;
293
         long total = 0;
300
         for(int i = 0;i < form.size(); i++) {
296
         for(int i = 0;i < form.size(); i++) {
301
             FormField field = new FormField(form.getMap(i));
297
             FormField field = new FormField(form.getMap(i));
302
             list.add(field);
298
             list.add(field);
303
-            String data = field.data;
304
-            if(data == null) {
299
+            if(field.data == null) {
305
                 RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
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
             else if (field.filename != null) {
302
             else if (field.filename != null) {
303
+                String data = field.data;
308
                 // upload from storage
304
                 // upload from storage
309
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
305
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
310
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
306
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
333
             }
329
             }
334
             // data field
330
             // data field
335
             else {
331
             else {
336
-                total += field.data != null ? field.data.getBytes().length : 0;
332
+                total += field.data.getBytes().length;
337
             }
333
             }
338
         }
334
         }
339
         contentLength = total;
335
         contentLength = total;
346
      */
342
      */
347
     private class FormField {
343
     private class FormField {
348
         public String name;
344
         public String name;
349
-        public String filename;
350
-        public String mime;
345
+        String filename;
346
+        String mime;
351
         public String data;
347
         public String data;
352
 
348
 
353
-        public FormField(ReadableMap rawData) {
349
+        FormField(ReadableMap rawData) {
354
             if(rawData.hasKey("name"))
350
             if(rawData.hasKey("name"))
355
                 name = rawData.getString("name");
351
                 name = rawData.getString("name");
356
             if(rawData.hasKey("filename"))
352
             if(rawData.hasKey("filename"))
368
 
364
 
369
     /**
365
     /**
370
      * Emit progress event
366
      * Emit progress event
371
-     * @param written
367
+     * @param written  Integer
372
      */
368
      */
373
     private void emitUploadProgress(int written) {
369
     private void emitUploadProgress(int written) {
374
         RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);
370
         RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);

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

3
 import com.facebook.react.bridge.ReadableArray;
3
 import com.facebook.react.bridge.ReadableArray;
4
 import com.facebook.react.bridge.ReadableMap;
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
     public Boolean fileCache;
8
     public Boolean fileCache;
12
     public String path;
9
     public String path;

+ 294
- 187
android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

1
 package com.RNFetchBlob;
1
 package com.RNFetchBlob;
2
 
2
 
3
-import android.content.pm.PackageInfo;
4
-import android.content.pm.PackageManager;
5
 import android.content.res.AssetFileDescriptor;
3
 import android.content.res.AssetFileDescriptor;
6
 import android.media.MediaScannerConnection;
4
 import android.media.MediaScannerConnection;
7
 import android.net.Uri;
5
 import android.net.Uri;
22
 import com.facebook.react.bridge.WritableMap;
20
 import com.facebook.react.bridge.WritableMap;
23
 import com.facebook.react.modules.core.DeviceEventManagerModule;
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
 import java.nio.ByteBuffer;
24
 import java.nio.ByteBuffer;
32
 import java.nio.charset.Charset;
25
 import java.nio.charset.Charset;
33
 import java.nio.charset.CharsetEncoder;
26
 import java.nio.charset.CharsetEncoder;
34
 import java.security.MessageDigest;
27
 import java.security.MessageDigest;
28
+import java.util.ArrayList;
35
 import java.util.HashMap;
29
 import java.util.HashMap;
36
 import java.util.Map;
30
 import java.util.Map;
37
 import java.util.UUID;
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
     RNFetchBlobFS(ReactApplicationContext ctx) {
41
     RNFetchBlobFS(ReactApplicationContext ctx) {
49
         this.mCtx = ctx;
42
         this.mCtx = ctx;
50
         this.emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
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
      * Write string with encoding to file
47
      * Write string with encoding to file
64
      * @param path Destination file path.
48
      * @param path Destination file path.
66
      * @param data Array passed from JS context.
50
      * @param data Array passed from JS context.
67
      * @param promise RCT Promise
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
         try {
54
         try {
71
-            int written = 0;
55
+            int written;
72
             File f = new File(path);
56
             File f = new File(path);
73
             File dir = f.getParentFile();
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
             FileOutputStream fout = new FileOutputStream(f, append);
72
             FileOutputStream fout = new FileOutputStream(f, append);
77
             // write data from a file
73
             // write data from a file
78
             if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
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
                     fout.close();
79
                     fout.close();
84
-                    return ;
80
+                    return;
85
                 }
81
                 }
86
                 FileInputStream fin = new FileInputStream(src);
82
                 FileInputStream fin = new FileInputStream(src);
87
-                byte [] buffer = new byte [10240];
83
+                byte[] buffer = new byte [10240];
88
                 int read;
84
                 int read;
89
                 written = 0;
85
                 written = 0;
90
                 while((read = fin.read(buffer)) > 0) {
86
                 while((read = fin.read(buffer)) > 0) {
100
             }
96
             }
101
             fout.close();
97
             fout.close();
102
             promise.resolve(written);
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
         } catch (Exception e) {
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
      * @param data Array passed from JS context.
110
      * @param data Array passed from JS context.
112
      * @param promise RCT Promise
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
         try {
114
         try {
117
-
118
             File f = new File(path);
115
             File f = new File(path);
119
             File dir = f.getParentFile();
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
             FileOutputStream os = new FileOutputStream(f, append);
131
             FileOutputStream os = new FileOutputStream(f, append);
123
-            byte [] bytes = new byte[data.size()];
132
+            byte[] bytes = new byte[data.size()];
124
             for(int i=0;i<data.size();i++) {
133
             for(int i=0;i<data.size();i++) {
125
                 bytes[i] = (byte) data.getInt(i);
134
                 bytes[i] = (byte) data.getInt(i);
126
             }
135
             }
127
             os.write(bytes);
136
             os.write(bytes);
128
             os.close();
137
             os.close();
129
             promise.resolve(data.size());
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
         } catch (Exception e) {
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
      * Read file with a buffer that has the same size as the target file.
148
      * Read file with a buffer that has the same size as the target file.
137
      * @param path  Path of the file.
149
      * @param path  Path of the file.
138
      * @param encoding  Encoding of read stream.
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
         String resolved = normalizePath(path);
154
         String resolved = normalizePath(path);
143
         if(resolved != null)
155
         if(resolved != null)
144
             path = resolved;
156
             path = resolved;
145
         try {
157
         try {
146
             byte[] bytes;
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
             if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
162
             if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
149
                 String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
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
                 InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
167
                 InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
153
-                in.read(bytes, 0, (int) length);
168
+                bytesRead = in.read(bytes, 0, length);
154
                 in.close();
169
                 in.close();
155
             }
170
             }
156
             // issue 287
171
             // issue 287
157
             else if(resolved == null) {
172
             else if(resolved == null) {
158
                 InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
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
                 bytes = new byte[length];
179
                 bytes = new byte[length];
161
-                in.read(bytes);
180
+                bytesRead = in.read(bytes);
162
                 in.close();
181
                 in.close();
163
             }
182
             }
164
             else {
183
             else {
165
                 File f = new File(path);
184
                 File f = new File(path);
166
-                int length = (int) f.length();
185
+                length = (int) f.length();
167
                 bytes = new byte[length];
186
                 bytes = new byte[length];
168
                 FileInputStream in = new FileInputStream(f);
187
                 FileInputStream in = new FileInputStream(f);
169
-                in.read(bytes);
188
+                bytesRead = in.read(bytes);
170
                 in.close();
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
             switch (encoding.toLowerCase()) {
197
             switch (encoding.toLowerCase()) {
174
                 case "base64" :
198
                 case "base64" :
175
                     promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
199
                     promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
176
                     break;
200
                     break;
177
                 case "ascii" :
201
                 case "ascii" :
178
                     WritableArray asciiResult = Arguments.createArray();
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
                     promise.resolve(asciiResult);
206
                     promise.resolve(asciiResult);
183
                     break;
207
                     break;
189
                     break;
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
         catch(Exception err) {
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
      * Static method that returns system folders to JS context
231
      * Static method that returns system folders to JS context
200
      * @param ctx   React Native application context
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
         Map<String, Object> res = new HashMap<>();
235
         Map<String, Object> res = new HashMap<>();
204
 
236
 
205
         res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
237
         res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
222
 
254
 
223
     /**
255
     /**
224
      * Static method that returns a temp file path
256
      * Static method that returns a temp file path
225
-     * @param ctx   React Native application context
226
      * @param taskId    An unique string for identify
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
         return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId;
261
         return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId;
231
     }
262
     }
232
 
263
 
236
      * @param encoding  File stream decoder, should be one of `base64`, `utf8`, `ascii`
267
      * @param encoding  File stream decoder, should be one of `base64`, `utf8`, `ascii`
237
      * @param bufferSize    Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
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
         String resolved = normalizePath(path);
271
         String resolved = normalizePath(path);
241
         if(resolved != null)
272
         if(resolved != null)
242
             path = resolved;
273
             path = resolved;
243
-        try {
244
 
274
 
275
+        try {
245
             int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
276
             int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
246
             if(bufferSize > 0)
277
             if(bufferSize > 0)
247
                 chunkSize = bufferSize;
278
                 chunkSize = bufferSize;
250
 
281
 
251
             if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
282
             if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
252
                 fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
283
                 fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
253
-
254
             }
284
             }
255
             // fix issue 287
285
             // fix issue 287
256
             else if(resolved == null) {
286
             else if(resolved == null) {
287
             } else if (encoding.equalsIgnoreCase("base64")) {
317
             } else if (encoding.equalsIgnoreCase("base64")) {
288
                 while ((cursor = fs.read(buffer)) != -1) {
318
                 while ((cursor = fs.read(buffer)) != -1) {
289
                     if(cursor < chunkSize) {
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
                         emitStreamEvent(streamId, "data", Base64.encodeToString(copy, Base64.NO_WRAP));
322
                         emitStreamEvent(streamId, "data", Base64.encodeToString(copy, Base64.NO_WRAP));
295
                     }
323
                     }
296
                     else
324
                     else
299
                         SystemClock.sleep(tick);
327
                         SystemClock.sleep(tick);
300
                 }
328
                 }
301
             } else {
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
                 error = true;
336
                 error = true;
305
             }
337
             }
306
 
338
 
308
                 emitStreamEvent(streamId, "end", "");
340
                 emitStreamEvent(streamId, "end", "");
309
             fs.close();
341
             fs.close();
310
             buffer = null;
342
             buffer = null;
311
-
343
+        } catch (FileNotFoundException err) {
344
+            emitStreamEvent(
345
+                    streamId,
346
+                    "error",
347
+                    "ENOENT",
348
+                    "No such file '" + path + "'"
349
+            );
312
         } catch (Exception err) {
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
             err.printStackTrace();
357
             err.printStackTrace();
316
         }
358
         }
317
     }
359
     }
319
     /**
361
     /**
320
      * Create a write stream and store its instance in RNFetchBlobFS.fileStreams
362
      * Create a write stream and store its instance in RNFetchBlobFS.fileStreams
321
      * @param path  Target file path
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
         try {
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
             OutputStream fs = new FileOutputStream(path, append);
389
             OutputStream fs = new FileOutputStream(path, append);
334
             this.encoding = encoding;
390
             this.encoding = encoding;
335
-            this.append = append;
336
             String streamId = UUID.randomUUID().toString();
391
             String streamId = UUID.randomUUID().toString();
337
             RNFetchBlobFS.fileStreams.put(streamId, this);
392
             RNFetchBlobFS.fileStreams.put(streamId, this);
338
             this.writeStreamInstance = fs;
393
             this.writeStreamInstance = fs;
339
-            callback.invoke(null, streamId);
394
+            callback.invoke(null, null, streamId);
340
         } catch(Exception err) {
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
      * @param callback JS context callback
404
      * @param callback JS context callback
351
      */
405
      */
352
     static void writeChunk(String streamId, String data, Callback callback) {
406
     static void writeChunk(String streamId, String data, Callback callback) {
353
-
354
         RNFetchBlobFS fs = fileStreams.get(streamId);
407
         RNFetchBlobFS fs = fileStreams.get(streamId);
355
         OutputStream stream = fs.writeStreamInstance;
408
         OutputStream stream = fs.writeStreamInstance;
356
-        byte [] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
357
-
409
+        byte[] chunk = RNFetchBlobFS.stringToBytes(data, fs.encoding);
358
         try {
410
         try {
359
             stream.write(chunk);
411
             stream.write(chunk);
360
             callback.invoke();
412
             callback.invoke();
370
      * @param callback JS context callback
422
      * @param callback JS context callback
371
      */
423
      */
372
     static void writeArrayChunk(String streamId, ReadableArray data, Callback callback) {
424
     static void writeArrayChunk(String streamId, ReadableArray data, Callback callback) {
373
-
374
         try {
425
         try {
375
             RNFetchBlobFS fs = fileStreams.get(streamId);
426
             RNFetchBlobFS fs = fileStreams.get(streamId);
376
             OutputStream stream = fs.writeStreamInstance;
427
             OutputStream stream = fs.writeStreamInstance;
377
-            byte [] chunk = new byte[data.size()];
428
+            byte[] chunk = new byte[data.size()];
378
             for(int i =0; i< data.size();i++) {
429
             for(int i =0; i< data.size();i++) {
379
                 chunk[i] = (byte) data.getInt(i);
430
                 chunk[i] = (byte) data.getInt(i);
380
             }
431
             }
412
             RNFetchBlobFS.deleteRecursive(new File(path));
463
             RNFetchBlobFS.deleteRecursive(new File(path));
413
             callback.invoke(null, true);
464
             callback.invoke(null, true);
414
         } catch(Exception err) {
465
         } catch(Exception err) {
415
-            if(err != null)
416
             callback.invoke(err.getLocalizedMessage(), false);
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
         if (fileOrDirectory.isDirectory()) {
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
      * Make a folder
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
         File dest = new File(path);
493
         File dest = new File(path);
437
         if(dest.exists()) {
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
             return;
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
      * @param callback  JS context callback
515
      * @param callback  JS context callback
450
      */
516
      */
451
     static void cp(String path, String dest, Callback callback) {
517
     static void cp(String path, String dest, Callback callback) {
452
-
453
         path = normalizePath(path);
518
         path = normalizePath(path);
454
         InputStream in = null;
519
         InputStream in = null;
455
         OutputStream out = null;
520
         OutputStream out = null;
456
 
521
 
457
         try {
522
         try {
458
-
459
             if(!isPathExists(path)) {
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
                 return;
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
             in = inputStreamFromPath(path);
535
             in = inputStreamFromPath(path);
467
             out = new FileOutputStream(dest);
536
             out = new FileOutputStream(dest);
471
             while ((len = in.read(buf)) > 0) {
540
             while ((len = in.read(buf)) > 0) {
472
                 out.write(buf, 0, len);
541
                 out.write(buf, 0, len);
473
             }
542
             }
474
-
475
         } catch (Exception err) {
543
         } catch (Exception err) {
476
             callback.invoke(err.getLocalizedMessage());
544
             callback.invoke(err.getLocalizedMessage());
477
         } finally {
545
         } finally {
498
     static void mv(String path, String dest, Callback callback) {
566
     static void mv(String path, String dest, Callback callback) {
499
         File src = new File(path);
567
         File src = new File(path);
500
         if(!src.exists()) {
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
             return;
580
             return;
503
         }
581
         }
504
-        src.renameTo(new File(dest));
505
         callback.invoke();
582
         callback.invoke();
506
     }
583
     }
507
 
584
 
511
      * @param callback  JS context callback
588
      * @param callback  JS context callback
512
      */
589
      */
513
     static void exists(String path, Callback callback) {
590
     static void exists(String path, Callback callback) {
514
-
515
         if(isAsset(path)) {
591
         if(isAsset(path)) {
516
             try {
592
             try {
517
                 String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
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
                 callback.invoke(true, false);
595
                 callback.invoke(true, false);
520
             } catch (IOException e) {
596
             } catch (IOException e) {
521
                 callback.invoke(false, false);
597
                 callback.invoke(false, false);
534
      * @param path Target folder
610
      * @param path Target folder
535
      * @param callback  JS context callback
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
      * Create a file by slicing given file path
640
      * Create a file by slicing given file path
554
-     * @param src   Source file path
641
+     * @param path   Source file path
555
      * @param dest  Destination of created file
642
      * @param dest  Destination of created file
556
      * @param start Start byte offset in source file
643
      * @param start Start byte offset in source file
557
      * @param end   End byte offset
644
      * @param end   End byte offset
558
      * @param encode NOT IMPLEMENTED
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
         try {
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
                 return;
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
             FileOutputStream out = new FileOutputStream(new File(dest));
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
             while(now < expected) {
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
                 if(read <= 0) {
674
                 if(read <= 0) {
580
                     break;
675
                     break;
581
                 }
676
                 }
588
             promise.resolve(dest);
683
             promise.resolve(dest);
589
         } catch (Exception e) {
684
         } catch (Exception e) {
590
             e.printStackTrace();
685
             e.printStackTrace();
591
-            promise.reject("RNFetchBlob slice error", e.getLocalizedMessage());
686
+            promise.reject("EUNSPECIFIED", e.getLocalizedMessage());
592
         }
687
         }
593
     }
688
     }
594
 
689
 
610
                 }
705
                 }
611
                 if(src.isDirectory()) {
706
                 if(src.isDirectory()) {
612
                     String [] files = src.list();
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
                     for(String p : files) {
710
                     for(String p : files) {
614
                         res.pushMap(statFile(src.getPath() + "/" + p));
711
                         res.pushMap(statFile(src.getPath() + "/" + p));
615
                     }
712
                     }
625
 
722
 
626
     /**
723
     /**
627
      * show status of a file or directory
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
     static void stat(String path, Callback callback) {
728
     static void stat(String path, Callback callback) {
632
         try {
729
         try {
643
 
740
 
644
     /**
741
     /**
645
      * Basic stat method
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
     static WritableMap statFile(String path) {
746
     static WritableMap statFile(String path) {
650
         try {
747
         try {
680
 
777
 
681
     /**
778
     /**
682
      * Media scanner scan file
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
     void scanFile(String [] path, String[] mimes, final Callback callback) {
784
     void scanFile(String [] path, String[] mimes, final Callback callback) {
688
         try {
785
         try {
708
             algorithms.put("sha384", "SHA-384");
805
             algorithms.put("sha384", "SHA-384");
709
             algorithms.put("sha512", "SHA-512");
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
             File file = new File(path);
813
             File file = new File(path);
714
 
814
 
715
             if (file.isDirectory()) {
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
                 return;
817
                 return;
718
             }
818
             }
719
 
819
 
720
             if (!file.exists()) {
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
                 return;
822
                 return;
723
             }
823
             }
724
 
824
 
737
                 hexString.append(String.format("%02x", digestByte));
837
                 hexString.append(String.format("%02x", digestByte));
738
 
838
 
739
             promise.resolve(hexString.toString());
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
      * @param path The destination path of the new file.
848
      * @param path The destination path of the new file.
749
      * @param data Initial data of the new file.
849
      * @param data Initial data of the new file.
750
      * @param encoding Encoding of initial data.
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
         try {
854
         try {
755
             File dest = new File(path);
855
             File dest = new File(path);
756
             boolean created = dest.createNewFile();
856
             boolean created = dest.createNewFile();
758
                 String orgPath = data.replace(RNFetchBlobConst.FILE_PREFIX, "");
858
                 String orgPath = data.replace(RNFetchBlobConst.FILE_PREFIX, "");
759
                 File src = new File(orgPath);
859
                 File src = new File(orgPath);
760
                 if(!src.exists()) {
860
                 if(!src.exists()) {
761
-                    callback.invoke("source file : " + data + " does not exist");
861
+                    promise.reject("ENOENT", "Source file : " + data + " does not exist");
762
                     return ;
862
                     return ;
763
                 }
863
                 }
764
                 FileInputStream fin = new FileInputStream(src);
864
                 FileInputStream fin = new FileInputStream(src);
773
                 ostream.close();
873
                 ostream.close();
774
             } else {
874
             } else {
775
                 if (!created) {
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
                     return;
877
                     return;
780
                 }
878
                 }
781
                 OutputStream ostream = new FileOutputStream(dest);
879
                 OutputStream ostream = new FileOutputStream(dest);
782
                 ostream.write(RNFetchBlobFS.stringToBytes(data, encoding));
880
                 ostream.write(RNFetchBlobFS.stringToBytes(data, encoding));
783
             }
881
             }
784
-            callback.invoke(null, path);
882
+            promise.resolve(path);
785
         } catch(Exception err) {
883
         } catch(Exception err) {
786
-            callback.invoke(err.getLocalizedMessage());
884
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
787
         }
885
         }
788
     }
886
     }
789
 
887
 
791
      * Create file for ASCII encoding
889
      * Create file for ASCII encoding
792
      * @param path  Path of new file.
890
      * @param path  Path of new file.
793
      * @param data  Content of new file
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
         try {
895
         try {
798
             File dest = new File(path);
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
             boolean created = dest.createNewFile();
897
             boolean created = dest.createNewFile();
804
             if(!created) {
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
                 return;
900
                 return;
807
             }
901
             }
808
             OutputStream ostream = new FileOutputStream(dest);
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
                 chunk[i] = (byte) data.getInt(i);
905
                 chunk[i] = (byte) data.getInt(i);
812
             }
906
             }
813
             ostream.write(chunk);
907
             ostream.write(chunk);
814
-            chunk = null;
815
-            callback.invoke(null, path);
908
+            promise.resolve(path);
816
         } catch(Exception err) {
909
         } catch(Exception err) {
817
-            callback.invoke(err.getLocalizedMessage());
910
+            promise.reject("EUNSPECIFIED", err.getLocalizedMessage());
818
         }
911
         }
819
     }
912
     }
820
 
913
 
838
      * @param callback JS contest callback
931
      * @param callback JS contest callback
839
      */
932
      */
840
     static void removeSession(ReadableArray paths, final Callback callback) {
933
     static void removeSession(ReadableArray paths, final Callback callback) {
841
-
842
         AsyncTask<ReadableArray, Integer, Integer> task = new AsyncTask<ReadableArray, Integer, Integer>() {
934
         AsyncTask<ReadableArray, Integer, Integer> task = new AsyncTask<ReadableArray, Integer, Integer>() {
843
             @Override
935
             @Override
844
             protected Integer doInBackground(ReadableArray ...paths) {
936
             protected Integer doInBackground(ReadableArray ...paths) {
845
                 try {
937
                 try {
938
+                    ArrayList<String> failuresToDelete = new ArrayList<>();
846
                     for (int i = 0; i < paths[0].size(); i++) {
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
                 } catch(Exception err) {
959
                 } catch(Exception err) {
853
                     callback.invoke(err.getLocalizedMessage());
960
                     callback.invoke(err.getLocalizedMessage());
854
                 }
961
                 }
891
         this.emitter.emit(streamName, eventData);
998
         this.emitter.emit(streamName, eventData);
892
     }
999
     }
893
 
1000
 
1001
+    // "event" always is "data"...
894
     private void emitStreamEvent(String streamName, String event, WritableArray data) {
1002
     private void emitStreamEvent(String streamName, String event, WritableArray data) {
895
         WritableMap eventData = Arguments.createMap();
1003
         WritableMap eventData = Arguments.createMap();
896
         eventData.putString("event", event);
1004
         eventData.putString("event", event);
898
         this.emitter.emit(streamName, eventData);
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
         WritableMap eventData = Arguments.createMap();
1011
         WritableMap eventData = Arguments.createMap();
904
         eventData.putString("event", event);
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
      * the stream is created by Assets Manager, otherwise use FileInputStream.
1020
      * the stream is created by Assets Manager, otherwise use FileInputStream.
912
      * @param path The file to open stream
1021
      * @param path The file to open stream
913
      * @return InputStream instance
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
         if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
1026
         if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
918
             return RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
1027
             return RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
919
         }
1028
         }
925
      * @param path A file path URI string
1034
      * @param path A file path URI string
926
      * @return A boolean value represents if the path exists.
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
         if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
1038
         if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
930
             try {
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
             } catch (IOException e) {
1041
             } catch (IOException e) {
933
                 return false;
1042
                 return false;
934
             }
1043
             }
941
     }
1050
     }
942
 
1051
 
943
     static boolean isAsset(String path) {
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
  */
5
  */
6
 public class RNFetchBlobProgressConfig {
6
 public class RNFetchBlobProgressConfig {
7
 
7
 
8
-    public enum ReportType {
8
+    enum ReportType {
9
         Upload,
9
         Upload,
10
         Download
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
     RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
20
     RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
21
         this.enable = report;
21
         this.enable = report;

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

8
 import android.database.Cursor;
8
 import android.database.Cursor;
9
 import android.net.Uri;
9
 import android.net.Uri;
10
 import android.os.Build;
10
 import android.os.Build;
11
+import android.support.annotation.NonNull;
11
 import android.util.Base64;
12
 import android.util.Base64;
12
 
13
 
13
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
14
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
22
 import com.facebook.react.bridge.WritableArray;
23
 import com.facebook.react.bridge.WritableArray;
23
 import com.facebook.react.bridge.WritableMap;
24
 import com.facebook.react.bridge.WritableMap;
24
 import com.facebook.react.modules.core.DeviceEventManagerModule;
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
 import java.io.File;
32
 import java.io.File;
29
 import java.io.FileOutputStream;
33
 import java.io.FileOutputStream;
37
 import java.nio.charset.CharacterCodingException;
41
 import java.nio.charset.CharacterCodingException;
38
 import java.nio.charset.Charset;
42
 import java.nio.charset.Charset;
39
 import java.nio.charset.CharsetEncoder;
43
 import java.nio.charset.CharsetEncoder;
44
+import java.security.KeyStore;
40
 import java.util.ArrayList;
45
 import java.util.ArrayList;
46
+import java.util.Arrays;
41
 import java.util.List;
47
 import java.util.List;
42
 import java.util.HashMap;
48
 import java.util.HashMap;
43
 
49
 
44
 import java.util.concurrent.TimeUnit;
50
 import java.util.concurrent.TimeUnit;
45
 
51
 
52
+import 	javax.net.ssl.SSLSocketFactory;
53
+
46
 import okhttp3.Call;
54
 import okhttp3.Call;
47
 import okhttp3.ConnectionPool;
55
 import okhttp3.ConnectionPool;
48
 import okhttp3.ConnectionSpec;
56
 import okhttp3.ConnectionSpec;
84
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
92
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
85
     static ConnectionPool pool = new ConnectionPool();
93
     static ConnectionPool pool = new ConnectionPool();
86
 
94
 
87
-    ReactApplicationContext ctx;
88
     RNFetchBlobConfig options;
95
     RNFetchBlobConfig options;
89
     String taskId;
96
     String taskId;
90
     String method;
97
     String method;
170
                 }
177
                 }
171
                 // set headers
178
                 // set headers
172
                 ReadableMapKeySetIterator it = headers.keySetIterator();
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
                     req.allowScanningByMediaScanner();
181
                     req.allowScanningByMediaScanner();
175
                 }
182
                 }
176
                 while (it.hasNextKey()) {
183
                 while (it.hasNextKey()) {
197
                 cacheKey = this.taskId;
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
             if (file.exists()) {
209
             if (file.exists()) {
203
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
210
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
208
         if(this.options.path != null)
215
         if(this.options.path != null)
209
             this.destPath = this.options.path;
216
             this.destPath = this.options.path;
210
         else if(this.options.fileCache)
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
         OkHttpClient.Builder clientBuilder;
221
         OkHttpClient.Builder clientBuilder;
330
             // Add request interceptor for upload progress event
337
             // Add request interceptor for upload progress event
331
             clientBuilder.addInterceptor(new Interceptor() {
338
             clientBuilder.addInterceptor(new Interceptor() {
332
                 @Override
339
                 @Override
333
-                public Response intercept(Chain chain) throws IOException {
340
+                public Response intercept(@NonNull Chain chain) throws IOException {
334
                     try {
341
                     try {
335
                         Response originalResponse = chain.proceed(req);
342
                         Response originalResponse = chain.proceed(req);
336
                         ResponseBody extended;
343
                         ResponseBody extended;
392
             call.enqueue(new okhttp3.Callback() {
399
             call.enqueue(new okhttp3.Callback() {
393
 
400
 
394
                 @Override
401
                 @Override
395
-                public void onFailure(Call call, IOException e) {
402
+                public void onFailure(@NonNull Call call, IOException e) {
396
                     cancelTask(taskId);
403
                     cancelTask(taskId);
397
                     if(respInfo == null) {
404
                     if(respInfo == null) {
398
                         respInfo = Arguments.createMap();
405
                         respInfo = Arguments.createMap();
409
                 }
416
                 }
410
 
417
 
411
                 @Override
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
                     ReadableMap notifyConfig = options.addAndroidDownloads;
420
                     ReadableMap notifyConfig = options.addAndroidDownloads;
414
                     // Download manager settings
421
                     // Download manager settings
415
                     if(notifyConfig != null ) {
422
                     if(notifyConfig != null ) {
470
                     // For XMLHttpRequest, automatic response data storing strategy, when response
477
                     // For XMLHttpRequest, automatic response data storing strategy, when response
471
                     // data is considered as binary data, write it to file system
478
                     // data is considered as binary data, write it to file system
472
                     if(isBlobResp && options.auto) {
479
                     if(isBlobResp && options.auto) {
473
-                        String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
480
+                        String dest = RNFetchBlobFS.getTmpPath(taskId);
474
                         InputStream ins = resp.body().byteStream();
481
                         InputStream ins = resp.body().byteStream();
475
                         FileOutputStream os = new FileOutputStream(new File(dest));
482
                         FileOutputStream os = new FileOutputStream(new File(dest));
476
                         int read;
483
                         int read;
477
-                        byte [] buffer = new byte[10240];
484
+                        byte[] buffer = new byte[10240];
478
                         while ((read = ins.read(buffer)) != -1) {
485
                         while ((read = ins.read(buffer)) != -1) {
479
                             os.write(buffer, 0, read);
486
                             os.write(buffer, 0, read);
480
                         }
487
                         }
670
                             options.addAndroidDownloads.getString("mime").contains("image")) {
677
                             options.addAndroidDownloads.getString("mime").contains("image")) {
671
                         Uri uri = Uri.parse(contentUri);
678
                         Uri uri = Uri.parse(contentUri);
672
                         Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
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
                         if (cursor != null) {
681
                         if (cursor != null) {
676
                             cursor.moveToFirst();
682
                             cursor.moveToFirst();
677
                             filePath = cursor.getString(0);
683
                             filePath = cursor.getString(0);
684
+                            cursor.close();
678
                         }
685
                         }
679
                     }
686
                     }
680
                 }
687
                 }
708
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
715
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
709
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
716
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
710
             try {
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
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
732
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
714
                         .tlsVersions(TlsVersion.TLS_1_2)
733
                         .tlsVersions(TlsVersion.TLS_1_2)

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

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

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

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

+ 5
- 3
class/RNFetchBlobReadStream.js View File

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

+ 171
- 156
fs.js View File

2
 // Use of this source code is governed by a MIT-style license that can be
2
 // Use of this source code is governed by a MIT-style license that can be
3
 // found in the LICENSE file.
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
 import RNFetchBlobSession from './class/RNFetchBlobSession'
8
 import RNFetchBlobSession from './class/RNFetchBlobSession'
12
 import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream'
9
 import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream'
13
 import RNFetchBlobReadStream from './class/RNFetchBlobReadStream'
10
 import RNFetchBlobReadStream from './class/RNFetchBlobReadStream'
14
 import RNFetchBlobFile from './class/RNFetchBlobFile'
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
 const dirs = {
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
  * @param  {string} name Stream ID
36
  * @param  {string} name Stream ID
40
  * @return {RNFetchBlobSession}
37
  * @return {RNFetchBlobSession}
41
  */
38
  */
42
-function session(name:string):RNFetchBlobSession {
39
+function session(name: string): RNFetchBlobSession {
43
   let s = RNFetchBlobSession.getSession(name)
40
   let s = RNFetchBlobSession.getSession(name)
44
-  if(s)
41
+  if (s)
45
     return new RNFetchBlobSession(name)
42
     return new RNFetchBlobSession(name)
46
   else {
43
   else {
47
     RNFetchBlobSession.setSession(name, [])
44
     RNFetchBlobSession.setSession(name, [])
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
     // path from camera roll
51
     // path from camera roll
55
-    if(/^assets-library\:\/\//.test(path))
52
+    if (/^assets-library\:\/\//.test(path))
56
       return path
53
       return path
57
   }
54
   }
58
   return 'bundle-assets://' + path
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
  * @return {Promise<RNFetchBlobWriteStream>} A promise resolves a `WriteStream` object.
74
  * @return {Promise<RNFetchBlobWriteStream>} A promise resolves a `WriteStream` object.
88
  */
75
  */
89
 function writeStream(
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
   return new Promise((resolve, reject) => {
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
       else
91
       else
103
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
92
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
104
     })
93
     })
114
  * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
103
  * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
115
  */
104
  */
116
 function readStream(
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
   return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
114
   return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
123
 }
115
 }
124
 
116
 
127
  * @param  {string} path Path of directory to be created
119
  * @param  {string} path Path of directory to be created
128
  * @return {Promise}
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
  * @param  {string} groupName Name of app group
131
  * @param  {string} groupName Name of app group
146
  * @return {Promise}
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
  * @param  {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream.
141
  * @param  {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream.
156
  * @return {Promise<Array<number> | string>}
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
   return RNFetchBlob.readFile(path, encoding)
148
   return RNFetchBlob.readFile(path, encoding)
162
 }
149
 }
163
 
150
 
168
  * @param  {string} encoding Encoding of data (Optional).
155
  * @param  {string} encoding Encoding of data (Optional).
169
  * @return {Promise}
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
     else
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
     else
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
     else
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
     else
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
  * @param  {string} path Target path
200
  * @param  {string} path Target path
208
  * @return {RNFetchBlobFile}
201
  * @return {RNFetchBlobFile}
209
  */
202
  */
210
-function stat(path:string):Promise<RNFetchBlobFile> {
203
+function stat(path: string): Promise<RNFetchBlobFile> {
211
   return new Promise((resolve, reject) => {
204
   return new Promise((resolve, reject) => {
205
+    if (typeof path !== 'string') {
206
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
207
+    }
212
     RNFetchBlob.stat(path, (err, stat) => {
208
     RNFetchBlob.stat(path, (err, stat) => {
213
-      if(err)
209
+      if (err)
214
         reject(new Error(err))
210
         reject(new Error(err))
215
       else {
211
       else {
216
-        if(stat) {
212
+        if (stat) {
217
           stat.size = parseInt(stat.size)
213
           stat.size = parseInt(stat.size)
218
           stat.lastModified = parseInt(stat.lastModified)
214
           stat.lastModified = parseInt(stat.lastModified)
219
         }
215
         }
228
  * @param  {Array<Object<string, string>>} pairs Array contains Key value pairs with key `path` and `mime`.
224
  * @param  {Array<Object<string, string>>} pairs Array contains Key value pairs with key `path` and `mime`.
229
  * @return {Promise}
225
  * @return {Promise}
230
  */
226
  */
231
-function scanFile(pairs:any):Promise {
227
+function scanFile(pairs: any): Promise {
232
   return new Promise((resolve, reject) => {
228
   return new Promise((resolve, reject) => {
229
+    if (pairs === undefined) {
230
+      return reject(addCode('EINVAL', new TypeError('Missing argument')))
231
+    }
233
     RNFetchBlob.scanFile(pairs, (err) => {
232
     RNFetchBlob.scanFile(pairs, (err) => {
234
-      if(err)
235
-        reject(new Error(err))
233
+      if (err)
234
+        reject(addCode('EUNSPECIFIED', new Error(err)))
236
       else
235
       else
237
         resolve()
236
         resolve()
238
     })
237
     })
240
 }
239
 }
241
 
240
 
242
 function hash(path: string, algorithm: string): Promise<string> {
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
   return RNFetchBlob.hash(path, algorithm)
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
   return new Promise((resolve, reject) => {
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
     RNFetchBlob.cp(path, dest, (err, res) => {
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
       else
256
       else
257
         resolve(res)
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
   return new Promise((resolve, reject) => {
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
     RNFetchBlob.mv(path, dest, (err, res) => {
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
       else
270
       else
268
         resolve(res)
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
   return new Promise((resolve, reject) => {
277
   return new Promise((resolve, reject) => {
278
+    if (typeof path !== 'string') {
279
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
280
+    }
275
     RNFetchBlob.lstat(path, (err, stat) => {
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
       else
284
       else
279
         resolve(stat)
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
  * @param  {string}   path:string Path of target file.
299
  * @param  {string}   path:string Path of target file.
298
  * @return {Promise}
300
  * @return {Promise}
299
  */
301
  */
300
-function unlink(path:string):Promise {
302
+function unlink(path: string): Promise {
301
   return new Promise((resolve, reject) => {
303
   return new Promise((resolve, reject) => {
304
+    if (typeof path !== 'string') {
305
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
306
+    }
302
     RNFetchBlob.unlink(path, (err) => {
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
       else
311
       else
307
         resolve()
312
         resolve()
312
 /**
317
 /**
313
  * Check if file exists and if it is a folder.
318
  * Check if file exists and if it is a folder.
314
  * @param  {string} path Path to check
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
   return new Promise((resolve, reject) => {
323
   return new Promise((resolve, reject) => {
324
+    if (typeof path !== 'string') {
325
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
326
+    }
319
     try {
327
     try {
320
       RNFetchBlob.exists(path, (exist) => {
328
       RNFetchBlob.exists(path, (exist) => {
321
         resolve(exist)
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
   let p = Promise.resolve()
343
   let p = Promise.resolve()
332
   let size = 0
344
   let size = 0
345
+
333
   function normalize(num, size) {
346
   function normalize(num, size) {
334
-    if(num < 0)
347
+    if (num < 0)
335
       return Math.max(0, size + num)
348
       return Math.max(0, size + num)
336
-    if(!num && num !== 0)
349
+    if (!num && num !== 0)
337
       return size
350
       return size
338
     return num
351
     return num
339
   }
352
   }
340
-  if(start < 0 || end < 0 || !start || !end) {
353
+
354
+  if (start < 0 || end < 0 || !start || !end) {
341
     p = p.then(() => stat(src))
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
   return p.then(() => RNFetchBlob.slice(src, dest, start, end))
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
   return new Promise((resolve, reject) => {
366
   return new Promise((resolve, reject) => {
367
+    if (typeof path !== 'string') {
368
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
369
+    }
355
     try {
370
     try {
356
       RNFetchBlob.exists(path, (exist, isDir) => {
371
       RNFetchBlob.exists(path, (exist, isDir) => {
357
         resolve(isDir)
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
   return new Promise((resolve, reject) => {
382
   return new Promise((resolve, reject) => {
368
     RNFetchBlob.df((err, stat) => {
383
     RNFetchBlob.df((err, stat) => {
369
-      if(err)
370
-        reject(new Error(err))
384
+      if (err)
385
+        reject(addCode('EUNSPECIFIED', new Error(err)))
371
       else
386
       else
372
         resolve(stat)
387
         resolve(stat)
373
     })
388
     })

+ 0
- 1
index.js View File

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

+ 1
- 1
ios.js View File

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

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

135
 }
135
 }
136
 
136
 
137
 #pragma mark - fs.createFile
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
     NSFileManager * fm = [NSFileManager defaultManager];
144
     NSFileManager * fm = [NSFileManager defaultManager];
141
     NSData * fileContent = nil;
145
     NSData * fileContent = nil;
142
 
146
 
154
         fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
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
 #pragma mark - fs.createFileASCII
173
 #pragma mark - fs.createFileASCII
166
 // method for create file with ASCII content
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
     NSFileManager * fm = [NSFileManager defaultManager];
180
     NSFileManager * fm = [NSFileManager defaultManager];
170
     NSMutableData * fileContent = [NSMutableData alloc];
181
     NSMutableData * fileContent = [NSMutableData alloc];
171
     // prevent stack overflow, alloc on heap
182
     // prevent stack overflow, alloc on heap
174
     for(int i = 0; i < dataArray.count; i++) {
185
     for(int i = 0; i < dataArray.count; i++) {
175
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
186
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
176
     }
187
     }
188
+
177
     [fileContent appendBytes:bytes length:dataArray.count];
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
 #pragma mark - fs.pathForAppGroup
205
 #pragma mark - fs.pathForAppGroup
194
     if(path) {
212
     if(path) {
195
         resolve(path);
213
         resolve(path);
196
     } else {
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
 {
238
 {
221
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
239
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
222
     NSFileManager * fm = [NSFileManager defaultManager];
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
     NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append];
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
 #pragma mark - fs.writeArrayChunk
263
 #pragma mark - fs.writeArrayChunk
291
 }
321
 }
292
 
322
 
293
 #pragma mark - fs.ls
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
     NSFileManager* fm = [NSFileManager defaultManager];
326
     NSFileManager* fm = [NSFileManager defaultManager];
297
     BOOL exist = nil;
327
     BOOL exist = nil;
298
     BOOL isDir = nil;
328
     BOOL isDir = nil;
299
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
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
     NSError * error = nil;
336
     NSError * error = nil;
305
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
337
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
306
 
338
 
307
     if(error == nil)
339
     if(error == nil)
308
-        callback(@[[NSNull null], result == nil ? [NSNull null] :result ]);
340
+        resolve(result);
309
     else
341
     else
310
-        callback(@[[error localizedDescription], [NSNull null]]);
311
-
342
+        reject(@"EUNSPECIFIED", [error description], nil);
312
 }
343
 }
313
 
344
 
314
 #pragma mark - fs.stat
345
 #pragma mark - fs.stat
389
 #pragma mark - fs.cp
420
 #pragma mark - fs.cp
390
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
421
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
391
 {
422
 {
392
-
393
 //    path = [RNFetchBlobFS getPathOfAsset:path];
423
 //    path = [RNFetchBlobFS getPathOfAsset:path];
394
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
424
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
395
         NSError * error = nil;
425
         NSError * error = nil;
400
         }
430
         }
401
         else
431
         else
402
         {
432
         {
433
+            // If the destination exists there will be an error
403
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
434
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
404
 
435
 
405
             if(error == nil)
436
             if(error == nil)
408
                 callback(@[[error localizedDescription], @NO]);
439
                 callback(@[[error localizedDescription], @NO]);
409
         }
440
         }
410
     }];
441
     }];
411
-
412
 }
442
 }
413
 
443
 
414
 
444
 
426
 }
456
 }
427
 
457
 
428
 #pragma mark - fs.mkdir
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
 #pragma mark - fs.readFile
464
 #pragma mark - fs.readFile
443
                   resolver:(RCTPromiseResolveBlock)resolve
467
                   resolver:(RCTPromiseResolveBlock)resolve
444
                   rejecter:(RCTPromiseRejectBlock)reject)
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
             return;
474
             return;
452
         }
475
         }
453
-        if(encoding == @"ascii")
454
-        {
476
+        if(encoding == @"ascii") {
455
             resolve((NSMutableArray *)content);
477
             resolve((NSMutableArray *)content);
456
         }
478
         }
457
-        else
458
-        {
479
+        else {
459
             resolve((NSString *)content);
480
             resolve((NSString *)content);
460
         }
481
         }
461
     }];
482
     }];
538
       });
559
       });
539
         resolve(@[[NSNull null]]);
560
         resolve(@[[NSNull null]]);
540
     } else {
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
         });
579
         });
559
         resolve(@[[NSNull null]]);
580
         resolve(@[[NSNull null]]);
560
     } else {
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
     {
593
     {
573
         resolve(@[[NSNull null]]);
594
         resolve(@[[NSNull null]]);
574
     } else {
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
 // fs methods
58
 // fs methods
59
 + (RNFetchBlobFS *) getFileStreams;
59
 + (RNFetchBlobFS *) getFileStreams;
60
 + (BOOL) mkdir:(NSString *) path;
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
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
66
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
62
 + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
67
 + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
63
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
68
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
64
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
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
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
71
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
67
 + (void) slice:(NSString *)path
72
 + (void) slice:(NSString *)path
68
          dest:(NSString *)dest
73
          dest:(NSString *)dest

+ 154
- 91
ios/RNFetchBlobFS.m View File

13
 #import "IOS7Polyfill.h"
13
 #import "IOS7Polyfill.h"
14
 @import AssetsLibrary;
14
 @import AssetsLibrary;
15
 
15
 
16
+#import <CommonCrypto/CommonDigest.h>
17
+
16
 #if __has_include(<React/RCTAssert.h>)
18
 #if __has_include(<React/RCTAssert.h>)
17
 #import <React/RCTBridge.h>
19
 #import <React/RCTBridge.h>
18
 #import <React/RCTEventDispatcher.h>
20
 #import <React/RCTEventDispatcher.h>
164
                 if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
166
                 if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
165
                 {
167
                 {
166
                     NSString * message = [NSString stringWithFormat:@"File does not exist at path %@", path];
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
                     [event sendDeviceEventWithName:streamId body:payload];
170
                     [event sendDeviceEventWithName:streamId body:payload];
169
                     free(buffer);
171
                     free(buffer);
170
                     return ;
172
                     return ;
197
             }
199
             }
198
             else
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
                 [event sendDeviceEventWithName:streamId body:payload];
203
                 [event sendDeviceEventWithName:streamId body:payload];
202
             }
204
             }
203
             // release buffer
205
             // release buffer
207
         }
209
         }
208
         @catch (NSError * err)
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
             [event sendDeviceEventWithName:streamId body:payload];
213
             [event sendDeviceEventWithName:streamId body:payload];
212
         }
214
         }
213
         @finally
215
         @finally
330
 
332
 
331
 # pragma mark - write file
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
     @try {
342
     @try {
336
         NSFileManager * fm = [NSFileManager defaultManager];
343
         NSFileManager * fm = [NSFileManager defaultManager];
339
         // after the folders created, write data into the file
346
         // after the folders created, write data into the file
340
         NSString * folder = [path stringByDeletingLastPathComponent];
347
         NSString * folder = [path stringByDeletingLastPathComponent];
341
         encoding = [encoding lowercaseString];
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
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
359
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
344
             if(err != nil) {
360
             if(err != nil) {
345
                 return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
361
                 return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
348
                 return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
364
                 return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
349
             }
365
             }
350
         }
366
         }
367
+
351
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
368
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
352
         NSData * content = nil;
369
         NSData * content = nil;
353
         if([encoding RNFBContainsString:@"base64"]) {
370
         if([encoding RNFBContainsString:@"base64"]) {
356
         else if([encoding isEqualToString:@"uri"]) {
373
         else if([encoding isEqualToString:@"uri"]) {
357
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
374
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
358
                 if(errMsg != nil)
375
                 if(errMsg != nil)
359
-                    reject(@"RNFetchBlob writeFile Error", errMsg, nil);
376
+                    reject(@"EUNSPECIFIED", errMsg, nil);
360
                 else
377
                 else
361
                     resolve(size);
378
                     resolve(size);
362
             }];
379
             }];
379
     }
396
     }
380
     @catch (NSException * e)
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
 # pragma mark - write file (array)
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
     @try {
411
     @try {
390
         NSFileManager * fm = [NSFileManager defaultManager];
412
         NSFileManager * fm = [NSFileManager defaultManager];
391
         NSError * err = nil;
413
         NSError * err = nil;
392
         // check if the folder exists, if not exists, create folders recursively
414
         // check if the folder exists, if not exists, create folders recursively
393
         // after the folders created, write data into the file
415
         // after the folders created, write data into the file
394
         NSString * folder = [path stringByDeletingLastPathComponent];
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
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
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
         NSMutableData * fileContent = [NSMutableData alloc];
433
         NSMutableData * fileContent = [NSMutableData alloc];
399
         // prevent stack overflow, alloc on heap
434
         // prevent stack overflow, alloc on heap
400
         char * bytes = (char*) malloc([data count]);
435
         char * bytes = (char*) malloc([data count]);
402
             bytes[i] = [[data objectAtIndex:i] charValue];
437
             bytes[i] = [[data objectAtIndex:i] charValue];
403
         }
438
         }
404
         [fileContent appendBytes:bytes length:data.count];
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
         // if file exists, write file
446
         // if file exists, write file
409
         else {
447
         else {
423
     }
461
     }
424
     @catch (NSException * e)
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
 
469
 
432
 + (void) readFile:(NSString *)path
470
 + (void) readFile:(NSString *)path
433
          encoding:(NSString *)encoding
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
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
474
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
437
         __block NSData * fileContent;
475
         __block NSData * fileContent;
443
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
481
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
444
             if(err != nil)
482
             if(err != nil)
445
             {
483
             {
446
-                onComplete(nil, [err description]);
484
+                onComplete(nil, @"EUNSPECIFIED", [err description]);
447
                 free(buffer);
485
                 free(buffer);
448
                 return;
486
                 return;
449
             }
487
             }
452
         }
490
         }
453
         else
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
                 return;
500
                 return;
458
             }
501
             }
459
             fileContent = [NSData dataWithContentsOfFile:path];
502
             fileContent = [NSData dataWithContentsOfFile:path];
466
             {
509
             {
467
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
510
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
468
                 if(utf8 == nil)
511
                 if(utf8 == nil)
469
-                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil);
512
+                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil, nil);
470
                 else
513
                 else
471
-                    onComplete(utf8, nil);
514
+                    onComplete(utf8, nil, nil);
472
             }
515
             }
473
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
516
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
474
-                onComplete([fileContent base64EncodedStringWithOptions:0], nil);
517
+                onComplete([fileContent base64EncodedStringWithOptions:0], nil, nil);
475
             }
518
             }
476
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
519
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
477
                 NSMutableArray * resultArray = [NSMutableArray array];
520
                 NSMutableArray * resultArray = [NSMutableArray array];
479
                 for(int i=0;i<[fileContent length];i++) {
522
                 for(int i=0;i<[fileContent length];i++) {
480
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
523
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
481
                 }
524
                 }
482
-                onComplete(resultArray, nil);
525
+                onComplete(resultArray, nil, nil);
483
             }
526
             }
484
         }
527
         }
485
         else
528
         else
486
         {
529
         {
487
-            onComplete(fileContent, nil);
530
+            onComplete(fileContent, nil, nil);
488
         }
531
         }
489
 
532
 
490
     }];
533
     }];
492
 
535
 
493
 # pragma mark - hash
536
 # pragma mark - hash
494
 
537
 
495
-RCT_EXPORT_METHOD(hash:(NSString *)filepath
538
++ (void) hash:(NSString *)path
496
                   algorithm:(NSString *)algorithm
539
                   algorithm:(NSString *)algorithm
497
                   resolver:(RCTPromiseResolveBlock)resolve
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
 # pragma mark - mkdir
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
     NSError * err = nil;
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
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
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
 # pragma mark - stat
636
 # pragma mark - stat
703
             NSFileManager * fm = [NSFileManager defaultManager];
763
             NSFileManager * fm = [NSFileManager defaultManager];
704
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
764
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
705
             [os open];
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
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
778
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
713
             long max = MIN(size, [end longValue]);
779
             long max = MIN(size, [end longValue]);
714
 
780
 
715
             if(![fm fileExistsAtPath:dest]) {
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
             [handle seekToFileOffset:[start longValue]];
786
             [handle seekToFileOffset:[start longValue]];
719
-            while(read < expected)
720
-            {
721
-
787
+            while(read < expected) {
722
                 NSData * chunk;
788
                 NSData * chunk;
723
                 long chunkSize = 0;
789
                 long chunkSize = 0;
724
                 if([start longValue] + read + 10240 > max)
790
                 if([start longValue] + read + 10240 > max)
754
             long size = asset.size;
820
             long size = asset.size;
755
             long max = MIN(size, [end longValue]);
821
             long max = MIN(size, [end longValue]);
756
 
822
 
757
-            while(read < expected)
758
-            {
759
-
823
+            while(read < expected) {
760
                 uint8_t * chunk[10240];
824
                 uint8_t * chunk[10240];
761
                 long chunkSize = 0;
825
                 long chunkSize = 0;
762
                 if([start longValue] + read + 10240 > max)
826
                 if([start longValue] + read + 10240 > max)
781
             [os close];
845
             [os close];
782
             resolve(dest);
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
                     if([orgPath hasPrefix:AL_PREFIX])
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
                             if(err != nil)
122
                             if(err != nil)
123
                             {
123
                             {
124
                                 onComplete(nil, nil);
124
                                 onComplete(nil, nil);
125
                             }
125
                             }
126
                             else
126
                             else
127
                             {
127
                             {
128
-                                [request setHTTPBody:content];
128
+                                [request setHTTPBody:((NSData *)content)];
129
                                 [request setHTTPMethod: method];
129
                                 [request setHTTPMethod: method];
130
                                 [request setAllHTTPHeaderFields:mheaders];
130
                                 [request setAllHTTPHeaderFields:mheaders];
131
-                                onComplete(request, [content length]);
131
+                                onComplete(request, [((NSData *)content) length]);
132
                             }
132
                             }
133
                         }];
133
                         }];
134
                         
134
                         
222
                         NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
222
                         NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
223
                         orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
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
                             if(err != nil)
226
                             if(err != nil)
227
                             {
227
                             {
228
                                 onComplete(formData, YES);
228
                                 onComplete(formData, YES);

+ 3
- 1
polyfill/File.js View File

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

+ 2
- 2
polyfill/XMLHttpRequest.js View File

224
       /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
224
       /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
225
       /tt/
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
         throw `SyntaxError : Invalid header field name ${name}`
229
         throw `SyntaxError : Invalid header field name ${name}`
230
       }
230
       }
231
     }
231
     }