Bläddra i källkod

Merge branch '0.10.9' into exception_fixes

chrusart 6 år sedan
förälder
incheckning
7cebeaa45a
No account linked to committer's email address

+ 50
- 4
README.md Visa fil

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)
598
+- [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
597
 - [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)
598
 - [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)
599
 - [ls](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#lspathstringpromise)
601
 - [ls](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#lspathstringpromise)
643
 
645
 
644
 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`.
645
 
647
 
648
+Since version 0.10.9 `write()` resolves with the `RNFetchBlob` instance so you can promise-chain write calls:
649
+
650
+```js
651
+RNFetchBlob.fs.writeStream(
652
+    PATH_TO_FILE,
653
+    // encoding, should be one of `base64`, `utf8`, `ascii`
654
+    'utf8',
655
+    // should data append to existing content ?
656
+    true
657
+)
658
+.then(ofstream => ofstream.write('foo'))
659
+.then(ofstream => ofstream.write('bar'))
660
+.then(ofstream => ofstream.write('foobar'))
661
+.then(ofstream => ofstream.close())
662
+.catch(console.error)
663
+```
664
+
665
+or 
666
+
667
+```js
668
+RNFetchBlob.fs.writeStream(
669
+    PATH_TO_FILE,
670
+    // encoding, should be one of `base64`, `utf8`, `ascii`
671
+    'utf8',
672
+    // should data append to existing content ?
673
+    true
674
+)
675
+.then(stream => Promise.all([
676
+    stream.write('foo'),
677
+    stream.write('bar'),
678
+    stream.write('foobar')
679
+]))
680
+// Use array destructuring to get the stream object from the first item of the array we get from Promise.all()
681
+.then(([stream]) => stream.close())
682
+.catch(console.error)
683
+```
684
+
685
+You should **NOT** do something like this:
686
+
646
 ```js
687
 ```js
647
 RNFetchBlob.fs.writeStream(
688
 RNFetchBlob.fs.writeStream(
648
     PATH_TO_FILE,
689
     PATH_TO_FILE,
651
     // should data append to existing content ?
692
     // should data append to existing content ?
652
     true)
693
     true)
653
 .then((ofstream) => {
694
 .then((ofstream) => {
695
+    // BAD IDEA - Don't do this, those writes are unchecked:
654
     ofstream.write('foo')
696
     ofstream.write('foo')
655
     ofstream.write('bar')
697
     ofstream.write('bar')
656
     ofstream.close()
698
     ofstream.close()
657
 })
699
 })
658
-
700
+.catch(console.error)  // Cannot catch any write() errors!
659
 ```
701
 ```
660
 
702
 
703
+The problem with the above code is that the promises from the `ofstream.write()` calls are detached and "Lost".
704
+That means the entire promise chain A) resolves without waiting for the writes to finish and B) any errors caused by them are lost.
705
+That code may _seem_ to work if there are no errors, but those writes are of the type "fire and forget": You start them and then turn away and never know if they really succeeded.
706
+
661
 ### Cache File Management
707
 ### Cache File Management
662
 
708
 
663
 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
- 1
android.js Visa fil

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

+ 1
- 0
android/build.gradle Visa fil

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 Visa fil

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

+ 38
- 20
android/src/main/java/com/RNFetchBlob/RNFetchBlob.java Visa fil

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
     }
123
             };
131
             };
124
             RCTContext.addLifecycleEventListener(listener);
132
             RCTContext.addLifecycleEventListener(listener);
125
         } catch(Exception ex) {
133
         } catch(Exception ex) {
126
-            promise.reject(ex.getLocalizedMessage());
134
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
127
         }
135
         }
128
     }
136
     }
129
 
137
 
148
     }
156
     }
149
 
157
 
150
     @ReactMethod
158
     @ReactMethod
151
-    public void mkdir(String path, Callback callback) {
152
-        RNFetchBlobFS.mkdir(path, callback);
159
+    public void mkdir(String path, Promise promise) {
160
+        RNFetchBlobFS.mkdir(path, promise);
153
     }
161
     }
154
 
162
 
155
     @ReactMethod
163
     @ReactMethod
173
     }
181
     }
174
 
182
 
175
     @ReactMethod
183
     @ReactMethod
176
-    public void ls(String path, Callback callback) {
177
-        RNFetchBlobFS.ls(path, callback);
184
+    public void ls(String path, Promise promise) {
185
+        RNFetchBlobFS.ls(path, promise);
178
     }
186
     }
179
 
187
 
180
     @ReactMethod
188
     @ReactMethod
262
     }
270
     }
263
 
271
 
264
     @ReactMethod
272
     @ReactMethod
273
+    public void hash(final String path, final String algorithm, final Promise promise) {
274
+        threadPool.execute(new Runnable() {
275
+            @Override
276
+            public void run() {
277
+                RNFetchBlobFS.hash(path, algorithm, promise);
278
+            }
279
+        });
280
+    }
281
+
265
     /**
282
     /**
266
      * @param path Stream file path
283
      * @param path Stream file path
267
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
284
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
268
      * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
285
      * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
269
      */
286
      */
287
+    @ReactMethod
270
     public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
288
     public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
271
         final ReactApplicationContext ctx = this.getReactApplicationContext();
289
         final ReactApplicationContext ctx = this.getReactApplicationContext();
272
         fsThreadPool.execute(new Runnable() {
290
         fsThreadPool.execute(new Runnable() {
340
 
358
 
341
     @ReactMethod
359
     @ReactMethod
342
     public void addCompleteDownload (ReadableMap config, Promise promise) {
360
     public void addCompleteDownload (ReadableMap config, Promise promise) {
343
-        DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
361
+        DownloadManager dm = (DownloadManager) RCTContext.getSystemService(RCTContext.DOWNLOAD_SERVICE);
344
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
362
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
345
         if(path == null) {
363
         if(path == null) {
346
-            promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path);
364
+            promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"));
347
             return;
365
             return;
348
         }
366
         }
349
         try {
367
         try {
360
             promise.resolve(null);
378
             promise.resolve(null);
361
         }
379
         }
362
         catch(Exception ex) {
380
         catch(Exception ex) {
363
-            promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString());
381
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
364
         }
382
         }
365
 
383
 
366
     }
384
     }

+ 28
- 32
android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java Visa fil

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 Visa fil

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;

+ 343
- 186
android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java Visa fil

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

+ 7
- 7
android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java Visa fil

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;

+ 46
- 14
android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java Visa fil

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

+ 2
- 1
android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java Visa fil

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 Visa fil

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 Visa fil

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') {

+ 2
- 7
class/RNFetchBlobSession.js Visa fil

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

+ 8
- 9
class/RNFetchBlobWriteStream.js Visa fil

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

+ 191
- 166
fs.js Visa fil

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

+ 7
- 9
index.js Visa fil

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

+ 2
- 2
ios.js Visa fil

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

+ 84
- 54
ios/RNFetchBlob/RNFetchBlob.m Visa fil

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

+ 6
- 1
ios/RNFetchBlobFS.h Visa fil

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

+ 190
- 56
ios/RNFetchBlobFS.m Visa fil

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

+ 4
- 4
ios/RNFetchBlobReqBuilder.m Visa fil

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

+ 5
- 2
json-stream.js Visa fil

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

+ 3
- 1
polyfill/File.js Visa fil

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 Visa fil

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
     }