Ver código fonte

Merge branch 'master' into 0.7.0

Ben Hsieh 8 anos atrás
pai
commit
2c35f522df

+ 23
- 12
README.md Ver arquivo

@@ -1,23 +1,23 @@
1
-# react-native-fetch-blob [![npm](https://img.shields.io/npm/v/react-native-fetch-blob.svg?style=flat-square)](https://www.npmjs.com/package/react-native-fetch-blob) ![](https://img.shields.io/badge/PR-Welcome-brightgreen.svg?style=flat-square) [![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000&style=flat-square)]() [![npm](https://img.shields.io/badge/inProgress-0.7.0-yellow.svg?style=flat-square)]()
1
+# react-native-fetch-blob [![npm](https://img.shields.io/npm/v/react-native-fetch-blob.svg?style=flat-square)](https://www.npmjs.com/package/react-native-fetch-blob) ![](https://img.shields.io/badge/PR-Welcome-brightgreen.svg?style=flat-square) [![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000&style=flat-square)]() [![npm](https://img.shields.io/badge/inProgress-0.7.0-yellow.svg?style=flat-square)](https://github.com/wkh237/react-native-fetch-blob/milestones)
2 2
 
3 3
 A module provides upload, download, and files access API. Supports file stream read/write for process large files.
4 4
 
5 5
 **Why do we need this**
6 6
 
7
-React Native does not support `Blob` object at this moment, which means if you're going to send/receive binary data via `fetch` API, that might not work as you expect. See [[fetch] Does fetch with blob() marshal data across the bridge?](https://github.com/facebook/react-native/issues/854).
7
+React Native does not support `Blob` object at this moment, which means if you're going to send/receive binary data via `fetch` API, that might not work as you expect. See [facebook/react-native#854](https://github.com/facebook/react-native/issues/854).
8 8
 
9 9
 For some use cases, you might get into trouble. For example, displaying an image that requires a specific field in headers (ex. "Authorization : Bearer ...") or body, so you can't just pass the image uri to `Image` component because that will probably returns a 401 response. Or you're going to upload binary data which generated from JS, the server will get an empry body due to [this issue](https://github.com/facebook/react-native/issues/854). With help of APIs provided by this module, you can send HTTP request with any headers, and decide how to handle the response/reqeust data without worry about if it is not supported by `fetch` API. The response data can be just simply converted into BASE64 string, or stored to a file directly so that you can read it by using file access APIs such as readFile, readStream.
10 10
 
11 11
 This module was designed to be a substitution of `Blob`, there's a set of APIs including basic file system CRUD method, and file stream reader/writer. Also it has a special `fetch` implementation that supports binary request/response body.
12 12
 
13
-**Pre v0.5.0 Users**
13
+**Backward Compatible**
14 14
 
15
-All updates are `backward-compatible` generally you don't have to change existing code unless you're going to use new APIs. In latest version (v0.5.0), new APIs can either `upload` or `download` files simply using a file path. It's much more memory efficent in some use case. We've also introduced `fs` APIs for access files, and `file stream` API that helps you read/write files (especially for **large ones**), see [Examples](#user-content-usage) bellow. This module implements native methods, supports both Android (uses awesome native library  [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client])) and IOS.
15
+All updates are `backward-compatible` generally you don't have to change existing code unless you're going to use new APIs. But we recommend pre `0.5.0` users consider upgrade the package to latest version, since we have introduced new APIs can either `upload` or `download` files simply using a file path. It's much more memory efficent in some use case. We've also introduced `fs` APIs for access files, and `file stream` API that helps you read/write files (especially for **large ones**), see [Examples](#user-content-recipes) bellow. This module implements native methods, supports both Android (uses awesome native library  [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client])) and IOS.
16 16
 
17 17
 ## TOC
18 18
 
19 19
 * [Installation](#user-content-installation)
20
-* [Guide](#user-content-guide)
20
+* [Recipes](#user-content-recipes)
21 21
  * [Download file](#user-content-download-example--fetch-files-that-needs-authorization-token)
22 22
  * [Upload file](#user-content-upload-example--dropbox-files-upload-api)
23 23
  * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
@@ -79,7 +79,7 @@ Also, if you're going to use `Android Download Manager` you have to add this to
79 79
 
80 80
 Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. So adding permissions in `AndroidManifest.xml` won't work in Android 6.0 devices. To grant permissions in runtime, you might use modules like [react-native-android-permissions](https://github.com/lucasferreira/react-native-android-permissions).
81 81
 
82
-## Guide
82
+## Recipes
83 83
 
84 84
 ```js
85 85
 import RNFetchBlob from 'react-native-fetch-blob'
@@ -256,7 +256,7 @@ Elements have property `filename` will be transformed into binary format, otherw
256 256
   })
257 257
 ```
258 258
 
259
-What if you want to upload a file in some field ? Just like [upload a file from storage](#user-content-upload-a-file-from-storage) example, wrap `data` by `wrap` API (this feature is only available for `version >= v0.5.0`)
259
+What if you want to upload a file in some field ? Just like [upload a file from storage](#user-content-upload-a-file-from-storage) example, wrap `data` by `wrap` API (this feature is only available for `version >= v0.5.0`). On version >= `0.6.2`, it is possible to set custom MIME type when appending file to form data.
260 260
 
261 261
 ```js
262 262
 
@@ -274,6 +274,14 @@ What if you want to upload a file in some field ? Just like [upload a file from
274 274
       // Or simply wrap the file path with RNFetchBlob.wrap().
275 275
       data: RNFetchBlob.wrap(PATH_TO_THE_FILE)
276 276
     },
277
+    {
278
+      name : 'ringtone',
279
+      filename : 'ring.mp3',
280
+      // use custom MIME type
281
+      type : 'application/mp3',
282
+      // upload a file from asset is also possible in version >= 0.6.2
283
+      data : RNFetchBlob.wrap(RNFetchBlob.fs.asset('default-ringtone.mp3'))
284
+    }
277 285
     // elements without property `filename` will be sent as plain text
278 286
     { name : 'name', data : 'user'},
279 287
     { name : 'info', data : JSON.stringify({
@@ -289,7 +297,7 @@ What if you want to upload a file in some field ? Just like [upload a file from
289 297
 
290 298
 #### Upload/Download progress
291 299
 
292
-In `version >= 0.4.2` it is possible to know the upload/download progress.
300
+In `version >= 0.4.2` it is possible to know the upload/download progress. On Anroid, only download progress is supported. See [wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetchprogresseventlistenerpromisernfetchblobresponse) for more information.
293 301
 
294 302
 ```js
295 303
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -391,10 +399,12 @@ RNFetchBlob.config({
391 399
 
392 400
 #### File Access
393 401
 
394
-File access APIs were made when developing `v0.5.0`, which helping us write tests, and was not planned to be a part of this module. However I realized that, it's hard to find a great solution to manage cached files, every one who use this moudle may need those APIs for there cases.
402
+File access APIs were made when developing `v0.5.0`, which helping us write tests, and was not planned to be a part of this module. However we realized that, it's hard to find a great solution to manage cached files, every one who use this moudle may need these APIs for there cases.
395 403
 
396
-File Access APIs
404
+Before get started using file APIs we recommend read [Differences between File Source](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#differences-between-file-source) first.
397 405
 
406
+File Access APIs
407
+- [asset (0.6.2)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#assetfilenamestringstring)
398 408
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
399 409
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
400 410
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
@@ -423,7 +433,7 @@ When calling `readStream` method, you have to `open` the stream, and start to re
423 433
 
424 434
 ```js
425 435
 let data = ''
426
-RNFetchBlob.readStream(
436
+RNFetchBlob.fs.readStream(
427 437
     // encoding, should be one of `base64`, `utf8`, `ascii`
428 438
     'base64',
429 439
     // file path
@@ -450,7 +460,7 @@ RNFetchBlob.readStream(
450 460
 When use `writeStream`, the stream is also opened immediately, but you have to `write`, and `close` by yourself.
451 461
 
452 462
 ```js
453
-RNFetchBlob.writeStream(
463
+RNFetchBlob.fs.writeStream(
454 464
     PATH_TO_FILE,
455 465
     // encoding, should be one of `base64`, `utf8`, `ascii`
456 466
     'utf8',
@@ -539,6 +549,7 @@ RNFetchBlob.config({
539 549
 
540 550
 | Version | |
541 551
 |---|---|
552
+| 0.6.2 | Add support of asset file and camera roll files, Support custom MIME type when sending multipart request, thanks @smartt |
542 553
 | 0.6.1 | Fix #37 progress report API issue on IOS |
543 554
 | 0.6.0 | Add readFile and writeFile API for easier file access, also added Android download manager support. |
544 555
 | 0.5.8 | Fix #33 PUT request will always be sent as POST on Android |

+ 1
- 1
package.json Ver arquivo

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "fetchblob",
3
-  "version": "0.6.1",
3
+  "version": "0.6.2",
4 4
   "private": true,
5 5
   "scripts": {
6 6
     "start": "node node_modules/react-native/local-cli/cli.js start",

+ 25
- 12
src/README.md Ver arquivo

@@ -1,23 +1,25 @@
1
-# react-native-fetch-blob [![npm](https://img.shields.io/npm/v/react-native-fetch-blob.svg?style=flat-square)](https://www.npmjs.com/package/react-native-fetch-blob) ![](https://img.shields.io/badge/PR-Welcome-brightgreen.svg?style=flat-square) [![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000&style=flat-square)]()
1
+# react-native-fetch-blob [![npm](https://img.shields.io/npm/v/react-native-fetch-blob.svg?style=flat-square)](https://www.npmjs.com/package/react-native-fetch-blob) ![](https://img.shields.io/badge/PR-Welcome-brightgreen.svg?style=flat-square) [![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000&style=flat-square)]() [![npm](https://img.shields.io/badge/inProgress-0.7.0-yellow.svg?style=flat-square)](https://github.com/wkh237/react-native-fetch-blob/milestones)
2 2
 
3 3
 A module provides upload, download, and files access API. Supports file stream read/write for process large files.
4 4
 
5
+## [Please check our github for updated document](https://github.com/wkh237/react-native-fetch-blob)
6
+
5 7
 **Why do we need this**
6 8
 
7
-React Native does not support `Blob` object at this moment, which means if you're going to send/receive binary data via `fetch` API, that might not work as you expect. See [[fetch] Does fetch with blob() marshal data across the bridge?](https://github.com/facebook/react-native/issues/854).
9
+React Native does not support `Blob` object at this moment, which means if you're going to send/receive binary data via `fetch` API, that might not work as you expect. See [facebook/react-native#854](https://github.com/facebook/react-native/issues/854).
8 10
 
9 11
 For some use cases, you might get into trouble. For example, displaying an image that requires a specific field in headers (ex. "Authorization : Bearer ...") or body, so you can't just pass the image uri to `Image` component because that will probably returns a 401 response. Or you're going to upload binary data which generated from JS, the server will get an empry body due to [this issue](https://github.com/facebook/react-native/issues/854). With help of APIs provided by this module, you can send HTTP request with any headers, and decide how to handle the response/reqeust data without worry about if it is not supported by `fetch` API. The response data can be just simply converted into BASE64 string, or stored to a file directly so that you can read it by using file access APIs such as readFile, readStream.
10 12
 
11 13
 This module was designed to be a substitution of `Blob`, there's a set of APIs including basic file system CRUD method, and file stream reader/writer. Also it has a special `fetch` implementation that supports binary request/response body.
12 14
 
13
-**Pre v0.5.0 Users**
15
+**Backward Compatible**
14 16
 
15
-All updates are `backward-compatible` generally you don't have to change existing code unless you're going to use new APIs. In latest version (v0.5.0), new APIs can either `upload` or `download` files simply using a file path. It's much more memory efficent in some use case. We've also introduced `fs` APIs for access files, and `file stream` API that helps you read/write files (especially for **large ones**), see [Examples](#user-content-usage) bellow. This module implements native methods, supports both Android (uses awesome native library  [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client])) and IOS.
17
+All updates are `backward-compatible` generally you don't have to change existing code unless you're going to use new APIs. But we recommend pre `0.5.0` users consider upgrade the package to latest version, since we have introduced new APIs can either `upload` or `download` files simply using a file path. It's much more memory efficent in some use case. We've also introduced `fs` APIs for access files, and `file stream` API that helps you read/write files (especially for **large ones**), see [Examples](#user-content-recipes) bellow. This module implements native methods, supports both Android (uses awesome native library  [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client])) and IOS.
16 18
 
17 19
 ## TOC
18 20
 
19 21
 * [Installation](#user-content-installation)
20
-* [Guide](#user-content-guide)
22
+* [Recipes](#user-content-recipes)
21 23
  * [Download file](#user-content-download-example--fetch-files-that-needs-authorization-token)
22 24
  * [Upload file](#user-content-upload-example--dropbox-files-upload-api)
23 25
  * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
@@ -79,7 +81,7 @@ Also, if you're going to use `Android Download Manager` you have to add this to
79 81
 
80 82
 Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. So adding permissions in `AndroidManifest.xml` won't work in Android 6.0 devices. To grant permissions in runtime, you might use modules like [react-native-android-permissions](https://github.com/lucasferreira/react-native-android-permissions).
81 83
 
82
-## Guide
84
+## Recipes
83 85
 
84 86
 ```js
85 87
 import RNFetchBlob from 'react-native-fetch-blob'
@@ -256,7 +258,7 @@ Elements have property `filename` will be transformed into binary format, otherw
256 258
   })
257 259
 ```
258 260
 
259
-What if you want to upload a file in some field ? Just like [upload a file from storage](#user-content-upload-a-file-from-storage) example, wrap `data` by `wrap` API (this feature is only available for `version >= v0.5.0`)
261
+What if you want to upload a file in some field ? Just like [upload a file from storage](#user-content-upload-a-file-from-storage) example, wrap `data` by `wrap` API (this feature is only available for `version >= v0.5.0`). On version >= `0.6.2`, it is possible to set custom MIME type when appending file to form data.
260 262
 
261 263
 ```js
262 264
 
@@ -274,6 +276,14 @@ What if you want to upload a file in some field ? Just like [upload a file from
274 276
       // Or simply wrap the file path with RNFetchBlob.wrap().
275 277
       data: RNFetchBlob.wrap(PATH_TO_THE_FILE)
276 278
     },
279
+    {
280
+      name : 'ringtone',
281
+      filename : 'ring.mp3',
282
+      // use custom MIME type
283
+      type : 'application/mp3',
284
+      // upload a file from asset is also possible in version >= 0.6.2
285
+      data : RNFetchBlob.wrap(RNFetchBlob.fs.asset('default-ringtone.mp3'))
286
+    }
277 287
     // elements without property `filename` will be sent as plain text
278 288
     { name : 'name', data : 'user'},
279 289
     { name : 'info', data : JSON.stringify({
@@ -289,7 +299,7 @@ What if you want to upload a file in some field ? Just like [upload a file from
289 299
 
290 300
 #### Upload/Download progress
291 301
 
292
-In `version >= 0.4.2` it is possible to know the upload/download progress.
302
+In `version >= 0.4.2` it is possible to know the upload/download progress. On Anroid, only download progress is supported. See [wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetchprogresseventlistenerpromisernfetchblobresponse) for more information.
293 303
 
294 304
 ```js
295 305
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -391,10 +401,12 @@ RNFetchBlob.config({
391 401
 
392 402
 #### File Access
393 403
 
394
-File access APIs were made when developing `v0.5.0`, which helping us write tests, and was not planned to be a part of this module. However I realized that, it's hard to find a great solution to manage cached files, every one who use this moudle may need those APIs for there cases.
404
+File access APIs were made when developing `v0.5.0`, which helping us write tests, and was not planned to be a part of this module. However we realized that, it's hard to find a great solution to manage cached files, every one who use this moudle may need these APIs for there cases.
395 405
 
396
-File Access APIs
406
+Before get started using file APIs we recommend read [Differences between File Source](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#differences-between-file-source) first.
397 407
 
408
+File Access APIs
409
+- [asset (0.6.2)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#assetfilenamestringstring)
398 410
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
399 411
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
400 412
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
@@ -423,7 +435,7 @@ When calling `readStream` method, you have to `open` the stream, and start to re
423 435
 
424 436
 ```js
425 437
 let data = ''
426
-RNFetchBlob.readStream(
438
+RNFetchBlob.fs.readStream(
427 439
     // encoding, should be one of `base64`, `utf8`, `ascii`
428 440
     'base64',
429 441
     // file path
@@ -450,7 +462,7 @@ RNFetchBlob.readStream(
450 462
 When use `writeStream`, the stream is also opened immediately, but you have to `write`, and `close` by yourself.
451 463
 
452 464
 ```js
453
-RNFetchBlob.writeStream(
465
+RNFetchBlob.fs.writeStream(
454 466
     PATH_TO_FILE,
455 467
     // encoding, should be one of `base64`, `utf8`, `ascii`
456 468
     'utf8',
@@ -539,6 +551,7 @@ RNFetchBlob.config({
539 551
 
540 552
 | Version | |
541 553
 |---|---|
554
+| 0.6.2 | Add support of asset file and camera roll files, Support custom MIME type when sending multipart request, thanks @smartt |
542 555
 | 0.6.1 | Fix #37 progress report API issue on IOS |
543 556
 | 0.6.0 | Add readFile and writeFile API for easier file access, also added Android download manager support. |
544 557
 | 0.5.8 | Fix #33 PUT request will always be sent as POST on Android |

+ 3
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java Ver arquivo

@@ -13,9 +13,12 @@ import java.util.Map;
13 13
 public class RNFetchBlob extends ReactContextBaseJavaModule {
14 14
 
15 15
     String filePathPrefix = "RNFetchBlob-file://";
16
+    static ReactApplicationContext RCTContext;
16 17
 
17 18
     public RNFetchBlob(ReactApplicationContext reactContext) {
19
+
18 20
         super(reactContext);
21
+        RCTContext = reactContext;
19 22
     }
20 23
 
21 24
     @Override

+ 164
- 43
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java Ver arquivo

@@ -1,10 +1,16 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
+import android.app.Application;
4
+import android.content.CursorLoader;
3 5
 import android.content.Intent;
6
+import android.content.res.AssetFileDescriptor;
7
+import android.content.res.AssetManager;
8
+import android.database.Cursor;
4 9
 import android.media.MediaScannerConnection;
5 10
 import android.net.Uri;
6 11
 import android.os.AsyncTask;
7 12
 import android.os.Environment;
13
+import android.provider.MediaStore;
8 14
 
9 15
 import com.facebook.react.bridge.Arguments;
10 16
 import com.facebook.react.bridge.Callback;
@@ -43,6 +49,7 @@ public class RNFetchBlobFS {
43 49
     ReactApplicationContext mCtx;
44 50
     DeviceEventManagerModule.RCTDeviceEventEmitter emitter;
45 51
     String encoding = "base64";
52
+    static final String assetPrefix = "bundle-assets://";
46 53
     boolean append = false;
47 54
     OutputStream writeStreamInstance = null;
48 55
     static HashMap<String, RNFetchBlobFS> fileStreams = new HashMap<>();
@@ -134,6 +141,7 @@ public class RNFetchBlobFS {
134 141
      * @param promise
135 142
      */
136 143
     static public void readFile(String path, String encoding, final Promise promise ) {
144
+        path = normalizePath(path);
137 145
         AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {
138 146
             @Override
139 147
             protected Integer doInBackground(String... strings) {
@@ -141,12 +149,25 @@ public class RNFetchBlobFS {
141 149
                 try {
142 150
                     String path = strings[0];
143 151
                     String encoding = strings[1];
144
-                    File f = new File(path);
145
-                    int length = (int) f.length();
146
-                    byte[] bytes = new byte[length];
147
-                    FileInputStream in = new FileInputStream(f);
148
-                    in.read(bytes);
149
-                    in.close();
152
+                    byte[] bytes;
153
+
154
+                    if(path.startsWith(assetPrefix)) {
155
+                        String assetName = path.replace(assetPrefix, "");
156
+                        long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
157
+                        bytes = new byte[(int) length];
158
+                        InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
159
+                        in.read(bytes, 0, (int) length);
160
+                        in.close();
161
+                    }
162
+                    else {
163
+                        File f = new File(path);
164
+                        int length = (int) f.length();
165
+                        bytes = new byte[length];
166
+                        FileInputStream in = new FileInputStream(f);
167
+                        in.read(bytes);
168
+                        in.close();
169
+                    }
170
+
150 171
                     switch (encoding.toLowerCase()) {
151 172
                         case "base64" :
152 173
                             promise.resolve(Base64.encodeToString(bytes, 0));
@@ -209,6 +230,7 @@ public class RNFetchBlobFS {
209 230
      * @param bufferSize    Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
210 231
      */
211 232
     public void readStream( String path, String encoding, int bufferSize) {
233
+        path = normalizePath(path);
212 234
         AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {
213 235
             @Override
214 236
             protected Integer doInBackground(String ... args) {
@@ -221,7 +243,15 @@ public class RNFetchBlobFS {
221 243
                     int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
222 244
                     if(bufferSize > 0)
223 245
                         chunkSize = bufferSize;
224
-                    FileInputStream fs = new FileInputStream(new File(path));
246
+
247
+                    InputStream fs;
248
+                    if(path.startsWith(assetPrefix)) {
249
+                        fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(assetPrefix, ""));
250
+                    }
251
+                    else {
252
+                        fs = new FileInputStream(new File(path));
253
+                    }
254
+
225 255
                     byte[] buffer = new byte[chunkSize];
226 256
                     int cursor = 0;
227 257
                     boolean error = false;
@@ -314,7 +344,6 @@ public class RNFetchBlobFS {
314 344
         try {
315 345
             stream.write(chunk);
316 346
             callback.invoke();
317
-            chunk = null;
318 347
         } catch (Exception e) {
319 348
             callback.invoke(e.getLocalizedMessage());
320 349
         }
@@ -344,7 +373,7 @@ public class RNFetchBlobFS {
344 373
     }
345 374
 
346 375
     /**
347
-     * Close file stream by ID
376
+     * Close file write stream by ID
348 377
      * @param streamId Stream ID
349 378
      * @param callback JS context callback
350 379
      */
@@ -395,13 +424,13 @@ public class RNFetchBlobFS {
395 424
      * @param callback  JS context callback
396 425
      */
397 426
     static void cp(String path, String dest, Callback callback) {
427
+        path = normalizePath(path);
398 428
         InputStream in = null;
399 429
         OutputStream out = null;
400 430
 
401 431
         try {
402 432
 
403
-            String destFolder = new File(dest).getPath();
404
-            if(!new File(path).exists()) {
433
+            if(!isPathExists(path)) {
405 434
                 callback.invoke("cp error: source file at path`" + path + "` not exists");
406 435
                 return;
407 436
             }
@@ -409,7 +438,7 @@ public class RNFetchBlobFS {
409 438
             if(!new File(dest).exists())
410 439
                 new File(dest).createNewFile();
411 440
 
412
-            in = new FileInputStream(path);
441
+            in = inputStreamFromPath(path);
413 442
             out = new FileOutputStream(dest);
414 443
 
415 444
             byte[] buf = new byte[1024];
@@ -454,9 +483,22 @@ public class RNFetchBlobFS {
454 483
      * @param callback  JS context callback
455 484
      */
456 485
     static void exists(String path, Callback callback) {
457
-        boolean exist = new File(path).exists();
458
-        boolean isDir = new File(path).isDirectory();;
459
-        callback.invoke(exist, isDir);
486
+        path = normalizePath(path);
487
+        if(isAsset(path)) {
488
+            try {
489
+                String filename = path.replace(assetPrefix, "");
490
+                AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(filename);
491
+                callback.invoke(true, false);
492
+            } catch (IOException e) {
493
+                callback.invoke(false, false);
494
+            }
495
+        }
496
+        else {
497
+            boolean exist = new File(path).exists();
498
+            boolean isDir = new File(path).isDirectory();
499
+            callback.invoke(exist, isDir);
500
+        }
501
+
460 502
     }
461 503
 
462 504
     /**
@@ -465,21 +507,23 @@ public class RNFetchBlobFS {
465 507
      * @param callback  JS context callback
466 508
      */
467 509
     static void ls(String path, Callback callback) {
510
+        path = normalizePath(path);
468 511
         File src = new File(path);
469
-        if(!src.exists() || !src.isDirectory()) {
512
+        if (!src.exists() || !src.isDirectory()) {
470 513
             callback.invoke("ls error: failed to list path `" + path + "` for it is not exist or it is not a folder");
471 514
             return;
472 515
         }
473
-        String [] files = new File(path).list();
516
+        String[] files = new File(path).list();
474 517
         WritableArray arg = Arguments.createArray();
475
-        for(String i : files) {
518
+        for (String i : files) {
476 519
             arg.pushString(i);
477 520
         }
478 521
         callback.invoke(null, arg);
479 522
     }
480 523
 
481 524
     static void lstat(String path, final Callback callback) {
482
-        File src = new File(path);
525
+        path = normalizePath(path);
526
+
483 527
         new AsyncTask<String, Integer, Integer>() {
484 528
             @Override
485 529
             protected Integer doInBackground(String ...args) {
@@ -511,24 +555,59 @@ public class RNFetchBlobFS {
511 555
      */
512 556
     static void stat(String path, Callback callback) {
513 557
         try {
514
-            File target = new File(path);
515
-            if (!target.exists()) {
516
-                callback.invoke("stat error: file " + path + " does not exists");
517
-                return;
518
-            }
519
-            WritableMap stat = Arguments.createMap();
520
-            stat.putString("filename", target.getName());
521
-            stat.putString("path", target.getPath());
522
-            stat.putString("type", target.isDirectory() ? "directory" : "file");
523
-            stat.putString("size", String.valueOf(target.length()));
524
-            String lastModified = String.valueOf(target.lastModified());
525
-            stat.putString("lastModified", lastModified);
526
-            callback.invoke(null, stat);
558
+            WritableMap result = statFile(path);
559
+            if(result == null)
560
+                callback.invoke("stat error: failed to list path `" + path + "` for it is not exist or it is not a folder", null);
561
+            else
562
+                callback.invoke(null, result);
527 563
         } catch(Exception err) {
528 564
             callback.invoke(err.getLocalizedMessage());
529 565
         }
530 566
     }
531 567
 
568
+    /**
569
+     * Basic stat method
570
+     * @param path
571
+     * @return Stat result of a file or path
572
+     */
573
+    static WritableMap statFile(String path) {
574
+        try {
575
+            path = normalizePath(path);
576
+            WritableMap stat = Arguments.createMap();
577
+            if(isAsset(path)) {
578
+                String name = path.replace(assetPrefix, "");
579
+                AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(name);
580
+                stat.putString("filename", name);
581
+                stat.putString("path", path);
582
+                stat.putString("type", "asset");
583
+                stat.putString("size", String.valueOf(fd.getLength()));
584
+                stat.putString("lastModified", "0");
585
+            }
586
+            else {
587
+                File target = new File(path);
588
+                if (!target.exists()) {
589
+                    return null;
590
+                }
591
+                stat.putString("filename", target.getName());
592
+                stat.putString("path", target.getPath());
593
+                stat.putString("type", target.isDirectory() ? "directory" : "file");
594
+                stat.putString("size", String.valueOf(target.length()));
595
+                String lastModified = String.valueOf(target.lastModified());
596
+                stat.putString("lastModified", lastModified);
597
+
598
+            }
599
+            return stat;
600
+        } catch(Exception err) {
601
+            return null;
602
+        }
603
+    }
604
+
605
+    /**
606
+     * Media scanner scan file
607
+     * @param path
608
+     * @param mimes
609
+     * @param callback
610
+     */
532 611
     void scanFile(String [] path, String[] mimes, final Callback callback) {
533 612
         try {
534 613
             MediaScannerConnection.scanFile(mCtx, path, mimes, new MediaScannerConnection.OnScanCompletedListener() {
@@ -669,18 +748,60 @@ public class RNFetchBlobFS {
669 748
         this.emitter.emit("RNFetchBlobStream" + taskId, eventData);
670 749
     }
671 750
 
672
-    static WritableMap statFile(String path) {
673
-        File target = new File(path);
674
-        if(!target.exists()) {
675
-            return null;
751
+    /**
752
+     * Get input stream of the given path, when the path is a string starts with bundle-assets://
753
+     * the stream is created by Assets Manager, otherwise use FileInputStream.
754
+     * @param path
755
+     * @return
756
+     * @throws IOException
757
+     */
758
+    static InputStream inputStreamFromPath(String path) throws IOException {
759
+        if (path.startsWith(assetPrefix)) {
760
+            return RNFetchBlob.RCTContext.getAssets().open(path.replace(assetPrefix, ""));
761
+        }
762
+        return new FileInputStream(new File(path));
763
+    }
764
+
765
+    /**
766
+     * Check if the asset or the file exists
767
+     * @param path
768
+     * @return
769
+     */
770
+    static boolean isPathExists(String path) {
771
+        if(path.startsWith(assetPrefix)) {
772
+            try {
773
+                RNFetchBlob.RCTContext.getAssets().open(path.replace(assetPrefix, ""));
774
+            } catch (IOException e) {
775
+                return false;
776
+            }
777
+            return true;
778
+        }
779
+        else {
780
+            return new File(path).exists();
781
+        }
782
+
783
+    }
784
+
785
+    static boolean isAsset(String path) {
786
+        return path.startsWith(assetPrefix);
787
+    }
788
+
789
+    static String normalizePath(String path) {
790
+        if(path.startsWith(assetPrefix)) {
791
+            return path;
792
+        }
793
+        else if (path.startsWith("content://")) {
794
+            String[] proj = { MediaStore.Images.Media.DATA };
795
+            Uri contentUri = Uri.parse(path);
796
+            CursorLoader loader = new CursorLoader(RNFetchBlob.RCTContext, contentUri, proj, null, null, null);
797
+            Cursor cursor = loader.loadInBackground();
798
+            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
799
+            cursor.moveToFirst();
800
+            String result = cursor.getString(column_index);
801
+            cursor.close();
802
+            return result;
676 803
         }
677
-        WritableMap stat = Arguments.createMap();
678
-        stat.putString("filename", target.getName());
679
-        stat.putString("path", target.getPath());
680
-        stat.putString("type", target.isDirectory() ? "directory" : "file");
681
-        stat.putInt("size", (int)target.length());
682
-        stat.putInt("lastModified", (int)target.lastModified());
683
-        return stat;
804
+        return path;
684 805
     }
685 806
 
686 807
 }

+ 45
- 5
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java Ver arquivo

@@ -19,6 +19,8 @@ import com.loopj.android.http.Base64;
19 19
 import com.loopj.android.http.MySSLSocketFactory;
20 20
 
21 21
 import java.io.File;
22
+import java.io.IOException;
23
+import java.io.InputStream;
22 24
 import java.security.KeyStore;
23 25
 
24 26
 import cz.msebera.android.httpclient.HttpEntity;
@@ -111,6 +113,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
111 113
 
112 114
             req = new AsyncHttpClient();
113 115
 
116
+            req.setLoggingEnabled(false);
117
+
114 118
             // use trusty SSL socket
115 119
             if(this.options.trusty) {
116 120
                 KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
@@ -196,15 +200,34 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
196 200
                 String data = map.getString("data");
197 201
                 // file field
198 202
                 if(map.hasKey("filename")) {
203
+                    String mime = map.hasKey("type") ? map.getString("type") : ContentType.APPLICATION_OCTET_STREAM.getMimeType();
199 204
                     String filename = map.getString("filename");
200 205
                     // upload from storage
201 206
                     if(data.startsWith(filePathPrefix)) {
202
-                        File file = new File(data.substring(filePathPrefix.length()));
203
-                        form.addBinaryBody(name, file, ContentType.APPLICATION_OCTET_STREAM, filename);
207
+                        String orgPath = data.substring(filePathPrefix.length());
208
+                        orgPath = RNFetchBlobFS.normalizePath(orgPath);
209
+                        // path starts with content://
210
+                        if(RNFetchBlobFS.isAsset(orgPath)) {
211
+                            try {
212
+                                String assetName = orgPath.replace(RNFetchBlobFS.assetPrefix, "");
213
+                                InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
214
+                                long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
215
+                                byte [] bytes = new byte[(int) length];
216
+                                in.read(bytes, 0, (int) length);
217
+                                in.close();
218
+                                form.addBinaryBody(name, bytes, ContentType.create(mime), filename);
219
+                            } catch (IOException e) {
220
+//                                e.printStackTrace();
221
+                            }
222
+                        }
223
+                        else {
224
+                            File file = new File(RNFetchBlobFS.normalizePath(orgPath));
225
+                            form.addBinaryBody(name, file, ContentType.create(mime), filename);
226
+                        }
204 227
                     }
205 228
                     // base64 embedded file content
206 229
                     else {
207
-                        form.addBinaryBody(name, Base64.decode(data, 0), ContentType.APPLICATION_OCTET_STREAM, filename);
230
+                        form.addBinaryBody(name, Base64.decode(data, 0), ContentType.create(mime), filename);
208 231
                     }
209 232
                 }
210 233
                 // data field
@@ -227,8 +250,25 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
227 250
             byte [] blob;
228 251
             // upload from storage
229 252
             if(body.startsWith(filePathPrefix)) {
230
-                String filePath = body.substring(filePathPrefix.length());
231
-                entity = new FileEntity(new File(filePath));
253
+                String orgPath = body.substring(filePathPrefix.length());
254
+                orgPath = RNFetchBlobFS.normalizePath(orgPath);
255
+                // handle
256
+                if(RNFetchBlobFS.isAsset(orgPath)) {
257
+                    try {
258
+                        String assetName = orgPath.replace(RNFetchBlobFS.assetPrefix, "");
259
+                        InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
260
+                        long length = 0;
261
+                        length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
262
+                        byte [] bytes = new byte[(int) length];
263
+                        in.read(bytes, 0, (int) length);
264
+                        in.close();
265
+                        entity = new ByteArrayEntity(bytes);
266
+                    } catch (IOException e) {
267
+//                        e.printStackTrace();
268
+                    }
269
+                }
270
+                else
271
+                    entity = new FileEntity(new File(RNFetchBlobFS.normalizePath(orgPath)));
232 272
             }
233 273
             else {
234 274
                 blob = Base64.decode(body, 0);

+ 12
- 1
src/fs.js Ver arquivo

@@ -8,6 +8,7 @@
8 8
 import {
9 9
   NativeModules,
10 10
   DeviceEventEmitter,
11
+  Platform,
11 12
   NativeAppEventEmitter,
12 13
 } from 'react-native'
13 14
 import RNFetchBlobSession from './class/RNFetchBlobSession'
@@ -47,6 +48,15 @@ function session(name:string):RNFetchBlobSession {
47 48
   }
48 49
 }
49 50
 
51
+function asset(path:string):string {
52
+  if(Platform.OS === 'ios') {
53
+    // path from camera roll
54
+    if(/^assets-library\:\/\//.test(path))
55
+      return path
56
+  }
57
+  return 'bundle-assets://' + path
58
+}
59
+
50 60
 function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise {
51 61
   encoding = encoding || 'utf8'
52 62
   return new Promise((resolve, reject) => {
@@ -324,5 +334,6 @@ export default {
324 334
   stat,
325 335
   lstat,
326 336
   scanFile,
327
-  dirs
337
+  dirs,
338
+  asset
328 339
 }

+ 1
- 1
src/index.js Ver arquivo

@@ -272,5 +272,5 @@ export default {
272 272
   config,
273 273
   session,
274 274
   fs,
275
-  wrap,
275
+  wrap
276 276
 }

+ 6
- 0
src/ios/RNFetchBlob.xcodeproj/project.pbxproj Ver arquivo

@@ -12,6 +12,7 @@
12 12
 		A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; };
13 13
 		A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = A15C30131CD25C330074CB35 /* RNFetchBlob.m */; };
14 14
 		A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */ = {isa = PBXBuildFile; fileRef = A15C30111CD25C330074CB35 /* RNFetchBlob.h */; };
15
+		A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */; };
15 16
 /* End PBXBuildFile section */
16 17
 
17 18
 /* Begin PBXCopyFilesBuildPhase section */
@@ -36,6 +37,8 @@
36 37
 		A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFetchBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
37 38
 		A15C30111CD25C330074CB35 /* RNFetchBlob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNFetchBlob.h; path = RNFetchBlob/RNFetchBlob.h; sourceTree = "<group>"; };
38 39
 		A15C30131CD25C330074CB35 /* RNFetchBlob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFetchBlob.m; path = RNFetchBlob/RNFetchBlob.m; sourceTree = "<group>"; };
40
+		A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobReqBuilder.h; sourceTree = "<group>"; };
41
+		A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobReqBuilder.m; sourceTree = "<group>"; };
39 42
 /* End PBXFileReference section */
40 43
 
41 44
 /* Begin PBXFrameworksBuildPhase section */
@@ -59,6 +62,8 @@
59 62
 		A15C30051CD25C330074CB35 = {
60 63
 			isa = PBXGroup;
61 64
 			children = (
65
+				A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */,
66
+				A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */,
62 67
 				A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */,
63 68
 				A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */,
64 69
 				A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */,
@@ -140,6 +145,7 @@
140 145
 				A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */,
141 146
 				A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */,
142 147
 				A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */,
148
+				A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */,
143 149
 				A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */,
144 150
 			);
145 151
 			runOnlyForDeploymentPostprocessing = 0;

+ 37
- 121
src/ios/RNFetchBlob/RNFetchBlob.m Ver arquivo

@@ -12,6 +12,7 @@
12 12
 #import "RNFetchBlobFS.h"
13 13
 #import "RNFetchBlobNetwork.h"
14 14
 #import "RNFetchBlobConst.h"
15
+#import "RNFetchBlobReqBuilder.h"
15 16
 
16 17
 
17 18
 ////////////////////////////////////////
@@ -61,77 +62,14 @@ RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options
61 62
                   form:(NSArray *)form
62 63
                   callback:(RCTResponseSenderBlock)callback)
63 64
 {
64
-    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
65
-    // send request
66
-    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
67
-                                    initWithURL:[NSURL
68
-                                                 URLWithString: encodedUrl]];
69 65
     
70
-    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[ RNFetchBlobNetwork normalizeHeaders:headers]];
71
-    
72
-    
73
-    NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
74
-    NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
75
-    
76
-    // generate boundary
77
-    NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
78
-    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
79
-        NSMutableData * postData = [[NSMutableData alloc] init];
80
-        // if method is POST or PUT, convert data string format
81
-        if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
82
-            
83
-            // combine multipart/form-data body
84
-            for(id field in form) {
85
-                NSString * name = [field valueForKey:@"name"];
86
-                NSString * content = [field valueForKey:@"data"];
87
-                // field is a text field
88
-                if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {
89
-                    [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
90
-                    [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]];
91
-                    [postData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
92
-                    [postData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]];
93
-                }
94
-                // field contains a file
95
-                else {
96
-                    NSMutableData * blobData;
97
-                    if(content != nil) {
98
-                        if([content hasPrefix:self.filePathPrefix]) {
99
-                            NSString * orgPath = [content substringFromIndex:[self.filePathPrefix length]];
100
-                            blobData = [[NSData alloc] initWithContentsOfFile:orgPath];
101
-                        }
102
-                        else
103
-                            blobData = [[NSData alloc] initWithBase64EncodedString:content options:0];
104
-                    }
105
-                    NSString * filename = [field valueForKey:@"filename"];
106
-                    [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
107
-                    [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
108
-                    [postData appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
109
-                    [postData appendData:blobData];
110
-                    [postData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
111
-                }
112
-                
113
-            }
114
-            
115
-            // close form data
116
-            [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
117
-            [request setHTTPBody:postData];
118
-            // set content-length
119
-            [mheaders setValue:[NSString stringWithFormat:@"%d",[postData length]] forKey:@"Content-Length"];
120
-            [mheaders setValue:[NSString stringWithFormat:@"100-continue",[postData length]] forKey:@"Expect"];
121
-            // appaned boundary to content-type
122
-            [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"];
123
-            
124
-        }
125
-        
126
-        [request setHTTPMethod: method];
127
-        [request setAllHTTPHeaderFields:mheaders];
128
-        
129
-        
66
+    [RNFetchBlobReqBuilder buildMultipartRequest:options taskId:taskId method:method url:url headers:headers form:form onComplete:^(NSURLRequest *req, long bodyLength) {
130 67
         // send HTTP request
131 68
         RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
132
-        [utils sendRequest:options bridge:self.bridge taskId:taskId withRequest:request callback:callback];
69
+        [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
133 70
         utils = nil;
134
-    });
71
+    }];
72
+    
135 73
 }
136 74
 
137 75
 // Fetch blob data request
@@ -142,45 +80,13 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
142 80
                   headers:(NSDictionary *)headers
143 81
                   body:(NSString *)body callback:(RCTResponseSenderBlock)callback)
144 82
 {
145
-    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
146
-    // send request
147
-    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
148
-                                    initWithURL:[NSURL
149
-                                                 URLWithString: encodedUrl]];
150
-    
151
-    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
152
-    // move heavy task to another thread
153
-    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
154
-        NSMutableData * blobData;
155
-        // if method is POST or PUT, convert data string format
156
-        if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
157
-            // generate octet-stream body
158
-            if(body != nil) {
159
-                
160
-                // when body is a string contains file path prefix, try load file from the path
161
-                if([body hasPrefix:self.filePathPrefix]) {
162
-                    NSString * orgPath = [body substringFromIndex:[self.filePathPrefix length]];
163
-                    [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]];
164
-                }
165
-                // otherwise convert it as BASE64 data string
166
-                else {
167
-                    blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
168
-                    [request setHTTPBody:blobData];
169
-                }
170
-                
171
-                [mheaders setValue:@"application/octet-stream" forKey:@"content-type"];
172
-                
173
-            }
174
-        }
175
-        
176
-        [request setHTTPMethod: method];
177
-        [request setAllHTTPHeaderFields:mheaders];
178
-        
83
+    [RNFetchBlobReqBuilder buildOctetRequest:options taskId:taskId method:method url:url headers:headers body:body onComplete:^(NSURLRequest *req, long bodyLength) {
179 84
         // send HTTP request
180 85
         RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
181
-        [utils sendRequest:options bridge:self.bridge taskId:taskId withRequest:request callback:callback];
86
+        [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
182 87
         utils = nil;
183
-    });
88
+    }];
89
+    
184 90
 }
185 91
 
186 92
 RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) {
@@ -236,16 +142,13 @@ RCT_EXPORT_METHOD(exists:(NSString *)path callback:(RCTResponseSenderBlock)callb
236 142
     
237 143
 }
238 144
 
239
-RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize) {
240
-    RNFetchBlobFS *fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
241
-    if(bufferSize == nil) {
242
-        if([[encoding lowercaseString] isEqualToString:@"base64"])
243
-            bufferSize = 4095;
244
-        else
245
-            bufferSize = 4096;
246
-    }
247
-    [fileStream readWithPath:path useEncoding:encoding bufferSize:bufferSize];
248
-}
145
+RCT_EXPORT_METHOD(writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
146
+    [RNFetchBlobFS writeFile:path encoding:encoding data:data append:append resolver:resolve rejecter:reject];
147
+})
148
+
149
+RCT_EXPORT_METHOD(writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
150
+    [RNFetchBlobFS writeFileArray:path data:data append:append resolver:resolve rejecter:reject];
151
+})
249 152
 
250 153
 RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding appendData:(BOOL)append callback:(RCTResponseSenderBlock)callback) {
251 154
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
@@ -335,6 +238,9 @@ RCT_EXPORT_METHOD(stat:(NSString *)path callback:(RCTResponseSenderBlock) callba
335 238
     BOOL exist = nil;
336 239
     BOOL isDir = nil;
337 240
     NSError * error = nil;
241
+    
242
+    path = [RNFetchBlobFS getPathOfAsset:path];
243
+    
338 244
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
339 245
     if(exist == NO) {
340 246
         callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not exist", path]]);
@@ -353,6 +259,9 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
353 259
     NSFileManager* fm = [NSFileManager defaultManager];
354 260
     BOOL exist = nil;
355 261
     BOOL isDir = nil;
262
+    
263
+    path = [RNFetchBlobFS getPathOfAsset:path];
264
+    
356 265
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
357 266
     if(exist == NO) {
358 267
         callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not exist", path]]);
@@ -381,6 +290,7 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
381 290
 
382 291
 RCT_EXPORT_METHOD(cp:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
383 292
     NSError * error = nil;
293
+    path = [RNFetchBlobFS getPathOfAsset:path];
384 294
     BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
385 295
     
386 296
     if(error == nil)
@@ -412,16 +322,22 @@ RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callb
412 322
 }
413 323
 
414 324
 RCT_EXPORT_METHOD(readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
415
-    [RNFetchBlobFS readFile:path encoding:encoding resolver:resolve rejecter:reject];
416
-})
417
-
418
-RCT_EXPORT_METHOD(writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
419
-    [RNFetchBlobFS writeFile:path encoding:encoding data:data append:append resolver:resolve rejecter:reject];
325
+    
326
+    [RNFetchBlobFS readFile:path encoding:encoding resolver:resolve rejecter:reject onComplete:nil];
420 327
 })
421 328
 
422
-RCT_EXPORT_METHOD(writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
423
-    [RNFetchBlobFS writeFileArray:path data:data append:append resolver:resolve rejecter:reject];
424
-})
329
+RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize) {
330
+    
331
+    RNFetchBlobFS *fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
332
+    if(bufferSize == nil) {
333
+        if([[encoding lowercaseString] isEqualToString:@"base64"])
334
+            bufferSize = 4095;
335
+        else
336
+            bufferSize = 4096;
337
+    }
338
+    // read asset stream
339
+    [fileStream readWithPath:path useEncoding:encoding bufferSize:bufferSize];
340
+}
425 341
 
426 342
 RCT_EXPORT_METHOD(getEnvironmentDirs:(RCTResponseSenderBlock) callback) {
427 343
     

+ 3
- 0
src/ios/RNFetchBlobConst.h Ver arquivo

@@ -16,7 +16,10 @@ extern NSString *const MSG_EVENT;
16 16
 extern NSString *const MSG_EVENT_LOG;
17 17
 extern NSString *const MSG_EVENT_WARN;
18 18
 extern NSString *const MSG_EVENT_ERROR;
19
+
19 20
 extern NSString *const FILE_PREFIX;
21
+extern NSString *const ASSET_PREFIX;
22
+extern NSString *const AL_PREFIX;
20 23
 
21 24
 // config
22 25
 extern NSString *const CONFIG_USE_TEMP;

+ 4
- 0
src/ios/RNFetchBlobConst.m Ver arquivo

@@ -8,6 +8,10 @@
8 8
 #import "RNFetchBlobConst.h"
9 9
 
10 10
 extern NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
11
+extern NSString *const ASSET_PREFIX = @"bundle-assets://";
12
+extern NSString *const AL_PREFIX = @"assets-library://";
13
+
14
+
11 15
 
12 16
 // fetch configs
13 17
 extern NSString *const CONFIG_USE_TEMP = @"fileCache";

+ 6
- 1
src/ios/RNFetchBlobFS.h Ver arquivo

@@ -11,6 +11,7 @@
11 11
 
12 12
 #import <Foundation/Foundation.h>
13 13
 #import "RCTBridgeModule.h"
14
+@import AssetsLibrary;
14 15
 
15 16
 @interface RNFetchBlobFS : NSObject <NSStreamDelegate>  {
16 17
     NSOutputStream * outStream;
@@ -42,6 +43,8 @@
42 43
 + (NSString *) getCacheDir;
43 44
 + (NSString *) getDocumentDir;
44 45
 + (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext;
46
++ (NSString *) getPathOfAsset:(NSString *)assetURI;
47
++ (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, ALAssetRepresentation *asset)) onComplete;
45 48
 
46 49
 // fs methods
47 50
 + (RNFetchBlobFS *) getFileStreams;
@@ -50,7 +53,8 @@
50 53
 + (BOOL) exists:(NSString *) path;
51 54
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
52 55
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
53
-+ (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
56
++ (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject onComplete:(void (^)(NSData * content))onComplete;
57
++ (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
54 58
 
55 59
 // constructor
56 60
 - (id) init;
@@ -61,6 +65,7 @@
61 65
 - (void) openWithDestination;
62 66
 - (void) openWithId;
63 67
 - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)encode appendData:(BOOL)append;
68
+- (void) startAssetReadStream:(NSData *)assetUrl;
64 69
 
65 70
 // file stream write data
66 71
 - (void)write:(NSData *) chunk;

+ 185
- 41
src/ios/RNFetchBlobFS.m Ver arquivo

@@ -14,6 +14,8 @@
14 14
 #import "RCTEventDispatcher.h"
15 15
 #import "RNFetchBlobFS.h"
16 16
 #import "RNFetchBlobConst.h"
17
+@import AssetsLibrary;
18
+
17 19
 
18 20
 NSMutableDictionary *fileStreams = nil;
19 21
 
@@ -49,6 +51,18 @@ NSMutableDictionary *fileStreams = nil;
49 51
     [fileStreams setValue:instance forKey:uuid];
50 52
 }
51 53
 
54
++(NSString *) getPathOfAsset:(NSString *)assetURI
55
+{
56
+    // get file path of an app asset
57
+    if([assetURI hasPrefix:ASSET_PREFIX])
58
+    {
59
+        assetURI = [assetURI stringByReplacingOccurrencesOfString:ASSET_PREFIX withString:@""];
60
+        assetURI = [[NSBundle mainBundle] pathForResource: [assetURI stringByDeletingPathExtension]
61
+                                               ofType: [assetURI pathExtension]];
62
+    }
63
+    return assetURI;
64
+}
65
+
52 66
 + (NSString *) getCacheDir {
53 67
     return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
54 68
 }
@@ -84,6 +98,93 @@ NSMutableDictionary *fileStreams = nil;
84 98
     return tempPath;
85 99
 }
86 100
 
101
+- (void) startAssetReadStream:(NSString *)assetUrl
102
+{
103
+    ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
104
+    {
105
+        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
106
+        dispatch_async(queue, ^ {
107
+            NSString * streamEventCode = [NSString stringWithFormat:@"RNFetchBlobStream+%@", self.path];
108
+            ALAssetRepresentation *rep = [myasset defaultRepresentation];
109
+            Byte *buffer = (Byte*)malloc(self.bufferSize);
110
+            NSUInteger cursor = [rep getBytes:buffer fromOffset:0 length:self.bufferSize error:nil];
111
+            while(cursor > 0)
112
+            {
113
+                cursor += [rep getBytes:buffer fromOffset:cursor length:self.bufferSize error:nil];
114
+                NSData * chunkData = [NSData dataWithBytes:buffer length:self.bufferSize];
115
+                NSString * encodedChunk = @"";
116
+                // emit data
117
+                if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
118
+                    encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
119
+                }
120
+                // when encoding is ASCII, send byte array data
121
+                else if ( [[self.encoding lowercaseString] isEqualToString:@"ascii"] ) {
122
+                    // RCTBridge only emits string data, so we have to create JSON byte array string
123
+                    NSMutableArray * asciiArray = [NSMutableArray array];
124
+                    unsigned char *bytePtr;
125
+                    if (chunkData.length > 0)
126
+                    {
127
+                        bytePtr = (unsigned char *)[chunkData bytes];
128
+                        NSInteger byteLen = chunkData.length/sizeof(uint8_t);
129
+                        for (int i = 0; i < byteLen; i++)
130
+                        {
131
+                            [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
132
+                        }
133
+                    }
134
+                    
135
+                    [self.bridge.eventDispatcher
136
+                     sendDeviceEventWithName:streamEventCode
137
+                     body: @{
138
+                             @"event": FS_EVENT_DATA,
139
+                             @"detail": asciiArray
140
+                             }
141
+                     ];
142
+                    return;
143
+                }
144
+                // convert byte array to base64 data chunks
145
+                else if ( [[self.encoding lowercaseString] isEqualToString:@"base64"] ) {
146
+                    encodedChunk = [chunkData base64EncodedStringWithOptions:0];
147
+                }
148
+                // unknown encoding, send error event
149
+                else {
150
+                    [self.bridge.eventDispatcher
151
+                     sendDeviceEventWithName:streamEventCode
152
+                     body:@{
153
+                            @"event": FS_EVENT_ERROR,
154
+                            @"detail": @"unrecognized encoding"
155
+                            }
156
+                     ];
157
+                    return;
158
+                }
159
+                
160
+                [self.bridge.eventDispatcher
161
+                 sendDeviceEventWithName:streamEventCode
162
+                 body:@{
163
+                        @"event": FS_EVENT_DATA,
164
+                        @"detail": encodedChunk
165
+                        }
166
+                 ];
167
+            }
168
+            free(buffer);
169
+        });
170
+        
171
+    };
172
+    
173
+    ALAssetsLibraryAccessFailureBlock failureblock  = ^(NSError *error)
174
+    {
175
+        
176
+    };
177
+    
178
+    if(assetUrl && [assetUrl length])
179
+    {
180
+        NSURL *asseturl = [NSURL URLWithString:assetUrl];
181
+        ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
182
+        [assetslibrary assetForURL:asseturl
183
+                       resultBlock:resultblock
184
+                      failureBlock:failureblock];
185
+    }
186
+}
187
+
87 188
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
88 189
     @try {
89 190
         NSFileManager * fm = [NSFileManager defaultManager];
@@ -172,38 +273,65 @@ NSMutableDictionary *fileStreams = nil;
172 273
     }
173 274
 }
174 275
 
175
-+ (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
276
++ (void) readFile:(NSString *)path encoding:(NSString *)encoding
277
+         resolver:(RCTPromiseResolveBlock)resolve
278
+         rejecter:(RCTPromiseRejectBlock)reject
279
+       onComplete:(void (^)(NSData * content))onComplete
280
+{
176 281
     @try
177 282
     {
178
-        NSFileManager * fm = [NSFileManager defaultManager];
179
-        NSError *err = nil;
180
-        BOOL exists = [fm fileExistsAtPath:path];
181
-        if(!exists) {
182
-            @throw @"RNFetchBlobFS readFile error", @"file not exists", path;
183
-            return;
184
-        }
185
-        if([[encoding lowercaseString] isEqualToString:@"utf8"]) {
186
-            NSString * utf8Result = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err];
187
-            resolve(utf8Result);
188
-        }
189
-        else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
190
-            NSData * fileData = [NSData dataWithContentsOfFile:path];
191
-            resolve([fileData base64EncodedStringWithOptions:0]);
192
-        }
193
-        else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
194
-            NSData * resultData = [NSData dataWithContentsOfFile:path];
195
-            NSMutableArray * resultArray = [NSMutableArray array];
196
-            char * bytes = [resultData bytes];
197
-            for(int i=0;i<[resultData length];i++) {
198
-                [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
283
+        [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
284
+            NSData * fileContent;
285
+            NSError * err;
286
+            Byte * buffer;
287
+            if(asset != nil)
288
+            {
289
+                buffer = malloc(asset.size);
290
+                [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
291
+                if(err != nil)
292
+                {
293
+                    reject(@"RNFetchBlobFS readFile error", @"failed to read asset", [err localizedDescription]);
294
+                    return;
295
+                }
296
+                fileContent = [NSData dataWithBytes:buffer length:asset.size];
297
+                free(buffer);
199 298
             }
200
-            resolve(resultArray);
201
-        }
202
-
299
+            else
300
+            {
301
+                BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
302
+                if(!exists) {
303
+                    reject(@"RNFetchBlobFS readFile error", @"file not exists", path);
304
+                    return;
305
+                }
306
+                fileContent = [NSData dataWithContentsOfFile:path];
307
+                
308
+            }
309
+            if(onComplete != nil)
310
+                onComplete(fileContent);
311
+            
312
+            if([[encoding lowercaseString] isEqualToString:@"utf8"]) {
313
+                if(resolve != nil)
314
+                    resolve([[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding]);
315
+            }
316
+            else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
317
+                if(resolve != nil)
318
+                    resolve([fileContent base64EncodedStringWithOptions:0]);
319
+            }
320
+            else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
321
+                NSMutableArray * resultArray = [NSMutableArray array];
322
+                char * bytes = [fileContent bytes];
323
+                for(int i=0;i<[fileContent length];i++) {
324
+                    [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
325
+                }
326
+                if(resolve != nil)
327
+                    resolve(resultArray);
328
+            }
329
+        }];
203 330
     }
204 331
     @catch(NSException * e)
205 332
     {
206
-        reject(@"RNFetchBlobFS readFile error", @"error", [e description]);
333
+        if(reject != nil)
334
+            reject(@"RNFetchBlobFS readFile error", @"error", [e description]);
207 335
     }
208 336
 }
209 337
 
@@ -325,7 +453,16 @@ NSMutableDictionary *fileStreams = nil;
325 453
     self.encoding = encoding;
326 454
     self.path = path;
327 455
     self.bufferSize = bufferSize;
328
-
456
+    
457
+    if([path hasPrefix:AL_PREFIX])
458
+    {
459
+        [self startAssetReadStream:path];
460
+        return;
461
+    }
462
+    
463
+    // normalize file path
464
+    path = [[self class] getPathOfAsset:path];
465
+    
329 466
     // NSStream needs a runloop so let's create a run loop for it
330 467
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
331 468
     // start NSStream is a runloop
@@ -334,7 +471,7 @@ NSMutableDictionary *fileStreams = nil;
334 471
                             forMode:NSDefaultRunLoopMode];
335 472
         [inStream open];
336 473
         [[NSRunLoop currentRunLoop] run];
337
-
474
+        
338 475
     });
339 476
 }
340 477
 
@@ -349,19 +486,6 @@ NSMutableDictionary *fileStreams = nil;
349 486
 
350 487
 }
351 488
 
352
-void runOnMainQueueWithoutDeadlocking(void (^block)(void))
353
-{
354
-    if ([NSThread isMainThread])
355
-    {
356
-        block();
357
-    }
358
-    else
359
-    {
360
-        dispatch_sync(dispatch_get_main_queue(), block);
361
-    }
362
-}
363
-
364
-
365 489
 #pragma mark RNFetchBlobFS read stream delegate
366 490
 
367 491
 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
@@ -475,4 +599,24 @@ void runOnMainQueueWithoutDeadlocking(void (^block)(void))
475 599
 
476 600
 }
477 601
 
602
++ (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, ALAssetRepresentation *asset)) onComplete
603
+{
604
+    if([uri hasPrefix:AL_PREFIX])
605
+    {
606
+        NSURL *asseturl = [NSURL URLWithString:uri];
607
+        ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
608
+        [assetslibrary assetForURL:asseturl
609
+                       resultBlock:^(ALAsset *asset) {
610
+                           onComplete(nil, [asset defaultRepresentation]);
611
+                       }
612
+                      failureBlock:^(NSError *error) {
613
+                          onComplete(nil, nil);
614
+                      }];
615
+    }
616
+    else
617
+    {
618
+        onComplete([[self class] getPathOfAsset:uri], nil);
619
+    }
620
+}
621
+
478 622
 @end

+ 2
- 1
src/ios/RNFetchBlobNetwork.h Ver arquivo

@@ -11,6 +11,7 @@
11 11
 
12 12
 #import <Foundation/Foundation.h>
13 13
 #import "RCTBridgeModule.h"
14
+#import "RNFetchBlobFS.h"
14 15
 
15 16
 typedef void(^CompletionHander)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error);
16 17
 typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse * _Nullable response, NSError * _Nullable error);
@@ -34,7 +35,7 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse
34 35
 - (void) sendRequest;
35 36
 
36 37
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
37
-- (void) sendRequest:(NSDictionary  * _Nullable )options bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
38
+- (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
38 39
 
39 40
 
40 41
 @end

+ 9
- 3
src/ios/RNFetchBlobNetwork.m Ver arquivo

@@ -69,7 +69,12 @@ NSOperationQueue *taskQueue;
69 69
 }
70 70
 
71 71
 // send HTTP request
72
-- (void) sendRequest:(NSDictionary  * _Nullable )options bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback
72
+- (void) sendRequest:(NSDictionary  * _Nullable )options
73
+       contentLength:(long) contentLength
74
+              bridge:(RCTBridge * _Nullable)bridgeRef
75
+              taskId:(NSString * _Nullable)taskId
76
+         withRequest:(NSURLRequest * _Nullable)req
77
+            callback:(_Nullable RCTResponseSenderBlock) callback
73 78
 {
74 79
     self.taskId = taskId;
75 80
     self.respData = [[NSMutableData alloc] initWithLength:0];
@@ -83,7 +88,7 @@ NSOperationQueue *taskQueue;
83 88
     NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
84 89
     NSURLSession * session;
85 90
     
86
-    bodyLength = [[req HTTPBody] length];
91
+    bodyLength = contentLength;
87 92
     
88 93
     // the session trust any SSL certification
89 94
 
@@ -104,6 +109,7 @@ NSOperationQueue *taskQueue;
104 109
         respFile = NO;
105 110
     }
106 111
     NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
112
+    
107 113
     [task resume];
108 114
     
109 115
     // network status indicator
@@ -191,7 +197,7 @@ NSOperationQueue *taskQueue;
191 197
      body:@{
192 198
             @"taskId": taskId,
193 199
             @"written": [NSString stringWithFormat:@"%d", totalBytesWritten],
194
-            @"total": [NSString stringWithFormat:@"%d", totalBytesExpectedToWrite]
200
+            @"total": [NSString stringWithFormat:@"%d", bodyLength]
195 201
             }
196 202
      ];
197 203
 }

+ 34
- 0
src/ios/RNFetchBlobReqBuilder.h Ver arquivo

@@ -0,0 +1,34 @@
1
+//
2
+//  RNFetchBlobReqBuilder.h
3
+//  RNFetchBlob
4
+//
5
+//  Created by Ben Hsieh on 2016/7/9.
6
+//  Copyright © 2016年 wkh237. All rights reserved.
7
+//
8
+
9
+#ifndef RNFetchBlobReqBuilder_h
10
+#define RNFetchBlobReqBuilder_h
11
+
12
+#import <Foundation/Foundation.h>
13
+
14
+@interface RNFetchBlobReqBuilder : NSObject;
15
+
16
++(void) buildMultipartRequest:(NSDictionary *)options
17
+                       taskId:(NSString *)taskId
18
+                       method:(NSString *)method
19
+                          url:(NSString *)url
20
+                      headers:(NSDictionary *)headers
21
+                         form:(NSArray *)form
22
+                   onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete;
23
+
24
++(void) buildOctetRequest:(NSDictionary *)options
25
+                   taskId:(NSString *)taskId
26
+                   method:(NSString *)method
27
+                      url:(NSString *)url
28
+                  headers:(NSDictionary *)headers
29
+                     body:(NSString *)body
30
+               onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete;
31
+
32
+@end
33
+
34
+#endif /* RNFetchBlobReqBuilder_h */

+ 199
- 0
src/ios/RNFetchBlobReqBuilder.m Ver arquivo

@@ -0,0 +1,199 @@
1
+//
2
+//  RNFetchBlobReqBuilder.m
3
+//  RNFetchBlob
4
+//
5
+//  Created by Ben Hsieh on 2016/7/9.
6
+//  Copyright © 2016年 wkh237. All rights reserved.
7
+//
8
+
9
+#import <Foundation/Foundation.h>
10
+#import "RNFetchBlobReqBuilder.h"
11
+#import "RNFetchBlobNetwork.h"
12
+#import "RNFetchBlobConst.h"
13
+#import "RNFetchBlobFS.h"
14
+
15
+@interface RNFetchBlobReqBuilder()
16
+{
17
+    
18
+}
19
+@end
20
+
21
+@implementation RNFetchBlobReqBuilder
22
+
23
+
24
+// Fetch blob data request
25
++(void) buildMultipartRequest:(NSDictionary *)options
26
+                       taskId:(NSString *)taskId
27
+                       method:(NSString *)method
28
+                          url:(NSString *)url
29
+                      headers:(NSDictionary *)headers
30
+                         form:(NSArray *)form
31
+                   onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete
32
+{
33
+    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
34
+    // send request
35
+    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString: encodedUrl]];
36
+    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
37
+    NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
38
+    NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
39
+    
40
+    // generate boundary
41
+    NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
42
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
43
+        NSMutableData * postData = [[NSMutableData alloc] init];
44
+        // combine multipart/form-data body
45
+        [[self class] buildFormBody:form boundary:boundary onComplete:^(NSData *formData) {
46
+            if(formData != nil) {
47
+                [postData appendData:formData];
48
+                // close form data
49
+                [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
50
+                [request setHTTPBody:postData];
51
+            }
52
+            // set content-length
53
+            [mheaders setValue:[NSString stringWithFormat:@"%d",[postData length]] forKey:@"Content-Length"];
54
+            [mheaders setValue:[NSString stringWithFormat:@"100-continue",[postData length]] forKey:@"Expect"];
55
+            // appaned boundary to content-type
56
+            [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"];
57
+            [request setHTTPMethod: method];
58
+            [request setAllHTTPHeaderFields:mheaders];
59
+            onComplete(request, [formData length]);
60
+        }];
61
+        
62
+    });
63
+}
64
+
65
+// Fetch blob data request
66
++(void) buildOctetRequest:(NSDictionary *)options
67
+                   taskId:(NSString *)taskId
68
+                   method:(NSString *)method
69
+                      url:(NSString *)url
70
+                  headers:(NSDictionary *)headers
71
+                     body:(NSString *)body
72
+               onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete
73
+{
74
+    NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
75
+    // send request
76
+    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
77
+                                    initWithURL:[NSURL
78
+                                                 URLWithString: encodedUrl]];
79
+    
80
+    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
81
+    // move heavy task to another thread
82
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
83
+        NSMutableData * blobData;
84
+        long size = -1;
85
+        // if method is POST or PUT, convert data string format
86
+        if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
87
+            // generate octet-stream body
88
+            if(body != nil) {
89
+                
90
+                // when body is a string contains file path prefix, try load file from the path
91
+                if([body hasPrefix:FILE_PREFIX]) {
92
+                    NSString * orgPath = [body substringFromIndex:[FILE_PREFIX length]];
93
+                    orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
94
+                    if([orgPath hasPrefix:AL_PREFIX])
95
+                    {
96
+                        [RNFetchBlobFS readFile:orgPath encoding:@"utf8" resolver:nil rejecter:nil onComplete:^(NSData *content) {
97
+                            [request setHTTPBody:content];
98
+                            [mheaders setValue:@"application/octet-stream" forKey:@"content-type"];
99
+                            [request setHTTPMethod: method];
100
+                            [request setAllHTTPHeaderFields:mheaders];
101
+                            onComplete(request, [content length]);
102
+                        }];
103
+                        return;
104
+                    }
105
+                    size = [[[NSFileManager defaultManager] attributesOfItemAtPath:orgPath error:nil] fileSize];
106
+                    [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]];
107
+                }
108
+                // otherwise convert it as BASE64 data string
109
+                else {
110
+                    blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
111
+                    [request setHTTPBody:blobData];
112
+                }
113
+                
114
+                [mheaders setValue:@"application/octet-stream" forKey:@"content-type"];
115
+                
116
+            }
117
+        }
118
+        
119
+        [request setHTTPMethod: method];
120
+        [request setAllHTTPHeaderFields:mheaders];
121
+        
122
+        onComplete(request, size);
123
+    });
124
+}
125
+
126
++(void) buildFormBody:(NSArray *)form boundary:(NSString *)boundary onComplete:(void(^)(NSData * formData))onComplete
127
+{
128
+    NSMutableData * formData = [[NSMutableData alloc] init];
129
+    if(form == nil)
130
+        onComplete(nil);
131
+    else
132
+    {
133
+        __block int i = 0;
134
+        __block int count = [form count];
135
+        void __block (^getFieldData)(id field) = ^(id field)
136
+        {
137
+            NSString * name = [field valueForKey:@"name"];
138
+            NSString * content = [field valueForKey:@"data"];
139
+            NSString * contentType = [field valueForKey:@"type"];
140
+            contentType = contentType == nil ? @"application/octet-stream" : contentType;
141
+            // field is a text field
142
+            if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {
143
+                [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
144
+                [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]];
145
+                [formData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
146
+                [formData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]];
147
+            }
148
+            // field contains a file
149
+            else {
150
+                NSMutableData * blobData;
151
+                if(content != nil)
152
+                {
153
+                    // append data from file asynchronously
154
+                    if([content hasPrefix:FILE_PREFIX])
155
+                    {
156
+                        NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
157
+                        orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
158
+                        [RNFetchBlobFS readFile:orgPath encoding:@"utf8" resolver:nil rejecter:nil onComplete:^(NSData *content) {
159
+                            NSString * filename = [field valueForKey:@"filename"];
160
+                            [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
161
+                            [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
162
+                            [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]];
163
+                            [formData appendData:content];
164
+                            [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
165
+                            i++;
166
+                            if(i < count)
167
+                            {
168
+                                getFieldData([form objectAtIndex:i]);
169
+                            }
170
+                            else
171
+                                onComplete(formData);
172
+                        }];
173
+                        return ;
174
+                    }
175
+                    else
176
+                        blobData = [[NSData alloc] initWithBase64EncodedString:content options:0];
177
+                }
178
+                NSString * filename = [field valueForKey:@"filename"];
179
+                [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
180
+                [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
181
+                [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]];
182
+                [formData appendData:blobData];
183
+                [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
184
+            }
185
+            i++;
186
+            if(i < count)
187
+            {
188
+                getFieldData([form objectAtIndex:i]);
189
+            }
190
+            else
191
+                onComplete(formData);
192
+            
193
+        };
194
+        getFieldData([form objectAtIndex:i]);
195
+    }
196
+}
197
+
198
+
199
+@end

+ 1
- 1
src/package.json Ver arquivo

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "react-native-fetch-blob",
3
-  "version": "0.6.1",
3
+  "version": "0.6.2",
4 4
   "description": "A module provides upload, download, and files access API. Supports file stream read/write for process large files.",
5 5
   "main": "index.js",
6 6
   "scripts": {

+ 12
- 0
test-server/server.js Ver arquivo

@@ -84,6 +84,18 @@ app.delete('/hey', function(req, res) {
84 84
   res.send('man')
85 85
 })
86 86
 
87
+app.post('/mime', mimeCheck)
88
+app.put('/mime', mimeCheck)
89
+
90
+function mimeCheck(req, res) {
91
+  console.log(req.files)
92
+  var mimes = []
93
+  for(var i in req.files) {
94
+    mimes.push(req.files[i].mimetype)
95
+  }
96
+  res.send(mimes)
97
+}
98
+
87 99
 // handle multipart/form-data request
88 100
 app.post('/upload-form', formUpload)
89 101
 

+ 1
- 0
test.sh Ver arquivo

@@ -33,6 +33,7 @@ if [ "$#" -eq 1 ]; then
33 33
 fi
34 34
 # copy js files to test app folder
35 35
 cp -R test/ "${TEST_APP_PATH}/"
36
+node -e "var fs=require('fs'); var pkg = JSON.parse(fs.readFileSync('./RNFetchBlobTest/package.json')); pkg.rnpm = {assets : ['assets']}; fs.writeFileSync('./RNFetchBlobTest/package.json', JSON.stringify(pkg, null, 4));"
36 37
 
37 38
 # install module
38 39
 cd "${TEST_APP_PATH}"

+ 3
- 0
test/assets/test-asset1.json Ver arquivo

@@ -0,0 +1,3 @@
1
+{
2
+  "secret" : "asset#1"
3
+}

BIN
test/assets/test-asset2.png Ver arquivo


+ 1
- 0
test/index.test.js Ver arquivo

@@ -10,6 +10,7 @@ import {
10 10
   View,
11 11
   Platform,
12 12
   ScrollView,
13
+  CameraRoll,
13 14
   Image,
14 15
 } from 'react-native';
15 16
 

+ 174
- 0
test/react-native-testkit/animate-text.js Ver arquivo

@@ -0,0 +1,174 @@
1
+/**
2
+ * @author wkh237
3
+ * @version 0.1.1
4
+ */
5
+
6
+// @flow
7
+
8
+import React, { Component } from 'react';
9
+import {
10
+  Text,
11
+  View
12
+} from 'react-native';
13
+import Timer from 'react-timer-mixin';
14
+
15
+const HALF_RAD = Math.PI/2
16
+
17
+export default class AnimateNumber extends Component {
18
+
19
+  props : {
20
+    countBy? : ?number,
21
+    interval? : ?number,
22
+    steps? : ?number,
23
+    value : number,
24
+    timing : 'linear' | 'easeOut' | 'easeIn' | () => number,
25
+    formatter : () => {},
26
+    onProgress : () => {},
27
+    onFinish : () => {}
28
+  };
29
+
30
+  static defaultProps = {
31
+    interval : 14,
32
+    timing : 'linear',
33
+    steps : 45,
34
+    value : 0,
35
+    formatter : (val) => val,
36
+    onFinish : () => {}
37
+  };
38
+
39
+  static TimingFunctions = {
40
+
41
+    linear : (interval:number, progress:number):number => {
42
+      return interval
43
+    },
44
+
45
+    easeOut : (interval:number, progress:number):number => {
46
+      return interval * Math.sin(HALF_RAD*progress) * 5
47
+    },
48
+
49
+    easeIn : (interval:number, progress:number):number => {
50
+      return interval * Math.sin((HALF_RAD - HALF_RAD*progress)) * 5
51
+    },
52
+
53
+  };
54
+
55
+  state : {
56
+    value? : ?number,
57
+    displayValue? : ?number
58
+  };
59
+
60
+  /**
61
+   * Animation direction, true means positive, false means negative.
62
+   * @type {bool}
63
+   */
64
+  direction : bool;
65
+  /**
66
+   * Start value of last animation.
67
+   * @type {number}
68
+   */
69
+  startFrom : number;
70
+  /**
71
+  * End value of last animation.
72
+  * @type {number}
73
+   */
74
+  endWith : number;
75
+
76
+  constructor(props:any) {
77
+    super(props);
78
+    // default values of state and non-state variables
79
+    this.state = {
80
+      value : 0,
81
+      displayValue : 0
82
+    }
83
+    this.dirty = false;
84
+    this.startFrom = 0;
85
+    this.endWith = 0;
86
+  }
87
+
88
+  componentDidMount() {
89
+    this.startFrom = this.state.value
90
+    this.endWith = this.props.value
91
+    this.dirty = true
92
+    this.startAnimate()
93
+  }
94
+
95
+  componentWillUpdate(nextProps, nextState) {
96
+
97
+    // check if start an animation
98
+    if(this.props.value !== nextProps.value) {
99
+      this.startFrom = this.props.value
100
+      this.endWith = nextProps.value
101
+      this.dirty = true
102
+      this.startAnimate()
103
+      return
104
+    }
105
+    // Check if iterate animation frame
106
+    if(!this.dirty) {
107
+      return
108
+    }
109
+    if (this.direction === true) {
110
+      if(parseFloat(this.state.value) <= parseFloat(this.props.value)) {
111
+        this.startAnimate();
112
+      }
113
+    }
114
+    else if(this.direction === false){
115
+      if (parseFloat(this.state.value) >= parseFloat(this.props.value)) {
116
+        this.startAnimate();
117
+      }
118
+    }
119
+
120
+  }
121
+
122
+  render() {
123
+    return (
124
+      <Text {...this.props}>
125
+        {this.state.displayValue}
126
+      </Text>)
127
+  }
128
+
129
+  startAnimate() {
130
+
131
+    let progress = this.getAnimationProgress()
132
+
133
+    Timer.setTimeout(() => {
134
+
135
+      let value = (this.endWith - this.startFrom)/this.props.steps
136
+      if(this.props.countBy)
137
+        value = Math.sign(value)*Math.abs(this.props.countBy)
138
+      let total = parseFloat(this.state.value) + parseFloat(value)
139
+
140
+      this.direction = (value > 0)
141
+      // animation terminate conditions
142
+      if (((this.direction) ^ (total <= this.endWith)) === 1) {
143
+        this.dirty = false
144
+        total = this.endWith
145
+        this.props.onFinish(total, this.props.formatter(total))
146
+      }
147
+
148
+      if(this.props.onProgress)
149
+        this.props.onProgress(this.state.value, total)
150
+
151
+      this.setState({
152
+        value : total,
153
+        displayValue : this.props.formatter(total)
154
+      })
155
+
156
+    }, this.getTimingFunction(this.props.interval, progress))
157
+
158
+  }
159
+
160
+  getAnimationProgress():number {
161
+    return (this.state.value - this.startFrom) / (this.endWith - this.startFrom)
162
+  }
163
+
164
+  getTimingFunction(interval:number, progress:number) {
165
+    if(typeof this.props.timing === 'string') {
166
+      let fn = AnimateNumber.TimingFunctions[this.props.timing]
167
+      return fn(interval, progress)
168
+    } else if(typeof this.props.timing === 'function')
169
+      return this.props.timing(interval, progress)
170
+    else
171
+      return AnimateNumber.TimingFunctions['linear'](interval, progress)
172
+  }
173
+
174
+}

+ 59
- 20
test/react-native-testkit/components/reporter.js Ver arquivo

@@ -9,9 +9,11 @@ import {
9 9
   ListView,
10 10
   Image,
11 11
   TouchableOpacity,
12
+  Dimensions,
12 13
   RecyclerViewBackedScrollView,
13 14
 } from 'react-native';
14 15
 
16
+import AnimateNumber from '../animate-text.js'
15 17
 import Assert from './assert.js'
16 18
 import RNTEST from '../index.js'
17 19
 
@@ -22,7 +24,10 @@ export default class Reporter extends Component {
22 24
     this.tests = {
23 25
       common : []
24 26
     }
25
-    this.testGroups = ['common']
27
+    this.state = {
28
+      listHeight : 0
29
+    }
30
+    this.testGroups = ['summary','common']
26 31
     this.ds = null
27 32
     this.updateDataSource()
28 33
 
@@ -34,23 +39,61 @@ export default class Reporter extends Component {
34 39
 
35 40
   render() {
36 41
 
42
+    let tests = RNTEST.TestContext.getTests()
43
+
44
+    let passed = 0
45
+    let executed = 0
46
+    let count = 0
47
+    for(let i in tests) {
48
+      if(tests[i].status !== 'skipped')
49
+        count++
50
+      if(tests[i].status !== 'waiting' && tests[i].status !== 'skipped')
51
+        executed++
52
+        passed += tests[i].status === 'pass' ? 1 : 0
53
+    }
54
+    let percent = passed / count
55
+    let color = `rgb(${Math.floor((1-percent) *255)},${Math.floor(percent *192)}, 0)`
56
+
37 57
     return (
38
-      <ListView
39
-        style={styles.container}
40
-        dataSource={this.ds}
41
-        renderRow={this.renderTest.bind(this)}
42
-        renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
43
-        renderSectionHeader={(data, id) => {
44
-          return (
45
-            <View style={styles.sectionHeader}>
46
-              <Text style={styles.sectionText}>{id}</Text>
47
-            </View>
48
-          )
49
-        }}
50
-      />)
58
+      <View style={{flex : 1}}>
59
+        <View style={{margin : 20}} onLayout={(e) => {
60
+          this.setState({
61
+            headerHeight : e.nativeEvent.layout.height,
62
+            listHeight : Dimensions.get('window').height - e.nativeEvent.layout.height
63
+          })
64
+        }}>
65
+          <Text>{`${executed} tests executed`}</Text>
66
+          <Text>{`${passed} test cases passed`}</Text>
67
+          <Text>{`${count} test cases`}</Text>
68
+          <View style={{flexDirection : 'row', alignSelf : 'center', alignItems : 'flex-end'}}>
69
+            <AnimateNumber style={{
70
+              color,
71
+              fontSize : 100,
72
+              textAlign : 'right'
73
+            }}
74
+              value={Math.floor(passed / count*100)}
75
+              countBy={1}/>
76
+            <Text style={{color, fontSize : 30, textAlign : 'left'}} >{`%`}</Text>
77
+          </View>
78
+        </View>
79
+        <ListView
80
+          style={[styles.container]}
81
+          dataSource={this.ds}
82
+          renderRow={this.renderTest.bind(this)}
83
+          renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
84
+          renderSectionHeader={(data, id) => {
85
+            return (
86
+              <View style={styles.sectionHeader}>
87
+                <Text style={styles.sectionText}>{id}</Text>
88
+              </View>
89
+            )
90
+          }}
91
+        />
92
+      </View>)
51 93
   }
52 94
 
53
-  renderTest(t) {
95
+  renderTest(t, group) {
96
+
54 97
     let pass = true
55 98
     let foundActions = false
56 99
     let tests = RNTEST.TestContext.getTests()
@@ -75,9 +118,6 @@ export default class Reporter extends Component {
75 118
       t.status = 'waiting'
76 119
 
77 120
     return (
78
-      <TouchableOpacity onPress={()=>{
79
-          t.start(t.sn)
80
-        }}>
81 121
         <View key={'rn-test-' + t.desc} style={{
82 122
           borderBottomWidth : 1.5,
83 123
           borderColor : '#DDD',
@@ -92,8 +132,7 @@ export default class Reporter extends Component {
92 132
           <View key={t.desc + '-result'} style={{backgroundColor : '#F4F4F4'}}>
93 133
             {t.expand ? t.result : (t.status === 'pass' ? null : t.result)}
94 134
           </View>
95
-        </View>
96
-      </TouchableOpacity>)
135
+        </View>)
97 136
   }
98 137
 
99 138
   updateDataSource() {

+ 14
- 1
test/react-native-testkit/lib/test-context.js Ver arquivo

@@ -4,6 +4,7 @@ let tests: Array<TestCase> = []
4 4
 let RCTContext: ReactElement = null
5 5
 let props:any = {}
6 6
 let timeout = 30000
7
+let summary = {}
7 8
 
8 9
 export default class TestContext {
9 10
 
@@ -41,7 +42,7 @@ export default class TestContext {
41 42
       run : run === false ? false : true,
42 43
       result : null,
43 44
       asserts : [],
44
-      timeout : timeout || 3000,
45
+      timeout : timeout || 15000,
45 46
       expired : false,
46 47
       running : false,
47 48
       executed : false,
@@ -127,6 +128,7 @@ export default class TestContext {
127 128
           })
128 129
           resolve(...res)
129 130
         }
131
+        RCTContext.forceUpdate()
130 132
       }).catch((err) => {
131 133
         updateInternal({
132 134
           executed : true,
@@ -146,6 +148,17 @@ export default class TestContext {
146 148
   static update(i, ...data) {
147 149
     let test = tests[i]
148 150
     let result = test.result || []
151
+    // if new element have prop `uid`, we should replace it not appending it.
152
+    for(let i in data) {
153
+      if(data[i].props.uid) {
154
+        for(let j in result) {
155
+          if(result[j].uid === data[i].props.uid)
156
+          result[j] = data[i]
157
+          result.splice(j,1)
158
+          break
159
+        }
160
+      }
161
+    }
149 162
     Object.assign(test, {result : [...result, ...data]})
150 163
     RCTContext.forceUpdate()
151 164
   }

+ 39
- 5
test/test-0.5.1.js Ver arquivo

@@ -119,6 +119,7 @@ describe('Upload from file storage', (report, done) => {
119 119
 })
120 120
 
121 121
 describe('Upload multipart data with file from storage', (report, done) => {
122
+  try{
122 123
     let filename = 'test-from-storage-img-'+Date.now()+'.png'
123 124
     RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/upload-form`, {
124 125
         'Content-Type' : 'multipart/form-data',
@@ -144,6 +145,9 @@ describe('Upload multipart data with file from storage', (report, done) => {
144 145
       </Info>)
145 146
       done()
146 147
     })
148
+  } catch(err) {
149
+    console.log(err)
150
+  }
147 151
 })
148 152
 
149 153
 describe('Upload and download at the same time', (report, done) => {
@@ -179,30 +183,61 @@ describe('Upload and download at the same time', (report, done) => {
179 183
         done()
180 184
       })
181 185
     })
182
-
183 186
 })
184 187
 
185 188
 RNTest.config({
186 189
   group : '0.5.1',
187 190
   run : true,
188 191
   expand : false,
189
-  timeout : 30000,
192
+  timeout : 600000,
190 193
 })('Upload and download large file', (report, done) => {
191 194
   let filename = '22mb-dummy-' + Date.now()
195
+  let begin = -1
196
+  let begin2 = -1
197
+  let deb = Date.now()
192 198
   RNFetchBlob.config({
193 199
     fileCache : true
194 200
   })
195 201
   .fetch('GET', `${TEST_SERVER_URL}/public/22mb-dummy`)
202
+  .progress((now, total) => {
203
+    if(begin === -1)
204
+      begin = Date.now()
205
+    if(Date.now() - deb < 1000)
206
+      return
207
+    deb = Date.now()
208
+    report(<Info uid="200" key="progress">
209
+      <Text>
210
+        {`download ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s)`}
211
+      </Text>
212
+    </Info>)
213
+  })
196 214
   .then((res) => {
197
-    return RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
215
+    deb = Date.now()
216
+    let promise =  RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
198 217
       Authorization : `Bearer ${DROPBOX_TOKEN}`,
199 218
       'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+filename+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
200 219
       'Content-Type' : 'application/octet-stream',
201 220
     }, RNFetchBlob.wrap(res.path()))
221
+    if(Platform.OS === 'ios')
222
+      promise.progress((now, total) => {
223
+        if(Date.now() - deb < 1000)
224
+          return
225
+        deb = Date.now()
226
+        if(begin2 === -1)
227
+          begin2 = Date.now()
228
+        let speed = Math.floor(now / (Date.now() - begin2))
229
+        report(<Info uid="100"  key="progress">
230
+          <Text>
231
+            {`upload ${now} / ${total} bytes (${speed} kb/s)`}
232
+            {` ${Math.floor((total-now)/speed/1000)} seconds left`}
233
+          </Text>
234
+        </Info>)
235
+      })
236
+    return promise
202 237
   })
203 238
   .then((res) => {
204 239
     report(<Assert
205
-      key="upload should success withou crashing app"
240
+      key="upload should success without crashing app"
206 241
       expect={filename}
207 242
       actual={res.json().name}/>)
208 243
     done()
@@ -302,5 +337,4 @@ describe('Session API CRUD test', (report, done) => {
302 337
       })
303 338
 
304 339
   })
305
-
306 340
 })

+ 203
- 0
test/test-0.6.2.js Ver arquivo

@@ -0,0 +1,203 @@
1
+import RNTest from './react-native-testkit/'
2
+import React from 'react'
3
+import RNFetchBlob from 'react-native-fetch-blob'
4
+
5
+import {
6
+  StyleSheet,
7
+  Text,
8
+  View,
9
+  ScrollView,
10
+  CameraRoll,
11
+  Platform,
12
+  Dimensions,
13
+  Image,
14
+} from 'react-native';
15
+
16
+const fs = RNFetchBlob.fs
17
+const { Assert, Comparer, Info, prop } = RNTest
18
+const describe = RNTest.config({
19
+  group : '0.6.2',
20
+  run : true,
21
+  expand : false,
22
+  timeout : 30000,
23
+})
24
+const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, DROPBOX_TOKEN, styles } = prop()
25
+const  dirs = RNFetchBlob.fs.dirs
26
+
27
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
28
+let photo = null
29
+
30
+describe('upload asset from camera roll', (report, done) => {
31
+  let imgName = `image-from-camera-roll-${Platform.OS}.jpg`
32
+  let tick = Date.now()
33
+  CameraRoll.getPhotos({first : 10})
34
+    .then((resp) => {
35
+      let url = resp.edges[0].node.image.uri
36
+      photo = url
37
+      return RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
38
+        Authorization : `Bearer ${DROPBOX_TOKEN}`,
39
+        'Dropbox-API-Arg': `{\"path\": \"/rn-upload/${imgName}\",\"mode\": \"add\",\"autorename\": false,\"mute\": false}`,
40
+        'Content-Type' : 'application/octet-stream',
41
+      }, RNFetchBlob.wrap(url))
42
+      .progress((now, total) => {
43
+        if(Date.now() - tick < 1000)
44
+        return
45
+        report(<Info key="progress" uid="pg1">
46
+          <Text>{`upload ${now} / ${total} ${Math.floor(now/total*100)}% `}</Text>
47
+        </Info>)
48
+      })
49
+    })
50
+    .then((resp) => {
51
+      resp = resp.json()
52
+      report(
53
+        <Assert key="confirm the file has been uploaded" expect={imgName} actual={resp.name}/>
54
+      )
55
+      done()
56
+    })
57
+})
58
+//
59
+// describe('Upload multipart data with file from CameraRoll', (report, done) => {
60
+//     let filename = 'test-from-storage-img-'+Date.now()+'.png'
61
+//     RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/upload-form`, {
62
+//         'Content-Type' : 'multipart/form-data',
63
+//       }, [
64
+//         { name : 'test-img', filename : filename, data: RNFetchBlob.wrap(photo)},
65
+//         { name : 'test-text', filename : 'test-text.txt', data: RNFetchBlob.base64.encode('hello.txt')},
66
+//         { name : 'field1', data : 'hello !!'},
67
+//         { name : 'field2', data : 'hello2 !!'}
68
+//       ])
69
+//     .then((resp) => {
70
+//       resp = resp.json()
71
+//       report(
72
+//         <Assert key="check posted form data #1" expect="hello !!" actual={resp.fields.field1}/>,
73
+//         <Assert key="check posted form data #2" expect="hello2 !!" actual={resp.fields.field2}/>,
74
+//       )
75
+//       return RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/${filename}`)
76
+//     })
77
+//     .then((resp) => {
78
+//       report(<Info key="uploaded image">
79
+//         <Image
80
+//           style={styles.image}
81
+//           source={{ uri : 'data:image/png;base64, '+ resp.base64()}}/>
82
+//       </Info>)
83
+//       done()
84
+//     })
85
+// })
86
+//
87
+//
88
+// describe('access assets from camera roll', (report, done) => {
89
+//   let photo = null
90
+//   CameraRoll.getPhotos({first : 10})
91
+//     .then((resp) => {
92
+//       photo = resp.edges[0].node.image.uri
93
+//       report(<Info key="items">
94
+//         <Text>{photo}</Text>
95
+//       </Info>)
96
+//       return fs.readFile(photo, 'base64')
97
+//     })
98
+//     .then((data) => {
99
+//       report(<Info key="asset image">
100
+//         <Image
101
+//           style={styles.image}
102
+//           source={{uri: `data:image/png;base64, ${data}`}}/>
103
+//       </Info>)
104
+//       done()
105
+//     })
106
+// })
107
+//
108
+// describe('read asset in app bundle',(report, done) => {
109
+//   let target = fs.asset('test-asset2.png')
110
+//   fs.readFile(target, 'base64')
111
+//   .then((data) => {
112
+//     report(<Info key="asset image">
113
+//       <Image
114
+//         style={styles.image}
115
+//         source={{uri: `data:image/png;base64, ${data}`}}/>
116
+//     </Info>)
117
+//     return fs.readFile(fs.asset('test-asset1.json'), 'utf8')
118
+//   })
119
+//   .then((resp) => {
120
+//     report(
121
+//       <Assert key="asset content verify"
122
+//         expect="asset#1"
123
+//         actual={JSON.parse(resp).secret}/>)
124
+//       done()
125
+//   })
126
+// })
127
+//
128
+// describe('stat assets in app', (report, done) => {
129
+//   fs.stat(fs.asset('test-asset2.png'))
130
+//     .then((data) => {
131
+//       report(<Info key="list of assets">
132
+//         <Text>{JSON.stringify(data)}</Text>
133
+//       </Info>)
134
+//       done()
135
+//     })
136
+// })
137
+//
138
+// describe('copy asset', (report, done) => {
139
+//   let dest = `${dirs.DocumentDir}/test-asset-1-${Date.now()}.json`
140
+//   fs.cp(fs.asset('test-asset1.json'), dest)
141
+//     .then(() => fs.readFile(dest, 'utf8'))
142
+//     .then((data) => {
143
+//       report(<Assert key="asset copied correctly"
144
+//         expect={'asset#1'}
145
+//         actual={JSON.parse(data).secret}/>)
146
+//       return fs.stat(fs.asset('test-asset1.json'))
147
+//     })
148
+//     .then((stat) => {
149
+//       report(<Assert key="file size check"
150
+//         expect={27}
151
+//         actual={Math.floor(stat.size)}/>,
152
+//       <Info key="dest file info">
153
+//         <Text>{JSON.stringify(stat)}</Text>
154
+//       </Info>)
155
+//       done()
156
+//     })
157
+// })
158
+//
159
+//
160
+// describe('upload file from assets',(report, done) => {
161
+//   let assetName = fs.asset('test-asset1.json')
162
+//   RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
163
+//     Authorization : `Bearer ${DROPBOX_TOKEN}`,
164
+//     'Dropbox-API-Arg': `{\"path\": \"/rn-upload/file-from-asset-${Platform.OS}.json\",\"mode\": \"add\",\"autorename\": false,\"mute\": false}`,
165
+//     'Content-Type' : 'application/octet-stream',
166
+//   }, RNFetchBlob.wrap(assetName))
167
+//   .then((resp) => {
168
+//     resp = resp.json()
169
+//     report(
170
+//       <Assert key="file name check"
171
+//         expect={`file-from-asset-${Platform.OS}.json`}
172
+//         actual={resp.name}/>)
173
+//     done()
174
+//   })
175
+// })
176
+
177
+describe('Check custom MIME type correctness',(report, done) => {
178
+  RNFetchBlob
179
+  .config({fileCache : true})
180
+  .fetch('GET', `${TEST_SERVER_URL}/public/beethoven.mp3`)
181
+  .then((resp) => {
182
+    return RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/mime`, null, [
183
+      { name : 'image', filename : 'image', type : 'image/jpeg', data : RNFetchBlob.base64.encode('123456') },
184
+      { name : 'mp3', filename : 'mp3', type : 'application/mp3', data : RNFetchBlob.base64.encode('123456') },
185
+      { name : 'mp3', filename : 'mp3', data : RNFetchBlob.base64.encode('123456') },
186
+      { name : 'asset', filename : 'asset.json', type: 'application/json', data : RNFetchBlob.wrap(fs.asset('test-asset1.json')) },
187
+      { name : 'camera-roll', filename : 'cameraRoll.png', type: 'image/png', data : RNFetchBlob.wrap(photo) },
188
+      { name : 'storage', filename : 'storage.mp3', type: 'application/mp3', data : RNFetchBlob.wrap(resp.path()) },
189
+    ])
190
+  })
191
+  .then((resp) => {
192
+    resp = resp.json()
193
+    report(
194
+      <Assert key="check #1 mime" expect={'image/jpeg'} actual={resp[0]} />,
195
+      <Assert key="check #2 mime" expect={'application/mp3'} actual={resp[1]} />,
196
+      <Assert key="check #3 mime" expect={'application/octet-stream'} actual={resp[2]} />,
197
+      <Assert key="check #4 mime" expect={'application/json'} actual={resp[3]} />,
198
+      <Assert key="check #5 mime" expect={'image/png'} actual={resp[4]} />,
199
+      <Assert key="check #6 mime" expect={'application/mp3'} actual={resp[5]} />,
200
+    )
201
+    done()
202
+  })
203
+})

+ 1
- 1
test/test-android.js Ver arquivo

@@ -17,7 +17,7 @@ const { Assert, Comparer, Info, prop } = RNTest
17 17
 const describe = RNTest.config({
18 18
   group : 'Android only functions',
19 19
   run : Platform.OS === 'android',
20
-  expand : true,
20
+  expand : false,
21 21
 })
22 22
 const { TEST_SERVER_URL, FILENAME, DROPBOX_TOKEN, styles } = prop()
23 23
 

+ 5
- 3
test/test-init.js Ver arquivo

@@ -18,8 +18,8 @@ const { Assert, Comparer, Info, describe, prop } = RNTest
18 18
 // test environment variables
19 19
 
20 20
 prop('FILENAME', `${Platform.OS}-0.7.0-${Date.now()}.png`)
21
-prop('TEST_SERVER_URL', 'http://192.168.16.70:8123')
22
-prop('TEST_SERVER_URL_SSL', 'https://192.168.16.70:8124')
21
+prop('TEST_SERVER_URL', 'http://192.168.0.11:8123')
22
+prop('TEST_SERVER_URL_SSL', 'https://192.168.0.11:8124')
23 23
 prop('DROPBOX_TOKEN', 'fsXcpmKPrHgAAAAAAAAAoXZhcXYWdgLpQMan6Tb_bzJ237DXhgQSev12hA-gUXt4')
24 24
 prop('styles', {
25 25
   image : {
@@ -50,10 +50,12 @@ describe('GET image from server', (report, done) => {
50 50
         done()
51 51
     })
52 52
 })
53
-//
53
+
54
+
54 55
 require('./test-0.1.x-0.4.x')
55 56
 require('./test-0.5.1')
56 57
 require('./test-0.5.2')
57 58
 require('./test-0.6.0')
59
+require('./test-0.6.2')
58 60
 require('./test-fs')
59 61
 require('./test-android')