瀏覽代碼

Merge branch 'master' into 0.10.0

Ben Hsieh 8 年之前
父節點
當前提交
9658ea1a78
共有 43 個檔案被更改,包括 1112 行新增812 行删除
  1. 1
    1
      .github/PULL_REQUEST_TEMPLATE
  2. 9
    1
      CONTRIBUTORS.md
  3. 69
    88
      README.md
  4. 二進制
      img/ios-1.png
  5. 二進制
      img/ios-2.png
  6. 二進制
      img/ios-3.png
  7. 二進制
      img/ios-4.png
  8. 二進制
      img/ios-5.png
  9. 1
    4
      scripts/test.sh
  10. 1
    1
      src/android.js
  11. 12
    4
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
  12. 43
    37
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
  13. 1
    3
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
  14. 0
    4
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java
  15. 87
    102
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
  16. 1
    11
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java
  17. 58
    48
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
  18. 1
    3
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java
  19. 12
    12
      src/class/RNFetchBlobReadStream.js
  20. 3
    2
      src/fs.js
  21. 6
    18
      src/index.js
  22. 27
    0
      src/ios/IOS7Polyfill.h
  23. 3
    1
      src/ios/RNFetchBlob.xcodeproj/project.pbxproj
  24. 2
    1
      src/ios/RNFetchBlob/RNFetchBlob.h
  25. 107
    46
      src/ios/RNFetchBlob/RNFetchBlob.m
  26. 4
    4
      src/ios/RNFetchBlobFS.h
  27. 303
    301
      src/ios/RNFetchBlobFS.m
  28. 15
    8
      src/ios/RNFetchBlobNetwork.m
  29. 28
    9
      src/ios/RNFetchBlobReqBuilder.m
  30. 3
    2
      src/package.json
  31. 22
    6
      src/polyfill/Blob.js
  32. 1
    1
      src/polyfill/File.js
  33. 7
    0
      src/polyfill/XMLHttpRequest.js
  34. 13
    0
      src/react-native-fetch-blob.podspec
  35. 57
    62
      src/scripts/prelink.js
  36. 2
    1
      src/utils/log.js
  37. 二進制
      test-server/public/1600k-img-dummy.jpg
  38. 6
    1
      test-server/server.js
  39. 2
    0
      test/test-0.7.0.js
  40. 127
    0
      test/test-0.9.4.js
  41. 60
    11
      test/test-firebase.js
  42. 16
    17
      test/test-init.js
  43. 2
    2
      test/test-xmlhttp.js

+ 1
- 1
.github/PULL_REQUEST_TEMPLATE 查看文件

@@ -1,5 +1,5 @@
1 1
 Thank you for making a pull request ! Just a gentle reminder :)
2 2
 
3 3
 1. If the PR is offering a feature please make the request to our "Feature Branch" 0.10.0
4
-2. Bug fix request to "Bug Fix Branch" 0.9.2
4
+2. Bug fix request to "Bug Fix Branch" 0.9.4
5 5
 3. Correct README.md can directly to master

+ 9
- 1
CONTRIBUTORS.md 查看文件

@@ -1,10 +1,18 @@
1
+
1 2
 Dmitry Petukhov <dmitryvpetukhov@gmail.com>
3
+
2 4
 Erik Smartt <code@eriksmartt.com>
5
+
3 6
 Evgeniy Baraniuk <ev.baraniuk@gmail.com>
7
+
4 8
 Juan B. Rodriguez <jbrodriguez@gmail.com>
9
+
5 10
 Kaishley <kklingachetti@msn.com>
11
+
6 12
 Nguyen Cao Nhat Linh <nhatlinh95@gmail.com>
7
-Tim Suchanek <tim.suchanek@gmail.com>
13
+
8 14
 follower <github@rancidbacon.com>
15
+
9 16
 francisco-sanchez-molina <psm1984@gmail.com>
17
+
10 18
 smartt <github@eriksmartt.com>

+ 69
- 88
README.md 查看文件

@@ -1,4 +1,4 @@
1
-# react-native-fetch-blob 
1
+# react-native-fetch-blob
2 2
 [![release](https://img.shields.io/github/release/wkh237/react-native-fetch-blob.svg?style=flat-square)](https://github.com/wkh237/react-native-fetch-blob/releases) [![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) [![](https://img.shields.io/badge/Wiki-Public-brightgreen.svg?style=flat-square)](https://github.com/wkh237/react-native-fetch-blob/wiki) [![npm](https://img.shields.io/npm/l/react-native-fetch-blob.svg?maxAge=2592000&style=flat-square)]()
3 3
 
4 4
 
@@ -11,7 +11,7 @@ A project committed to make file acess and data transfer easier, efficient for R
11 11
 - File stream support for dealing with large file
12 12
 - Blob, File, XMLHttpRequest polyfills that make browser-based library available in RN (experimental)
13 13
 
14
-> The npm package is inside `src` folder, this is development folder
14
+> The npm package is inside `src` folder, if you're going to install via git repository do not directly poiint to this folder
15 15
 
16 16
 ## TOC
17 17
 * [About](#user-content-about)
@@ -25,6 +25,7 @@ A project committed to make file acess and data transfer easier, efficient for R
25 25
  * [Cancel HTTP request](#user-content-cancel-request)
26 26
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
27 27
  * [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
28
+ * [Transfer Encoding](#user-content-transfer-encoding)
28 29
  * [RNFetchBlob as Fetch](#user-content-rnfetchblob-as-fetch)
29 30
 * [File System](#user-content-file-system)
30 31
  * [File access](#user-content-file-access)
@@ -51,40 +52,40 @@ Install package from npm
51 52
 npm install --save react-native-fetch-blob
52 53
 ```
53 54
 
54
-Link package using [rnpm](https://github.com/rnpm/rnpm)
55
+Or if using CocoaPods, add the pod to your `Podfile`, for example:
55 56
 
56
-```sh
57
-rnpm link
57
+```
58
+pod 'react-native-fetch-blob,
59
+    :path => '../node_modules/react-native-fetch-blob
58 60
 ```
59 61
 
60
-### Manually link the package (Android)
62
+**Automatically Link Native Modules**
61 63
 
62
-If rnpm link command failed to link the package automatically, you might try manually link the package.
64
+For 0.29.2+ projects, simply link native packages via following command because rnpm has been merged into react-native, you no longer need it.
63 65
 
64
-Open `android/settings.gradle`, and add these lines which will app RNFetchBlob Android project dependency to your app.
66
+```
67
+react-native link
68
+```
65 69
 
66
-```diff
67
-include ':app'      
68
-+ include ':react-native-fetch-blob'                                                                                                  
69
-+ project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')                        
70
+As for projects < 0.29 you need `rnpm` to link native packages
71
+
72
+```sh
73
+rnpm link
70 74
 ```
71 75
 
72
-Add this line to `MainApplication.java`, so that RNFetchBlob package becomes part of react native package.
76
+Optionally, use the following command to add Android permissions to `AndroidManifest.xml` automatically
73 77
 
74
-```diff
75
-...
76
-+ import com.RNFetchBlob.RNFetchBlobPackage;                                                                                 
77
-...
78
-protected List<ReactPackage> getPackages() {
79
-      return Arrays.<ReactPackage>asList(
80
-          new MainReactPackage(),
81
-+          new RNFetchBlobPackage()                                                                                         
82
-      );
83
-    }
84
-  };
85
-...
78
+```sh
79
+RNFB_ANDROID_PERMISSIONS=true react-native link
86 80
 ```
87
-> If you still having problem on installing this package, please check the [trouble shooting page](https://github.com/wkh237/react-native-fetch-blob/wiki/Trouble-Shooting) or [file an issue](https://github.com/wkh237/react-native-fetch-blob/issues/new)
81
+
82
+pre 0.29 projects 
83
+
84
+```sh
85
+RNFB_ANDROID_PERMISSIONS=true rnpm link
86
+```
87
+
88
+The link script might not take effect if you have non-default project structure, please visit [the wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package/_edit) to manually link the pacakge.
88 89
 
89 90
 **Grant Permission to External storage for Android 5.0 or lower**
90 91
 
@@ -139,11 +140,10 @@ If you're using ES5 require statement to load the module, please add `default`.
139 140
 var RNFetchBlob = require('react-native-fetch-blob').default
140 141
 ```
141 142
 
142
-### HTTP Data Transfer
143
+## HTTP Data Transfer
143 144
 
144
----
145 145
 
146
-#### Regular Request
146
+### Regular Request
147 147
 
148 148
 After `0.8.0` react-native-fetch-blob automatically decide how to send the body by checking its type and `Content-Type` in header. The rule is described in the following diagram
149 149
 
@@ -151,13 +151,15 @@ After `0.8.0` react-native-fetch-blob automatically decide how to send the body
151 151
 
152 152
 To sum up :
153 153
 
154
-- To send a form data, the `Content-Type` header won't take effect if the body is an `Array` because we will set proper content type for you.
155
-- To send binary data, you have two choices, use BASE64 encoded string or a file path which points to a file contains the body. The `Content-Type` header does not matters.
156
- - The body is a BASE64 encoded string, the `Content-Type` header filed must containing substring`;BASE64` or `application/octet`  
157
- - The body is a path point to a file, it must be a string starts with `RNFetchBlob-file://`, which can simply done by `RNFetchBlob.wrap(PATH_TO_THE_FILE)`
158
-- To send the body as-is, set a `Content-Type` header not containing `;BASE64` or `application/octet`.
154
+- To send a form data, the `Content-Type` header does not matters. When the body is an `Array` we will set proper content type for you.
155
+- To send binary data, you have two choices, use BASE64 encoded string or path points to a file contains the body.
156
+ - If the `Content-Type` containing substring`;BASE64` or `application/octet` the given body will be considered as a BASE64 encoded data which will be decoded to binary data as the request body.   
157
+ - Otherwise, if a string starts with `RNFetchBlob-file://` (which can simply done by `RNFetchBlob.wrap(PATH_TO_THE_FILE)`), it will try to find the data from the URI string after `RNFetchBlob-file://` and use it as request body. 
158
+- To send the body as-is, simply use a `Content-Type` header not containing `;BASE64` or `application/octet`.
159
+
160
+> After 0.9.4, we disabled `Chunked` transfer encoding by default, if you're going to use it, you should explicitly set header `Transfer-Encoding` to `Chunked`.
159 161
 
160
-#### Download example : Fetch files that needs authorization token
162
+### Download example : Fetch files that needs authorization token
161 163
 
162 164
 Most simple way is download to memory and stored as BASE64 encoded string, this is handy when the response data is small.
163 165
 
@@ -183,9 +185,9 @@ RNFetchBlob.fetch('GET', 'http://www.example.com/images/img1.png', {
183 185
   })
184 186
 ```
185 187
 
186
-#### Download to storage directly
188
+### Download to storage directly
187 189
 
188
-If the response data is large, that would be a bad idea to convert it into BASE64 string. The better solution is store the response data directly into file system. The simplest way is give a `fileCache` option to config, and set it to `true`. This will make incoming response data stored in a temporary path **without** any file extension.
190
+If the response data is large, that would be a bad idea to convert it into BASE64 string. A better solution is streaming the response directly into a file, simply add a `fileCache` option to config, and set it to `true`. This will make incoming response data stored in a temporary path **without** any file extension.
189 191
 
190 192
 **These files won't be removed automatically, please refer to [Cache File Management](#user-content-cache-file-management)**
191 193
 
@@ -207,7 +209,7 @@ RNFetchBlob
207 209
 
208 210
 **Set Temp File Extension**
209 211
 
210
-Sometimes you might need a file extension for some reason. For instance, when using file path as source of `Image` component, the path should end with something like .png or .jpg, you can do this by add `appendExt` option to `config`.
212
+Sometimes you might need a file extension for some reason. For example, when using file path as source of `Image` component, the path should end with something like .png or .jpg, you can do this by add `appendExt` option to `config`.
211 213
 
212 214
 ```js
213 215
 RNFetchBlob
@@ -230,7 +232,7 @@ RNFetchBlob
230 232
 
231 233
 **Use Specific File Path**
232 234
 
233
-If you prefer a specific path rather than randomly generated one, you can use `path` option. We've added a constant [dirs](#user-content-dirs) in v0.5.0 that contains several common used directories.
235
+If you prefer a specific path rather than randomly generated one, you can use `path` option. We've added [several  constants](#user-content-dirs) in v0.5.0 which represents commonly used directories.
234 236
 
235 237
 ```js
236 238
 let dirs = RNFetchBlob.fs.dirs
@@ -252,7 +254,7 @@ RNFetchBlob
252 254
 
253 255
 ####  Upload example : Dropbox [files-upload](https://www.dropbox.com/developers/documentation/http/documentation#files-upload) API
254 256
 
255
-`react-native-fetch-blob` will convert the base64 string in `body` to binary format using native API, this process will be  done in a new thread, so it's async.
257
+`react-native-fetch-blob` will convert the base64 string in `body` to binary format using native API, this process will be  done in a separated thread, so it won't block your GUI.
256 258
 
257 259
 ```js
258 260
 
@@ -277,9 +279,9 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
277 279
   })
278 280
 ```
279 281
 
280
-#### Upload a file from storage
282
+### Upload a file from storage
281 283
 
282
-If you're going to use a `file` request body, just wrap the path with `wrap` API.
284
+If you're going to use a `file` as request body, just wrap the path with `wrap` API.
283 285
 
284 286
 ```js
285 287
 RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
@@ -303,7 +305,7 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
303 305
   })
304 306
 ```
305 307
 
306
-#### Multipart/form-data example : Post form data with file and data
308
+### Multipart/form-data example : Post form data with file and data
307 309
 
308 310
 In `version >= 0.3.0` you can also post files with form data, just put an array in `body`, with elements have property `name`, `data`, and `filename`(optional).
309 311
 
@@ -335,7 +337,7 @@ Elements have property `filename` will be transformed into binary format, otherw
335 337
   })
336 338
 ```
337 339
 
338
-What if you want to upload a file using form data ? 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.
340
+What if you want to append a file to form data ? 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. But keep in mind when the file is large it's likely crash your app. Please consider use other strategy (see [#94](https://github.com/wkh237/react-native-fetch-blob/issues/94)).
339 341
 
340 342
 ```js
341 343
 
@@ -374,9 +376,9 @@ What if you want to upload a file using form data ? Just like [upload a file fro
374 376
   })
375 377
 ```
376 378
 
377
-#### Upload/Download progress
379
+### Upload/Download progress
378 380
 
379
-In `version >= 0.4.2` it is possible to know the upload/download progress. After `0.7.0` IOS and Android upload progress are supported.
381
+In `version >= 0.4.2` it is possible to know the upload/download progress. After `0.7.0` IOS and Android upload progress are also supported.
380 382
 
381 383
 ```js
382 384
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -399,7 +401,7 @@ In `version >= 0.4.2` it is possible to know the upload/download progress. After
399 401
     })
400 402
 ```
401 403
 
402
-#### Cancel Request
404
+### Cancel Request
403 405
 
404 406
 After `0.7.0` it is possible to cancel a HTTP request. When the request cancel, it will definately throws an promise rejection, be sure to catch it.
405 407
 
@@ -538,9 +540,9 @@ Or show an image in image viewer
538 540
       android.actionViewIntent(PATH_OF_IMG, 'image/png')
539 541
 ```
540 542
 
541
-### File System
543
+## File System
542 544
 
543
-#### File Access
545
+### File Access
544 546
 
545 547
 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.
546 548
 
@@ -568,11 +570,11 @@ File Access APIs
568 570
 
569 571
 See [File API](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API) for more information
570 572
 
571
-#### File Stream
573
+### File Stream
572 574
 
573 575
 In `v0.5.0` we've added  `writeStream` and `readStream`, which allows your app read/write data from file path. This API creates a file stream, rather than convert whole data into BASE64 encoded string, it's handy when processing **large files**.
574 576
 
575
-When calling `readStream` method, you have to `open` the stream, and start to read data.
577
+When calling `readStream` method, you have to `open` the stream, and start to read data. When the file is large, consider use an appropriate `bufferSize` and `interval` to reduce the native event dispatching overhead (see [Performance Tips](#user-content-performance-tips))
576 578
 
577 579
 ```js
578 580
 let data = ''
@@ -617,7 +619,7 @@ RNFetchBlob.fs.writeStream(
617 619
 
618 620
 ```
619 621
 
620
-#### Cache File Management
622
+### Cache File Management
621 623
 
622 624
 When using `fileCache` or `path` options along with `fetch` API, response data will automatically stored into file system. The files will **NOT** removed unless you `unlink` it. There're several ways to remove the files
623 625
 
@@ -674,7 +676,15 @@ You can also grouping requests by using `session` API, and use `dispose` to remo
674 676
 
675 677
 ```
676 678
 
677
-#### Self-Signed SSL Server
679
+### Transfer Encoding
680
+
681
+After `0.9.4`, the `Chunked` transfer encoding is disabled by default due to some service provoder may not support chunked transfer. To enable it, set `Transfer-Encoding` header to `Chunked`.
682
+
683
+```js
684
+RNFetchBlob.fetch('POST', 'http://example.com/upload', { 'Transfer-Encoding' : 'Chunked' }, bodyData)
685
+```
686
+
687
+### Self-Signed SSL Server
678 688
 
679 689
 By default, react-native-fetch-blob does NOT allow connection to unknown certification provider since it's dangerous. If you're going to connect a server with self-signed certification, add `trusty` to `config`. This function is available for version >= `0.5.3`
680 690
 
@@ -688,7 +698,7 @@ RNFetchBlob.config({
688 698
 })
689 699
 ```
690 700
 
691
-### Web API Polyfills
701
+## Web API Polyfills
692 702
 
693 703
 After `0.8.0` we've made some [Web API polyfills](https://github.com/wkh237/react-native-fetch-blob/wiki/Web-API-Polyfills-(experimental)) that makes some browser-based library available in RN.
694 704
 
@@ -697,13 +707,13 @@ After `0.8.0` we've made some [Web API polyfills](https://github.com/wkh237/reac
697 707
 
698 708
 Here's a [sample app](https://github.com/wkh237/rn-firebase-storage-upload-sample) that uses polyfills to upload files to FireBase.
699 709
 
700
-### Performance Tips
701
-
702
----
710
+## Performance Tips
703 711
 
704 712
 **Reduce RCT Bridge and BASE64 Overheard**
705 713
 
706
-React Native connects JS and Native context by passing JSON through React bridge, therefore there will be an overhead to convert data before they sent. When data is large, this will be quite a performance impact to your app, it's recommended to use file storage instead of BASE64 if possible. The following chart shows how much faster when loading data from storage than BASE64 encoded string on iphone 6.
714
+When reading data via `fs.readStream` the process seems blocking JS thread when file is large, it's because the default buffer size is quite small (4kb) which result in large amount of events triggered in JS thread, try to increase the buffer size (for example 100kb = 102400) and set a larger interval (which is introduced in 0.9.4 default value is 10ms) to limit the frequency. 
715
+
716
+React Native connects JS and Native context by passing JSON around React Native bridge, and there will be an overhead to convert data before they sent to each side. When data is large, this will be quite a performance impact to your app, it's recommended to use file storage instead of BASE64 if possible.The following chart shows how much faster when loading data from storage than BASE64 encoded string on iphone 6.
707 717
 
708 718
 <img src="img/performance_1.png" style="width : 100%"/>
709 719
 
@@ -721,36 +731,7 @@ If you're going to concatenate files, you don't have to read the data to JS cont
721 731
 
722 732
 ## Changes
723 733
 
724
-| Version | |
725
-|---|---|
726
-| 0.9.2 | Add Blob.slice() method implementation #89 |
727
-| 0.9.1 | Fix Android Blob constructor asynchronous issue caused by 0.9.0 fs change |
728
-| 0.9.0 | Fix unicode response data format issue #73. Improve Android performance by using thread pool instead of async task. Add Fetch replacement #70. Add Android only API `actionViewIntent` to open file or install APK in app |
729
-| 0.8.1 | Remove Web API log and fix ios progress report function. |
730
-| 0.8.0 | Added Web API polyfills, support regular request, added timeout option. |
731
-| 0.7.5 | Fix installation script that make it compatible to react-native < 0.28 |
732
-| 0.7.4 | Fix app crash problem in version > 0.27 |
733
-| 0.7.3 | Fix OkHttp dependency issue in version < 0.29 |
734
-| 0.7.2 | Fix cancel request bug |
735
-| 0.7.1 | Fix #57 ios module could not compile on ios version <= 9.3 |
736
-| 0.7.0 | Add support of Android upload progress, and remove AsyncHttpClient dependency from Android native implementation. |
737
-| 0.6.4 | Fix rnpm link script. |
738
-| 0.6.3 | Fix performance issue on IOS, increase max concurrent request limitation from 1. |
739
-| 0.6.2 | Add support of asset file and camera roll files, Support custom MIME type when sending multipart request, thanks @smartt |
740
-| 0.6.1 | Fix #37 progress report API issue on IOS |
741
-| 0.6.0 | Add readFile and writeFile API for easier file access, also added Android download manager support. |
742
-| 0.5.8 | Fix #33 PUT request will always be sent as POST on Android |
743
-| 0.5.7 | Fix #31 #30 Xcode pre 7.3 build error |
744
-| 0.5.6 | Add support for IOS network status indicator. Fix file stream ASCII reader bug. |
745
-| 0.5.5 | Remove work in progress code added in 0.5.2 which may cause memory leaks. |
746
-| 0.5.4 | Fix #30 #31 build build error, and improve memory efficiency. |
747
-| 0.5.3 | Add API for access untrusted SSL server |
748
-| 0.5.2 | Fix improper url params bug [#26](https://github.com/wkh237/react-native-fetch-blob/issues/26) and change IOS HTTP implementation from NSURLConnection to NSURLSession |
749
-| 0.5.0 | Upload/download with direct access to file storage, and also added file access APIs |
750
-| 0.4.2 | Supports upload/download progress |
751
-| 0.4.1 | Fix upload form-data missing file extension problem on Android |
752
-| 0.4.0 | Add base-64 encode/decode library and API |
753
-| ~0.3.0 | Upload/Download octet-stream and form-data |
734
+See [release notes](https://github.com/wkh237/react-native-fetch-blob/releases)
754 735
 
755 736
 ### Development
756 737
 

二進制
img/ios-1.png 查看文件


二進制
img/ios-2.png 查看文件


二進制
img/ios-3.png 查看文件


二進制
img/ios-4.png 查看文件


二進制
img/ios-5.png 查看文件


+ 1
- 4
scripts/test.sh 查看文件

@@ -41,10 +41,7 @@ cd "${TEST_APP_PATH}"
41 41
 npm install --save react-native-fetch-blob
42 42
 # libs that requires web API polyfills
43 43
 npm install --save firebase
44
-# libs that requires Node polyfills
45
-npm install --save oboe
46
-
47
-rnpm link
44
+react-native link
48 45
 
49 46
 # copy android assets
50 47
 cd ${CWD}

+ 1
- 1
src/android.js 查看文件

@@ -18,7 +18,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
18 18
  * @param  {string} mime MIME type string
19 19
  * @return {Promise}
20 20
  */
21
-function actionViewIntent(path:string, mime = 'text/plain':string) {
21
+function actionViewIntent(path:string, mime:string = 'text/plain') {
22 22
   if(Platform.OS === 'android')
23 23
     return RNFetchBlob.actionViewIntent(path, mime)
24 24
   else

+ 12
- 4
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java 查看文件

@@ -21,6 +21,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
21 21
     static ReactApplicationContext RCTContext;
22 22
     static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
23 23
     static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
24
+    static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
25
+    static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
24 26
 
25 27
     public RNFetchBlob(ReactApplicationContext reactContext) {
26 28
 
@@ -205,11 +207,17 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
205 207
     /**
206 208
      * @param path Stream file path
207 209
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
208
-     * @param bufferSize Stream buffer size, default to 1024 or 1026(base64).
210
+     * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
209 211
      */
210
-    public void readStream(String path, String encoding, int bufferSize) {
211
-        RNFetchBlobFS fs = new RNFetchBlobFS(this.getReactApplicationContext());
212
-        fs.readStream(path, encoding, bufferSize);
212
+    public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
213
+        final ReactApplicationContext ctx = this.getReactApplicationContext();
214
+        fsThreadPool.execute(new Runnable() {
215
+            @Override
216
+            public void run() {
217
+                RNFetchBlobFS fs = new RNFetchBlobFS(ctx);
218
+                fs.readStream(path, encoding, bufferSize, tick, streamId);
219
+            }
220
+        });
213 221
     }
214 222
 
215 223
     @ReactMethod

+ 43
- 37
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java 查看文件

@@ -21,9 +21,6 @@ import okhttp3.MediaType;
21 21
 import okhttp3.RequestBody;
22 22
 import okio.BufferedSink;
23 23
 
24
-/**
25
- * Created by wkh237 on 2016/7/11.
26
- */
27 24
 public class RNFetchBlobBody extends RequestBody{
28 25
 
29 26
     InputStream requestStream;
@@ -34,42 +31,35 @@ public class RNFetchBlobBody extends RequestBody{
34 31
     RNFetchBlobReq.RequestType requestType;
35 32
     MediaType mime;
36 33
     File bodyCache;
34
+    Boolean chunkedEncoding = false;
37 35
 
38 36
 
39
-    /**
40
-     * Single file or raw content request constructor
41
-     * @param taskId
42
-     * @param type
43
-     * @param form
44
-     * @param contentType
45
-     */
46
-    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
37
+    public RNFetchBlobBody(String taskId) {
47 38
         this.mTaskId = taskId;
48
-        this.form = form;
49
-        requestType = type;
50
-        mime = contentType;
51
-        try {
52
-            bodyCache = createMultipartBodyCache();
53
-            requestStream = new FileInputStream(bodyCache);
54
-            contentLength = bodyCache.length();
55
-        } catch(Exception ex) {
56
-            ex.printStackTrace();
57
-            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create request multipart body :" + ex.getLocalizedMessage());
58
-        }
39
+    }
40
+
41
+    RNFetchBlobBody chunkedEncoding(boolean val) {
42
+        this.chunkedEncoding = val;
43
+        return this;
44
+    }
45
+
46
+    RNFetchBlobBody setMIME(MediaType mime) {
47
+        this.mime = mime;
48
+        return this;
49
+    }
50
+
51
+    RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) {
52
+        this.requestType = type;
53
+        return this;
59 54
     }
60 55
 
61 56
     /**
62
-     * Multipart request constructor
63
-     * @param taskId
64
-     * @param type
65
-     * @param rawBody
66
-     * @param contentType
57
+     * Set request body
58
+     * @param body A string represents the request body
59
+     * @return object itself
67 60
      */
68
-    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
69
-        this.mTaskId = taskId;
70
-        requestType = type;
71
-        this.rawBody = rawBody;
72
-        mime = contentType;
61
+    RNFetchBlobBody setBody(String body) {
62
+        this.rawBody = body;
73 63
         if(rawBody == null) {
74 64
             this.rawBody = "";
75 65
             requestType = RNFetchBlobReq.RequestType.AsIs;
@@ -82,6 +72,7 @@ public class RNFetchBlobBody extends RequestBody{
82 72
                     break;
83 73
                 case AsIs:
84 74
                     contentLength = this.rawBody.getBytes().length;
75
+                    requestStream = new ByteArrayInputStream(this.rawBody.getBytes());
85 76
                     break;
86 77
                 case Others:
87 78
                     break;
@@ -90,12 +81,30 @@ public class RNFetchBlobBody extends RequestBody{
90 81
             ex.printStackTrace();
91 82
             RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create single content request body :" + ex.getLocalizedMessage() + "\r\n");
92 83
         }
84
+        return this;
85
+    }
93 86
 
87
+    /**
88
+     * Set request body (Array)
89
+     * @param body A Readable array contains form data
90
+     * @return object itself
91
+     */
92
+    RNFetchBlobBody setBody(ReadableArray body) {
93
+        this.form = body;
94
+        try {
95
+            bodyCache = createMultipartBodyCache();
96
+            requestStream = new FileInputStream(bodyCache);
97
+            contentLength = bodyCache.length();
98
+        } catch(Exception ex) {
99
+            ex.printStackTrace();
100
+            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create request multipart body :" + ex.getLocalizedMessage());
101
+        }
102
+        return this;
94 103
     }
95 104
 
96 105
     @Override
97 106
     public long contentLength() {
98
-        return contentLength;
107
+        return chunkedEncoding ? -1 : contentLength;
99 108
     }
100 109
 
101 110
     @Override
@@ -106,10 +115,7 @@ public class RNFetchBlobBody extends RequestBody{
106 115
     @Override
107 116
     public void writeTo(BufferedSink sink) {
108 117
         try {
109
-            if (requestType == RNFetchBlobReq.RequestType.AsIs)
110
-                sink.write(rawBody.getBytes());
111
-            else
112
-                pipeStreamToSink(requestStream, sink);
118
+            pipeStreamToSink(requestStream, sink);
113 119
         } catch(Exception ex) {
114 120
             RNFetchBlobUtils.emitWarningEvent(ex.getLocalizedMessage());
115 121
             ex.printStackTrace();

+ 1
- 3
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java 查看文件

@@ -5,9 +5,7 @@ import com.facebook.react.bridge.ReadableMap;
5 5
 
6 6
 import java.util.HashMap;
7 7
 
8
-/**
9
- * Created by wkh237 on 2016/5/29.
10
- */
8
+
11 9
 public class RNFetchBlobConfig {
12 10
 
13 11
     public Boolean fileCache;

+ 0
- 4
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java 查看文件

@@ -1,10 +1,6 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
-import okhttp3.MediaType;
4 3
 
5
-/**
6
- * Created by wkh237 on 2016/7/11.
7
- */
8 4
 public class RNFetchBlobConst {
9 5
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
10 6
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";

+ 87
- 102
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java 查看文件

@@ -1,16 +1,11 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
-import android.app.LoaderManager;
4
-import android.content.ContentResolver;
5
-import android.content.CursorLoader;
6 3
 import android.content.res.AssetFileDescriptor;
7
-import android.database.Cursor;
8 4
 import android.media.MediaScannerConnection;
9 5
 import android.net.Uri;
10 6
 import android.os.AsyncTask;
11 7
 import android.os.Environment;
12
-import android.os.Looper;
13
-import android.provider.MediaStore;
8
+import android.os.SystemClock;
14 9
 import android.util.Base64;
15 10
 
16 11
 import com.RNFetchBlob.Utils.PathResolver;
@@ -21,31 +16,19 @@ import com.facebook.react.bridge.ReactApplicationContext;
21 16
 import com.facebook.react.bridge.ReadableArray;
22 17
 import com.facebook.react.bridge.WritableArray;
23 18
 import com.facebook.react.bridge.WritableMap;
24
-import com.facebook.react.bridge.WritableNativeArray;
25 19
 import com.facebook.react.modules.core.DeviceEventManagerModule;
26 20
 
27 21
 import java.io.File;
28 22
 import java.io.FileInputStream;
29
-import java.io.FileNotFoundException;
30 23
 import java.io.FileOutputStream;
31 24
 import java.io.IOException;
32 25
 import java.io.InputStream;
33 26
 import java.io.OutputStream;
34
-import java.io.UnsupportedEncodingException;
35
-import java.net.URLDecoder;
36 27
 import java.nio.charset.Charset;
37 28
 import java.util.HashMap;
38 29
 import java.util.Map;
39 30
 import java.util.UUID;
40
-import java.util.concurrent.BlockingDeque;
41
-import java.util.concurrent.BlockingQueue;
42
-import java.util.concurrent.LinkedBlockingQueue;
43
-import java.util.concurrent.ThreadPoolExecutor;
44
-import java.util.concurrent.TimeUnit;
45
-
46
-/**
47
- * Created by wkh237 on 2016/5/26.
48
- */
31
+
49 32
 public class RNFetchBlobFS {
50 33
 
51 34
     ReactApplicationContext mCtx;
@@ -74,7 +57,7 @@ public class RNFetchBlobFS {
74 57
      * @param path Destination file path.
75 58
      * @param encoding Encoding of the string.
76 59
      * @param data Array passed from JS context.
77
-     * @param promise
60
+     * @param promise RCT Promise
78 61
      */
79 62
     static public void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) {
80 63
         try {
@@ -86,6 +69,7 @@ public class RNFetchBlobFS {
86 69
             FileOutputStream fout = new FileOutputStream(f, append);
87 70
             // write data from a file
88 71
             if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
72
+                data = normalizePath(data);
89 73
                 File src = new File(data);
90 74
                 if(!src.exists()) {
91 75
                     promise.reject("RNfetchBlob writeFileError", "source file : " + data + "not exists");
@@ -118,7 +102,7 @@ public class RNFetchBlobFS {
118 102
      * Write array of bytes into file
119 103
      * @param path Destination file path.
120 104
      * @param data Array passed from JS context.
121
-     * @param promise
105
+     * @param promise RCT Promise
122 106
      */
123 107
     static public void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
124 108
 
@@ -228,77 +212,72 @@ public class RNFetchBlobFS {
228 212
      * @param encoding  File stream decoder, should be one of `base64`, `utf8`, `ascii`
229 213
      * @param bufferSize    Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
230 214
      */
231
-    public void readStream( String path, String encoding, int bufferSize) {
215
+    public void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
232 216
         path = normalizePath(path);
233
-        AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {
234
-            @Override
235
-            protected Integer doInBackground(String ... args) {
236
-                String path = args[0];
237
-                String encoding = args[1];
238
-                int bufferSize = Integer.parseInt(args[2]);
239
-                String eventName = "RNFetchBlobStream+" + path;
240
-                try {
217
+        try {
241 218
 
242
-                    int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
243
-                    if(bufferSize > 0)
244
-                        chunkSize = bufferSize;
219
+            int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
220
+            if(bufferSize > 0)
221
+                chunkSize = bufferSize;
245 222
 
246
-                    InputStream fs;
247
-                    if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
248
-                        fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
249
-                    }
250
-                    else {
251
-                        fs = new FileInputStream(new File(path));
252
-                    }
223
+            InputStream fs;
224
+            if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
225
+                fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
226
+            }
227
+            else {
228
+                fs = new FileInputStream(new File(path));
229
+            }
253 230
 
254
-                    byte[] buffer = new byte[chunkSize];
255
-                    int cursor = 0;
256
-                    boolean error = false;
231
+            byte[] buffer = new byte[chunkSize];
232
+            int cursor = 0;
233
+            boolean error = false;
257 234
 
258
-                    if (encoding.equalsIgnoreCase("utf8")) {
259
-                        while ((cursor = fs.read(buffer)) != -1) {
260
-                            String chunk = new String(buffer, 0, cursor, "UTF-8");
261
-                            emitStreamEvent(eventName, "data", chunk);
262
-                        }
263
-                    } else if (encoding.equalsIgnoreCase("ascii")) {
264
-                        while ((cursor = fs.read(buffer)) != -1) {
265
-                            WritableArray chunk = Arguments.createArray();
266
-                            for(int i =0;i<cursor;i++)
267
-                            {
268
-                                chunk.pushInt((int)buffer[i]);
269
-                            }
270
-                            emitStreamEvent(eventName, "data", chunk);
271
-                        }
272
-                    } else if (encoding.equalsIgnoreCase("base64")) {
273
-                        while ((cursor = fs.read(buffer)) != -1) {
274
-                            if(cursor < chunkSize) {
275
-                                byte [] copy = new byte[cursor];
276
-                                for(int i =0;i<cursor;i++) {
277
-                                    copy[i] = buffer[i];
278
-                                }
279
-                                emitStreamEvent(eventName, "data", Base64.encodeToString(copy, Base64.NO_WRAP));
280
-                            }
281
-                            else
282
-                                emitStreamEvent(eventName, "data", Base64.encodeToString(buffer, Base64.NO_WRAP));
235
+            if (encoding.equalsIgnoreCase("utf8")) {
236
+                while ((cursor = fs.read(buffer)) != -1) {
237
+                    String chunk = new String(buffer, 0, cursor, "UTF-8");
238
+                    emitStreamEvent(streamId, "data", chunk);
239
+                    if(tick > 0)
240
+                        SystemClock.sleep(tick);
241
+                }
242
+            } else if (encoding.equalsIgnoreCase("ascii")) {
243
+                while ((cursor = fs.read(buffer)) != -1) {
244
+                    WritableArray chunk = Arguments.createArray();
245
+                    for(int i =0;i<cursor;i++)
246
+                    {
247
+                        chunk.pushInt((int)buffer[i]);
248
+                    }
249
+                    emitStreamEvent(streamId, "data", chunk);
250
+                    if(tick > 0)
251
+                        SystemClock.sleep(tick);
252
+                }
253
+            } else if (encoding.equalsIgnoreCase("base64")) {
254
+                while ((cursor = fs.read(buffer)) != -1) {
255
+                    if(cursor < chunkSize) {
256
+                        byte [] copy = new byte[cursor];
257
+                        for(int i =0;i<cursor;i++) {
258
+                            copy[i] = buffer[i];
283 259
                         }
284
-                    } else {
285
-                        String msg = "unrecognized encoding `" + encoding + "`";
286
-                        emitStreamEvent(eventName, "error", msg);
287
-                        error = true;
260
+                        emitStreamEvent(streamId, "data", Base64.encodeToString(copy, Base64.NO_WRAP));
288 261
                     }
289
-
290
-                    if(!error)
291
-                        emitStreamEvent(eventName, "end", "");
292
-                    fs.close();
293
-                    buffer = null;
294
-
295
-                } catch (Exception err) {
296
-                    emitStreamEvent(eventName, "error", err.getLocalizedMessage());
262
+                    else
263
+                        emitStreamEvent(streamId, "data", Base64.encodeToString(buffer, Base64.NO_WRAP));
264
+                    if(tick > 0)
265
+                        SystemClock.sleep(tick);
297 266
                 }
298
-                return null;
267
+            } else {
268
+                String msg = "unrecognized encoding `" + encoding + "`";
269
+                emitStreamEvent(streamId, "error", msg);
270
+                error = true;
299 271
             }
300
-        };
301
-        task.execute(path, encoding, String.valueOf(bufferSize));
272
+
273
+            if(!error)
274
+                emitStreamEvent(streamId, "end", "");
275
+            fs.close();
276
+            buffer = null;
277
+
278
+        } catch (Exception err) {
279
+            emitStreamEvent(streamId, "error", err.getLocalizedMessage());
280
+        }
302 281
     }
303 282
 
304 283
     /**
@@ -433,6 +412,7 @@ public class RNFetchBlobFS {
433 412
      * @param callback  JS context callback
434 413
      */
435 414
     static void cp(String path, String dest, Callback callback) {
415
+
436 416
         path = normalizePath(path);
437 417
         InputStream in = null;
438 418
         OutputStream out = null;
@@ -443,7 +423,6 @@ public class RNFetchBlobFS {
443 423
                 callback.invoke("cp error: source file at path`" + path + "` not exists");
444 424
                 return;
445 425
             }
446
-
447 426
             if(!new File(dest).exists())
448 427
                 new File(dest).createNewFile();
449 428
 
@@ -457,14 +436,17 @@ public class RNFetchBlobFS {
457 436
             }
458 437
 
459 438
         } catch (Exception err) {
460
-            if(err != null)
461
-                callback.invoke(err.getLocalizedMessage());
439
+            callback.invoke(err.getLocalizedMessage());
462 440
         } finally {
463 441
             try {
464
-                in.close();
465
-                out.close();
442
+                if (in != null) {
443
+                    in.close();
444
+                }
445
+                if (out != null) {
446
+                    out.close();
447
+                }
466 448
                 callback.invoke();
467
-            } catch (IOException e) {
449
+            } catch (Exception e) {
468 450
                 callback.invoke(e.getLocalizedMessage());
469 451
             }
470 452
         }
@@ -492,7 +474,7 @@ public class RNFetchBlobFS {
492 474
      * @param callback  JS context callback
493 475
      */
494 476
     static void exists(String path, Callback callback) {
495
-        path = normalizePath(path);
477
+
496 478
         if(isAsset(path)) {
497 479
             try {
498 480
                 String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
@@ -503,11 +485,11 @@ public class RNFetchBlobFS {
503 485
             }
504 486
         }
505 487
         else {
488
+            path = normalizePath(path);
506 489
             boolean exist = new File(path).exists();
507 490
             boolean isDir = new File(path).isDirectory();
508 491
             callback.invoke(exist, isDir);
509 492
         }
510
-
511 493
     }
512 494
 
513 495
     /**
@@ -536,13 +518,15 @@ public class RNFetchBlobFS {
536 518
      * @param dest  Destination of created file
537 519
      * @param start Start byte offset in source file
538 520
      * @param end   End byte offset
539
-     * @param encode
521
+     * @param encode NOT IMPLEMENTED
540 522
      */
541 523
     public static void slice(String src, String dest, int start, int end, String encode, Promise promise) {
542 524
         try {
525
+            src = normalizePath(src);
543 526
             File source = new File(src);
544 527
             if(!source.exists()) {
545 528
                 promise.reject("RNFetchBlob.slice error", "source file : " + src + " not exists");
529
+                return;
546 530
             }
547 531
             long size = source.length();
548 532
             long max = Math.min(size, end);
@@ -605,6 +589,7 @@ public class RNFetchBlobFS {
605 589
      */
606 590
     static void stat(String path, Callback callback) {
607 591
         try {
592
+            path = normalizePath(path);
608 593
             WritableMap result = statFile(path);
609 594
             if(result == null)
610 595
                 callback.invoke("stat error: failed to list path `" + path + "` for it is not exist or it is not a folder", null);
@@ -673,10 +658,10 @@ public class RNFetchBlobFS {
673 658
 
674 659
     /**
675 660
      * Create new file at path
676
-     * @param path
677
-     * @param data
678
-     * @param encoding
679
-     * @param callback
661
+     * @param path The destination path of the new file.
662
+     * @param data Initial data of the new file.
663
+     * @param encoding Encoding of initial data.
664
+     * @param callback RCT bridge callback.
680 665
      */
681 666
     static void createFile(String path, String data, String encoding, Callback callback) {
682 667
         try {
@@ -782,8 +767,8 @@ public class RNFetchBlobFS {
782 767
             return data.getBytes(Charset.forName("US-ASCII"));
783 768
         }
784 769
         else if(encoding.toLowerCase().contains("base64")) {
785
-            byte [] b = Base64.decode(data, Base64.NO_WRAP);
786
-            return b;
770
+            return Base64.decode(data, Base64.NO_WRAP);
771
+
787 772
         }
788 773
         else if(encoding.equalsIgnoreCase("utf8")) {
789 774
             return data.getBytes(Charset.forName("UTF-8"));
@@ -822,8 +807,8 @@ public class RNFetchBlobFS {
822 807
     /**
823 808
      * Get input stream of the given path, when the path is a string starts with bundle-assets://
824 809
      * the stream is created by Assets Manager, otherwise use FileInputStream.
825
-     * @param path
826
-     * @return
810
+     * @param path The file to open stream
811
+     * @return InputStream instance
827 812
      * @throws IOException
828 813
      */
829 814
     static InputStream inputStreamFromPath(String path) throws IOException {
@@ -835,8 +820,8 @@ public class RNFetchBlobFS {
835 820
 
836 821
     /**
837 822
      * Check if the asset or the file exists
838
-     * @param path
839
-     * @return
823
+     * @param path A file path URI string
824
+     * @return A boolean value represents if the path exists.
840 825
      */
841 826
     static boolean isPathExists(String path) {
842 827
         if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {

+ 1
- 11
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java 查看文件

@@ -1,26 +1,16 @@
1 1
 package com.RNFetchBlob;
2 2
 
3 3
 import com.facebook.react.ReactPackage;
4
-import com.facebook.react.bridge.Callback;
5 4
 import com.facebook.react.bridge.JavaScriptModule;
6 5
 import com.facebook.react.bridge.NativeModule;
7 6
 import com.facebook.react.bridge.ReactApplicationContext;
8
-import com.facebook.react.bridge.ReactContext;
9
-import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
-import com.facebook.react.bridge.ReactMethod;
11
-import com.facebook.react.bridge.ReadableArray;
12
-import com.facebook.react.bridge.ReadableMap;
13
-import com.facebook.react.bridge.ReadableMapKeySetIterator;
14
-import com.facebook.react.bridge.ReadableType;
15 7
 import com.facebook.react.uimanager.ViewManager;
16 8
 
17 9
 import java.util.ArrayList;
18 10
 import java.util.Collections;
19 11
 import java.util.List;
20 12
 
21
-/**
22
- * Created by wkh237 on 2016/4/29.
23
- */
13
+
24 14
 public class RNFetchBlobPackage implements ReactPackage {
25 15
 
26 16
     @Override

+ 58
- 48
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java 查看文件

@@ -17,6 +17,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
17 17
 import com.facebook.react.bridge.ReadableArray;
18 18
 import com.facebook.react.bridge.ReadableMap;
19 19
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
20
+import com.facebook.react.bridge.WritableArray;
20 21
 import com.facebook.react.bridge.WritableMap;
21 22
 import com.facebook.react.modules.core.DeviceEventManagerModule;
22 23
 
@@ -32,6 +33,7 @@ import java.nio.ByteBuffer;
32 33
 import java.nio.charset.CharacterCodingException;
33 34
 import java.nio.charset.Charset;
34 35
 import java.nio.charset.CharsetEncoder;
36
+import java.util.ArrayList;
35 37
 import java.util.HashMap;
36 38
 import java.util.concurrent.TimeUnit;
37 39
 
@@ -46,10 +48,6 @@ import okhttp3.RequestBody;
46 48
 import okhttp3.Response;
47 49
 import okhttp3.ResponseBody;
48 50
 
49
-
50
-/**
51
- * Created by wkh237 on 2016/6/21.
52
- */
53 51
 public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
54 52
 
55 53
     enum RequestType  {
@@ -58,12 +56,12 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
58 56
         AsIs,
59 57
         WithoutBody,
60 58
         Others
61
-    };
59
+    }
62 60
 
63 61
     enum ResponseType {
64 62
         KeepInMemory,
65 63
         FileStorage
66
-    };
64
+    }
67 65
 
68 66
     public static HashMap<String, Call> taskTable = new HashMap<>();
69 67
     static HashMap<String, Boolean> progressReport = new HashMap<>();
@@ -87,6 +85,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
87 85
     ResponseType responseType;
88 86
     WritableMap respInfo;
89 87
     boolean timeout = false;
88
+    ArrayList<String> redirects = new ArrayList<>();
90 89
 
91 90
     public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) {
92 91
         this.method = method.toUpperCase();
@@ -104,7 +103,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
104 103
             responseType = ResponseType.KeepInMemory;
105 104
 
106 105
 
107
-		if (body != null)
106
+        if (body != null)
108 107
             requestType = RequestType.SingleFile;
109 108
         else if (arrayBody != null)
110 109
             requestType = RequestType.Form;
@@ -156,21 +155,21 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
156 155
 
157 156
         // find cached result if `key` property exists
158 157
         String cacheKey = this.taskId;
159
-		String ext = this.options.appendExt.isEmpty() ? "." + this.options.appendExt : "";
158
+        String ext = this.options.appendExt.isEmpty() ? "." + this.options.appendExt : "";
160 159
 
161
-       	if (this.options.key != null) {
162
-           cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
163
-           if (cacheKey == null) {
164
-               cacheKey = this.taskId;
165
-           }
160
+        if (this.options.key != null) {
161
+            cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
162
+            if (cacheKey == null) {
163
+                cacheKey = this.taskId;
164
+            }
166 165
 
167
-           File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext);
166
+            File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext);
168 167
 
169
-           if (file.exists()) {
170
-               callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
171
-               return;
172
-           }
173
-       }
168
+            if (file.exists()) {
169
+                callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
170
+                return;
171
+            }
172
+        }
174 173
 
175 174
         if(this.options.path != null)
176 175
             this.destPath = this.options.path;
@@ -236,35 +235,33 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
236 235
                 requestType = RequestType.WithoutBody;
237 236
             }
238 237
 
238
+            boolean isChunkedRequest = getHeaderIgnoreCases(mheaders, "Transfer-Encoding").equalsIgnoreCase("chunked");
239 239
 
240 240
             // set request body
241 241
             switch (requestType) {
242 242
                 case SingleFile:
243
-                    requestBody = new RNFetchBlobBody(
244
-                            taskId,
245
-                            requestType,
246
-                            rawRequestBody,
247
-                            MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
248
-                    );
243
+                    requestBody = new RNFetchBlobBody(taskId)
244
+                            .chunkedEncoding(isChunkedRequest)
245
+                            .setRequestType(requestType)
246
+                            .setBody(rawRequestBody)
247
+                            .setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type")));
249 248
                     builder.method(method, requestBody);
250 249
                     break;
251 250
                 case AsIs:
252
-                    requestBody = new RNFetchBlobBody(
253
-                            taskId,
254
-                            requestType,
255
-                            rawRequestBody,
256
-                            MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
257
-                    );
251
+                    requestBody = new RNFetchBlobBody(taskId)
252
+                            .chunkedEncoding(isChunkedRequest)
253
+                            .setRequestType(requestType)
254
+                            .setBody(rawRequestBody)
255
+                            .setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type")));
258 256
                     builder.method(method, requestBody);
259 257
                     break;
260 258
                 case Form:
261 259
                     String boundary = "RNFetchBlob-" + taskId;
262
-                    requestBody = new RNFetchBlobBody(
263
-                            taskId,
264
-                            requestType,
265
-                            rawRequestBodyArray,
266
-                            MediaType.parse("multipart/form-data; boundary="+ boundary)
267
-                    );
260
+                    requestBody = new RNFetchBlobBody(taskId)
261
+                            .chunkedEncoding(isChunkedRequest)
262
+                            .setRequestType(requestType)
263
+                            .setBody(rawRequestBodyArray)
264
+                            .setMIME(MediaType.parse("multipart/form-data; boundary="+ boundary));
268 265
                     builder.method(method, requestBody);
269 266
                     break;
270 267
 
@@ -279,7 +276,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
279 276
             }
280 277
 
281 278
             final Request req = builder.build();
282
-
279
+            clientBuilder.addNetworkInterceptor(new Interceptor() {
280
+                @Override
281
+                public Response intercept(Chain chain) throws IOException {
282
+                        redirects.add(chain.request().url().toString());
283
+                        return chain.proceed(chain.request());
284
+                    }
285
+            });
283 286
             // Add request interceptor for upload progress event
284 287
             clientBuilder.addInterceptor(new Interceptor() {
285 288
                 @Override
@@ -334,6 +337,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
334 337
             clientBuilder.retryOnConnectionFailure(false);
335 338
             clientBuilder.followRedirects(true);
336 339
 
340
+
337 341
             OkHttpClient client = clientBuilder.retryOnConnectionFailure(true).build();
338 342
             Call call =  client.newCall(req);
339 343
             taskTable.put(taskId, call);
@@ -414,7 +418,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
414 418
                 try {
415 419
                     // For XMLHttpRequest, automatic response data storing strategy, when response
416 420
                     // data is considered as binary data, write it to file system
417
-                    if(isBlobResp && options.auto == true) {
421
+                    if(isBlobResp && options.auto) {
418 422
                         String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
419 423
                         InputStream ins = resp.body().byteStream();
420 424
                         FileOutputStream os = new FileOutputStream(new File(dest));
@@ -458,7 +462,9 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
458 462
                     // It uses customized response body which is able to report download progress
459 463
                     // and write response data to destination path.
460 464
                     resp.body().bytes();
461
-                } catch (Exception ignored) {  }
465
+                } catch (Exception ignored) {
466
+                    ignored.printStackTrace();
467
+                }
462 468
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
463 469
                 break;
464 470
             default:
@@ -469,9 +475,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
469 475
                 }
470 476
                 break;
471 477
         }
472
-//        if(!resp.isSuccessful())
473
-//            resp.body().close();
474
-        resp.body().close();
478
+        if(!resp.isSuccessful())
479
+            resp.body().close();
475 480
         releaseTaskResource();
476 481
     }
477 482
 
@@ -496,10 +501,10 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
496 501
     }
497 502
 
498 503
     /**
499
-     * Create response information object, conatins status code, headers, etc.
500
-     * @param resp
501
-     * @param isBlobResp
502
-     * @return
504
+     * Create response information object, contains status code, headers, etc.
505
+     * @param resp Response object
506
+     * @param isBlobResp If the response is binary data
507
+     * @return Get RCT bridge object contains response information.
503 508
      */
504 509
     private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
505 510
         WritableMap info = Arguments.createMap();
@@ -511,6 +516,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
511 516
         for(int i =0;i< resp.headers().size();i++) {
512 517
             headers.putString(resp.headers().name(i), resp.headers().value(i));
513 518
         }
519
+        WritableArray redirectList = Arguments.createArray();
520
+        for(String r : redirects) {
521
+                redirectList.pushString(r);
522
+        }
523
+        info.putArray("redirects", redirectList);
514 524
         info.putMap("headers", headers);
515 525
         Headers h = resp.headers();
516 526
         if(isBlobResp) {
@@ -531,7 +541,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
531 541
     /**
532 542
      * Check if response data is binary data.
533 543
      * @param resp OkHttp response.
534
-     * @return
544
+     * @return If the response data contains binary bytes
535 545
      */
536 546
     private boolean isBlobResponse(Response resp) {
537 547
         Headers h = resp.headers();

+ 1
- 3
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java 查看文件

@@ -16,9 +16,7 @@ import javax.net.ssl.X509TrustManager;
16 16
 
17 17
 import okhttp3.OkHttpClient;
18 18
 
19
-/**
20
- * Created by wkh237 on 2016/7/11.
21
- */
19
+
22 20
 public class RNFetchBlobUtils {
23 21
 
24 22
     public static String getMD5(String input) {

+ 12
- 12
src/class/RNFetchBlobReadStream.js 查看文件

@@ -8,6 +8,7 @@ import {
8 8
   DeviceEventEmitter,
9 9
   NativeAppEventEmitter,
10 10
 } from 'react-native'
11
+import UUID from '../utils/uuid'
11 12
 
12 13
 const RNFetchBlob = NativeModules.RNFetchBlob
13 14
 const emitter = DeviceEventEmitter
@@ -18,24 +19,28 @@ export default class RNFetchBlobReadStream {
18 19
   encoding : 'utf8' | 'ascii' | 'base64';
19 20
   bufferSize : ?number;
20 21
   closed : boolean;
22
+  tick : number = 10;
21 23
 
22
-  constructor(path:string, encoding:string, bufferSize?:?number) {
24
+  constructor(path:string, encoding:string, bufferSize?:?number, tick:number) {
23 25
     if(!path)
24 26
       throw Error('RNFetchBlob could not open file stream with empty `path`')
25 27
     this.encoding = encoding || 'utf8'
26 28
     this.bufferSize = bufferSize
27 29
     this.path = path
28 30
     this.closed = false
31
+    this.tick = tick
29 32
     this._onData = () => {}
30 33
     this._onEnd = () => {}
31 34
     this._onError = () => {}
35
+    this.streamId = 'RNFBRS'+ UUID()
32 36
 
33 37
     // register for file stream event
34
-    let subscription = emitter.addListener(`RNFetchBlobStream+${this.path}`, (e) => {
35
-
38
+    let subscription = emitter.addListener(this.streamId, (e) => {
36 39
       let {event, detail} = e
37
-      if(this._onData && event === 'data')
40
+      if(this._onData && event === 'data') {
38 41
         this._onData(detail)
42
+        return
43
+      }
39 44
       else if (this._onEnd && event === 'end') {
40 45
         this._onEnd(detail)
41 46
       }
@@ -56,18 +61,13 @@ export default class RNFetchBlobReadStream {
56 61
 
57 62
   open() {
58 63
     if(!this.closed)
59
-      RNFetchBlob.readStream(this.path, this.encoding, this.bufferSize || 0)
64
+      RNFetchBlob.readStream(this.path, this.encoding, this.bufferSize || 10240 , this.tick || -1, this.streamId)
60 65
     else
61 66
       throw new Error('Stream closed')
62 67
   }
63 68
 
64
-  onData(fn) {
65
-    if(this.encoding.toLowerCase() === 'ascii')
66
-      this._onData = (data) => {
67
-        fn(data)
68
-      }
69
-    else
70
-      this._onData = fn
69
+  onData(fn:() => void) {
70
+    this._onData = fn
71 71
   }
72 72
 
73 73
   onError(fn) {

+ 3
- 2
src/fs.js 查看文件

@@ -113,9 +113,10 @@ function writeStream(
113 113
 function readStream(
114 114
   path : string,
115 115
   encoding : 'utf8' | 'ascii' | 'base64',
116
-  bufferSize? : ?number
116
+  bufferSize? : ?number,
117
+  tick : ?number = 10
117 118
 ):Promise<RNFetchBlobReadStream> {
118
-  return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize))
119
+  return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
119 120
 }
120 121
 
121 122
 /**

+ 6
- 18
src/index.js 查看文件

@@ -261,9 +261,7 @@ function fetch(...args:any):Promise {
261 261
       delete promise['uploadProgress']
262 262
       delete promise['stateChange']
263 263
       delete promise['cancel']
264
-      promise.cancel = () => {
265
-        console.warn('finished request could not be canceled')
266
-      }
264
+      promise.cancel = () => {}
267 265
 
268 266
       if(err)
269 267
         reject(new Error(err, respInfo))
@@ -320,14 +318,14 @@ class FetchBlobResponse {
320 318
   path : () => string | null;
321 319
   type : 'base64' | 'path' | 'utf8';
322 320
   data : any;
323
-  blob : (contentType:string, sliceSize:number) => null;
324
-  text : () => string;
321
+  blob : (contentType:string, sliceSize:number) => Promise<Blob>;
322
+  text : () => string | Promise<any>;
325 323
   json : () => any;
326 324
   base64 : () => any;
327 325
   flush : () => void;
328 326
   respInfo : RNFetchBlobResponseInfo;
329 327
   session : (name:string) => RNFetchBlobSession | null;
330
-  readFile : (encode: 'base64' | 'utf8' | 'ascii') => ?Promise;
328
+  readFile : (encode: 'base64' | 'utf8' | 'ascii') => ?Promise<any>;
331 329
   readStream : (
332 330
     encode: 'utf8' | 'ascii' | 'base64',
333 331
   ) => RNFetchBlobStream | null;
@@ -366,18 +364,15 @@ class FetchBlobResponse {
366 364
      * Convert result to text.
367 365
      * @return {string} Decoded base64 string.
368 366
      */
369
-    this.text = ():string => {
367
+    this.text = ():string | Promise<any> => {
370 368
       let res = this.data
371 369
       switch(this.type) {
372 370
         case 'base64':
373 371
           return base64.decode(this.data)
374
-        break
375 372
         case 'path':
376 373
           return fs.readFile(this.data, 'base64').then((b64) => Promise.resolve(base64.decode(b64)))
377
-        break
378 374
         default:
379 375
           return this.data
380
-        break
381 376
       }
382 377
     }
383 378
     /**
@@ -388,31 +383,25 @@ class FetchBlobResponse {
388 383
       switch(this.type) {
389 384
         case 'base64':
390 385
           return JSON.parse(base64.decode(this.data))
391
-        break
392 386
         case 'path':
393 387
           return fs.readFile(this.data, 'utf8')
394 388
                    .then((text) => Promise.resolve(JSON.parse(text)))
395
-        break
396 389
         default:
397 390
           return JSON.parse(this.data)
398
-        break
399 391
       }
400 392
     }
401 393
     /**
402 394
      * Return BASE64 string directly.
403 395
      * @return {string} BASE64 string of response body.
404 396
      */
405
-    this.base64 = ():string => {
397
+    this.base64 = ():string | Promise<any> => {
406 398
       switch(this.type) {
407 399
         case 'base64':
408 400
           return this.data
409
-        break
410 401
         case 'path':
411 402
           return fs.readFile(this.data, 'base64')
412
-        break
413 403
         default:
414 404
           return base64.encode(this.data)
415
-        break
416 405
       }
417 406
     }
418 407
     /**
@@ -467,7 +456,6 @@ class FetchBlobResponse {
467 456
     this.readFile = (encode: 'base64' | 'utf8' | 'ascii') => {
468 457
       if(this.type === 'path') {
469 458
         encode = encode || 'utf8'
470
-
471 459
         return readFile(this.data, encode)
472 460
       }
473 461
       else {

+ 27
- 0
src/ios/IOS7Polyfill.h 查看文件

@@ -0,0 +1,27 @@
1
+//
2
+//  IOS7Polyfill.h
3
+//  RNFetchBlob
4
+//
5
+//  Created by Ben Hsieh on 2016/9/6.
6
+//  Copyright © 2016年 wkh237.github.io. All rights reserved.
7
+//
8
+
9
+#ifndef IOS7Polyfill_h
10
+#define IOS7Polyfill_h
11
+
12
+@interface NSString (Contains)
13
+
14
+- (BOOL)RNFBContainsString:(NSString*)other;
15
+
16
+@end
17
+
18
+@implementation NSString (Contains)
19
+
20
+- (BOOL)RNFBContainsString:(NSString*)other {
21
+    NSRange range = [self rangeOfString:other];
22
+    return range.length != 0;
23
+}
24
+
25
+
26
+@end
27
+#endif /* IOS7Polyfill_h */

+ 3
- 1
src/ios/RNFetchBlob.xcodeproj/project.pbxproj 查看文件

@@ -39,6 +39,7 @@
39 39
 		A15C30131CD25C330074CB35 /* RNFetchBlob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFetchBlob.m; path = RNFetchBlob/RNFetchBlob.m; sourceTree = "<group>"; };
40 40
 		A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobReqBuilder.h; sourceTree = "<group>"; };
41 41
 		A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobReqBuilder.m; sourceTree = "<group>"; };
42
+		A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOS7Polyfill.h; sourceTree = "<group>"; };
42 43
 /* End PBXFileReference section */
43 44
 
44 45
 /* Begin PBXFrameworksBuildPhase section */
@@ -62,6 +63,7 @@
62 63
 		A15C30051CD25C330074CB35 = {
63 64
 			isa = PBXGroup;
64 65
 			children = (
66
+				A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */,
65 67
 				A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */,
66 68
 				A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */,
67 69
 				A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */,
@@ -112,7 +114,7 @@
112 114
 			isa = PBXProject;
113 115
 			attributes = {
114 116
 				LastUpgradeCheck = 730;
115
-				ORGANIZATIONNAME = suzuri04x2;
117
+				ORGANIZATIONNAME = wkh237.github.io;
116 118
 				TargetAttributes = {
117 119
 					A15C300D1CD25C330074CB35 = {
118 120
 						CreatedOnToolsVersion = 7.3;

+ 2
- 1
src/ios/RNFetchBlob/RNFetchBlob.h 查看文件

@@ -17,7 +17,8 @@
17 17
 }
18 18
 
19 19
 @property (nonatomic) NSString * filePathPrefix;
20
-@property (nonatomic, strong) UIDocumentInteractionController *documentController;
20
+
21
++ (RCTBridge *)getRCTBridge;
21 22
 
22 23
 @end
23 24
 

+ 107
- 46
src/ios/RNFetchBlob/RNFetchBlob.m 查看文件

@@ -5,7 +5,6 @@
5 5
 //
6 6
 
7 7
 #import "RNFetchBlob.h"
8
-#import "RCTConvert.h"
9 8
 #import "RCTLog.h"
10 9
 #import "RCTBridge.h"
11 10
 #import "RCTEventDispatcher.h"
@@ -15,6 +14,10 @@
15 14
 #import "RNFetchBlobReqBuilder.h"
16 15
 
17 16
 
17
+RCTBridge * bridgeRef;
18
+dispatch_queue_t commonTaskQueue;
19
+dispatch_queue_t fsQueue;
20
+
18 21
 ////////////////////////////////////////
19 22
 //
20 23
 //  Exported native methods
@@ -30,7 +33,14 @@
30 33
 @synthesize bridge = _bridge;
31 34
 
32 35
 - (dispatch_queue_t) methodQueue {
33
-    return dispatch_queue_create("RNFetchBlob.queue", DISPATCH_QUEUE_SERIAL);
36
+    if(commonTaskQueue == nil)
37
+        commonTaskQueue = dispatch_queue_create("RNFetchBlob.queue", DISPATCH_QUEUE_SERIAL);
38
+    return commonTaskQueue;
39
+}
40
+
41
++ (RCTBridge *)getRCTBridge
42
+{
43
+    return bridgeRef;
34 44
 }
35 45
 
36 46
 RCT_EXPORT_MODULE();
@@ -38,11 +48,16 @@ RCT_EXPORT_MODULE();
38 48
 - (id) init {
39 49
     self = [super init];
40 50
     self.filePathPrefix = FILE_PREFIX;
51
+    if(commonTaskQueue == nil)
52
+        commonTaskQueue = dispatch_queue_create("RNFetchBlob.queue", DISPATCH_QUEUE_SERIAL);
53
+    if(fsQueue == nil)
54
+        fsQueue = dispatch_queue_create("RNFetchBlob.fs.queue", DISPATCH_QUEUE_SERIAL);
41 55
     BOOL isDir;
42 56
     // if temp folder not exists, create one
43 57
     if(![[NSFileManager defaultManager] fileExistsAtPath: [RNFetchBlobFS getTempPath] isDirectory:&isDir]) {
44 58
         [[NSFileManager defaultManager] createDirectoryAtPath:[RNFetchBlobFS getTempPath] withIntermediateDirectories:YES attributes:nil error:NULL];
45 59
     }
60
+    bridgeRef = _bridge;
46 61
     return self;
47 62
 }
48 63
 
@@ -87,6 +102,7 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
87 102
     }];
88 103
 }
89 104
 
105
+#pragma mark - fs.createFile
90 106
 RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) {
91 107
 
92 108
     NSFileManager * fm = [NSFileManager defaultManager];
@@ -113,7 +129,7 @@ RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NS
113 129
         callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
114 130
 
115 131
 }
116
-
132
+#pragma mark - fs.createFileASCII
117 133
 // method for create file with ASCII content
118 134
 RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray callback:(RCTResponseSenderBlock)callback) {
119 135
 
@@ -135,24 +151,22 @@ RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray cal
135 151
 
136 152
 }
137 153
 
138
-
154
+#pragma mark - fs.exists
139 155
 RCT_EXPORT_METHOD(exists:(NSString *)path callback:(RCTResponseSenderBlock)callback) {
140
-    BOOL isDir = NO;
141
-    BOOL exists = NO;
142
-    exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
143
-    callback(@[@(exists), @(isDir)]);
144
-
156
+    [RNFetchBlobFS exists:path callback:callback];
145 157
 }
146 158
 
159
+#pragma mark - fs.writeFile
147 160
 RCT_EXPORT_METHOD(writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
148
-    
149 161
     [RNFetchBlobFS writeFile:path encoding:[NSString stringWithString:encoding] data:data append:append resolver:resolve rejecter:reject];
150 162
 })
151 163
 
164
+#pragma mark - fs.writeArray
152 165
 RCT_EXPORT_METHOD(writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
153 166
     [RNFetchBlobFS writeFileArray:path data:data append:append resolver:resolve rejecter:reject];
154 167
 })
155 168
 
169
+#pragma mark - fs.writeStream
156 170
 RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding appendData:(BOOL)append callback:(RCTResponseSenderBlock)callback) {
157 171
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
158 172
     NSFileManager * fm = [NSFileManager defaultManager];
@@ -166,6 +180,7 @@ RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding
166 180
     callback(@[[NSNull null], streamId]);
167 181
 }
168 182
 
183
+#pragma mark - fs.writeArrayChunk
169 184
 RCT_EXPORT_METHOD(writeArrayChunk:(NSString *)streamId withArray:(NSArray *)dataArray callback:(RCTResponseSenderBlock) callback) {
170 185
     RNFetchBlobFS *fs = [[RNFetchBlobFS getFileStreams] valueForKey:streamId];
171 186
     char * bytes = (char *) malloc([dataArray count]);
@@ -179,18 +194,21 @@ RCT_EXPORT_METHOD(writeArrayChunk:(NSString *)streamId withArray:(NSArray *)data
179 194
     callback(@[[NSNull null]]);
180 195
 }
181 196
 
197
+#pragma mark - fs.writeChunk
182 198
 RCT_EXPORT_METHOD(writeChunk:(NSString *)streamId withData:(NSString *)data callback:(RCTResponseSenderBlock) callback) {
183 199
     RNFetchBlobFS *fs = [[RNFetchBlobFS getFileStreams] valueForKey:streamId];
184 200
     [fs writeEncodeChunk:data];
185 201
     callback(@[[NSNull null]]);
186 202
 }
187 203
 
204
+#pragma mark - fs.closeStream
188 205
 RCT_EXPORT_METHOD(closeStream:(NSString *)streamId callback:(RCTResponseSenderBlock) callback) {
189 206
     RNFetchBlobFS *fs = [[RNFetchBlobFS getFileStreams] valueForKey:streamId];
190 207
     [fs closeOutStream];
191 208
     callback(@[[NSNull null], @YES]);
192 209
 }
193 210
 
211
+#pragma mark - unlink
194 212
 RCT_EXPORT_METHOD(unlink:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
195 213
     NSError * error = nil;
196 214
     NSString * tmpPath = nil;
@@ -201,6 +219,7 @@ RCT_EXPORT_METHOD(unlink:(NSString *)path callback:(RCTResponseSenderBlock) call
201 219
         callback(@[[NSString stringWithFormat:@"failed to unlink file or path at %@", path]]);
202 220
 }
203 221
 
222
+#pragma mark - fs.removeSession
204 223
 RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBlock) callback) {
205 224
     NSError * error = nil;
206 225
     NSString * tmpPath = nil;
@@ -216,6 +235,7 @@ RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBloc
216 235
 
217 236
 }
218 237
 
238
+#pragma mark - fs.ls
219 239
 RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
220 240
     NSFileManager* fm = [NSFileManager defaultManager];
221 241
     BOOL exist = nil;
@@ -235,28 +255,46 @@ RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback
235 255
 
236 256
 }
237 257
 
238
-RCT_EXPORT_METHOD(stat:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
239
-    NSFileManager* fm = [NSFileManager defaultManager];
240
-    BOOL exist = nil;
241
-    BOOL isDir = nil;
242
-    NSError * error = nil;
243
-
244
-    path = [RNFetchBlobFS getPathOfAsset:path];
245
-
246
-    exist = [fm fileExistsAtPath:path isDirectory:&isDir];
247
-    if(exist == NO) {
248
-        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not exist", path]]);
249
-        return ;
250
-    }
251
-    NSData * res = [RNFetchBlobFS stat:path error:&error];
252
-
253
-    if(error == nil)
254
-        callback(@[[NSNull null], res]);
255
-    else
256
-        callback(@[[error localizedDescription], [NSNull null]]);
258
+#pragma mark - fs.stat
259
+RCT_EXPORT_METHOD(stat:(NSString *)target callback:(RCTResponseSenderBlock) callback) {
260
+    
261
+    [RNFetchBlobFS getPathFromUri:target completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
262
+        __block NSMutableArray * result;
263
+        if(path != nil)
264
+        {
265
+            NSFileManager* fm = [NSFileManager defaultManager];
266
+            BOOL exist = nil;
267
+            BOOL isDir = nil;
268
+            NSError * error = nil;
269
+            
270
+            exist = [fm fileExistsAtPath:path isDirectory:&isDir];
271
+            if(exist == NO) {
272
+                callback(@[[NSString stringWithFormat:@"failed to stat path `%@` for it is not exist or it is not exist", path]]);
273
+                return ;
274
+            }
275
+            result = [RNFetchBlobFS stat:path error:&error];
276
+            
277
+            if(error == nil)
278
+                callback(@[[NSNull null], result]);
279
+            else
280
+                callback(@[[error localizedDescription], [NSNull null]]);
257 281
 
282
+        }
283
+        else if(asset != nil)
284
+        {
285
+            __block NSNumber * size = [NSNumber numberWithLong:[asset size]];
286
+            result = [asset metadata];
287
+            [result setValue:size forKey:@"size"];
288
+            callback(@[[NSNull null], result]);
289
+        }
290
+        else
291
+        {
292
+            callback(@[@"failed to stat path, could not resolve URI", [NSNull null]]);
293
+        }
294
+    }];
258 295
 }
259 296
 
297
+#pragma mark - fs.lstat
260 298
 RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
261 299
     NSFileManager* fm = [NSFileManager defaultManager];
262 300
     BOOL exist = nil;
@@ -290,18 +328,32 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
290 328
 
291 329
 }
292 330
 
293
-RCT_EXPORT_METHOD(cp:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
294
-    NSError * error = nil;
295
-    path = [RNFetchBlobFS getPathOfAsset:path];
296
-    BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
297
-
298
-    if(error == nil)
299
-        callback(@[[NSNull null], @YES]);
300
-    else
301
-        callback(@[[error localizedDescription], @NO]);
302
-
331
+#pragma mark - fs.cp
332
+RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
333
+    
334
+//    path = [RNFetchBlobFS getPathOfAsset:path];
335
+    [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
336
+        NSError * error = nil;
337
+        if(path == nil)
338
+        {
339
+            [RNFetchBlobFS writeAssetToPath:asset dest:dest];
340
+            callback(@[[NSNull null], @YES]);
341
+        }
342
+        else
343
+        {
344
+            BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
345
+            
346
+            if(error == nil)
347
+                callback(@[[NSNull null], @YES]);
348
+            else
349
+                callback(@[[error localizedDescription], @NO]);
350
+        }
351
+    }];
352
+    
303 353
 }
304 354
 
355
+
356
+#pragma mark - fs.mv
305 357
 RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) {
306 358
     NSError * error = nil;
307 359
     BOOL result = [[NSFileManager defaultManager] moveItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
@@ -313,8 +365,9 @@ RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTRespo
313 365
 
314 366
 }
315 367
 
368
+#pragma mark - fs.mkdir
316 369
 RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback) {
317
-    if([RNFetchBlobFS exists:path]) {
370
+    if([[NSFileManager defaultManager] fileExistsAtPath:path]) {
318 371
         callback(@[@"mkdir failed, folder already exists"]);
319 372
         return;
320 373
     }
@@ -323,24 +376,28 @@ RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callb
323 376
     callback(@[[NSNull null]]);
324 377
 }
325 378
 
379
+#pragma mark - fs.readFile
326 380
 RCT_EXPORT_METHOD(readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
327 381
 
328 382
     [RNFetchBlobFS readFile:path encoding:encoding resolver:resolve rejecter:reject onComplete:nil];
329 383
 })
330 384
 
331
-RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize) {
332
-
333
-    RNFetchBlobFS *fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
385
+#pragma mark - fs.readStream
386
+RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId
387
+{
334 388
     if(bufferSize == nil) {
335 389
         if([[encoding lowercaseString] isEqualToString:@"base64"])
336 390
             bufferSize = 4095;
337 391
         else
338 392
             bufferSize = 4096;
339 393
     }
340
-    // read asset stream
341
-    [fileStream readWithPath:path useEncoding:encoding bufferSize:bufferSize];
342
-}
394
+    
395
+    dispatch_async(fsQueue, ^{
396
+        [RNFetchBlobFS readStream:path encoding:encoding bufferSize:bufferSize tick:tick streamId:streamId bridgeRef:_bridge];
397
+    });
398
+})
343 399
 
400
+#pragma mark - fs.getEnvionmentDirs
344 401
 RCT_EXPORT_METHOD(getEnvironmentDirs:(RCTResponseSenderBlock) callback) {
345 402
 
346 403
     callback(@[
@@ -349,20 +406,24 @@ RCT_EXPORT_METHOD(getEnvironmentDirs:(RCTResponseSenderBlock) callback) {
349 406
                ]);
350 407
 }
351 408
 
409
+#pragma mark - net.cancelRequest
352 410
 RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) {
353 411
     [RNFetchBlobNetwork cancelRequest:taskId];
354 412
     callback(@[[NSNull null], taskId]);
355 413
 
356 414
 }
357 415
 
416
+#pragma mark - net.enableProgressReport
358 417
 RCT_EXPORT_METHOD(enableProgressReport:(NSString *)taskId {
359 418
     [RNFetchBlobNetwork enableProgressReport:taskId];
360 419
 })
361 420
 
421
+#pragma mark - net.enableUploadProgressReport
362 422
 RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId {
363 423
     [RNFetchBlobNetwork enableUploadProgress:taskId];
364 424
 })
365 425
 
426
+#pragma mark - fs.slice
366 427
 RCT_EXPORT_METHOD(slice:(NSString *)src dest:(NSString *)dest start:(nonnull NSNumber *)start end:(nonnull NSNumber *)end resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
367 428
 {
368 429
     [RNFetchBlobFS slice:src dest:dest start:start end:end encode:@"" resolver:resolve rejecter:reject];

+ 4
- 4
src/ios/RNFetchBlobFS.h 查看文件

@@ -50,12 +50,12 @@
50 50
 + (RNFetchBlobFS *) getFileStreams;
51 51
 + (BOOL) mkdir:(NSString *) path;
52 52
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
53
-+ (BOOL) exists:(NSString *) path;
53
++ (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
54 54
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
55 55
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
56 56
 + (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject onComplete:(void (^)(NSData * content))onComplete;
57 57
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
58
-+ (void)slice:(NSString *)path
58
++ (void) slice:(NSString *)path
59 59
          dest:(NSString *)dest
60 60
         start:(nonnull NSNumber *)start
61 61
           end:(nonnull NSNumber *)end
@@ -63,6 +63,8 @@
63 63
      resolver:(RCTPromiseResolveBlock)resolve
64 64
      rejecter:(RCTPromiseRejectBlock)reject;
65 65
 //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append;
66
++ (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest;
67
++ (void) readStream:(NSString *)uri encoding:(NSString * )encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId bridgeRef:(RCTBridge *)bridgeRef;
66 68
 
67 69
 // constructor
68 70
 - (id) init;
@@ -72,12 +74,10 @@
72 74
 // file stream
73 75
 - (void) openWithDestination;
74 76
 - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)encode appendData:(BOOL)append;
75
-- (void) startAssetReadStream:(NSData *)assetUrl;
76 77
 
77 78
 // file stream write data
78 79
 - (void)write:(NSData *) chunk;
79 80
 - (void)writeEncodeChunk:(NSString *) chunk;
80
-- (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding bufferSize:(int) bufferSize;
81 81
 
82 82
 - (void) closeInStream;
83 83
 - (void) closeOutStream;

+ 303
- 301
src/ios/RNFetchBlobFS.m 查看文件

@@ -1,4 +1,4 @@
1
-//
1
+
2 2
 //  RNFetchBlobFS.m
3 3
 //  RNFetchBlob
4 4
 //
@@ -7,13 +7,13 @@
7 7
 //
8 8
 
9 9
 
10
-#import "RCTConvert.h"
11
-#import "RCTLog.h"
12 10
 #import <Foundation/Foundation.h>
13 11
 #import "RCTBridge.h"
12
+#import "RNFetchBlob.h"
14 13
 #import "RCTEventDispatcher.h"
15 14
 #import "RNFetchBlobFS.h"
16 15
 #import "RNFetchBlobConst.h"
16
+#import "IOS7Polyfill.h"
17 17
 @import AssetsLibrary;
18 18
 
19 19
 
@@ -40,6 +40,23 @@ NSMutableDictionary *fileStreams = nil;
40 40
 @synthesize appendData;
41 41
 @synthesize bufferSize;
42 42
 
43
+- (id)init {
44
+    self = [super init];
45
+    return self;
46
+}
47
+
48
+- (id)initWithCallback:(RCTResponseSenderBlock)callback {
49
+    self = [super init];
50
+    self.callback = callback;
51
+    return self;
52
+}
53
+
54
+- (id)initWithBridgeRef:(RCTBridge *)bridgeRef {
55
+    self = [super init];
56
+    self.bridge = bridgeRef;
57
+    return self;
58
+}
59
+
43 60
 // static member getter
44 61
 + (NSArray *) getFileStreams {
45 62
     
@@ -103,115 +120,171 @@ NSMutableDictionary *fileStreams = nil;
103 120
     return tempPath;
104 121
 }
105 122
 
106
-#pragma mark - read asset stream
123
+#pragma margk - readStream 
107 124
 
108
-- (void) startAssetReadStream:(NSString *)assetUrl
125
++ (void) readStream:(NSString *)uri
126
+           encoding:(NSString * )encoding
127
+         bufferSize:(int)bufferSize
128
+               tick:(int)tick
129
+           streamId:(NSString *)streamId
130
+          bridgeRef:(RCTBridge *)bridgeRef
109 131
 {
110
-    ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
111
-    {
112
-        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
113
-        dispatch_async(queue, ^ {
114
-            NSString * streamEventCode = [NSString stringWithFormat:@"RNFetchBlobStream+%@", self.path];
115
-            ALAssetRepresentation *rep = [myasset defaultRepresentation];
116
-            Byte *buffer = (Byte*)malloc(self.bufferSize);
117
-            NSUInteger cursor = [rep getBytes:buffer fromOffset:0 length:self.bufferSize error:nil];
118
-            while(cursor > 0)
132
+    [[self class] getPathFromUri:uri completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
133
+    
134
+        __block RCTEventDispatcher * event = bridgeRef.eventDispatcher;
135
+        __block int read = 0;
136
+        __block int backoff = tick *1000;
137
+        __block int chunkSize = bufferSize;
138
+        // allocate buffer in heap instead of stack
139
+        uint8_t * buffer;
140
+        @try
141
+        {
142
+            buffer = (uint8_t *) malloc(bufferSize);
143
+            if(path != nil)
119 144
             {
120
-                cursor += [rep getBytes:buffer fromOffset:cursor length:self.bufferSize error:nil];
121
-                NSData * chunkData = [NSData dataWithBytes:buffer length:self.bufferSize];
122
-                NSString * encodedChunk = @"";
123
-                // emit data
124
-                if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
125
-                    encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
145
+                if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
146
+                {
147
+                    NSString * message = [NSString stringWithFormat:@"File not exists at path %@", path];
148
+                    NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": message };
149
+                    [event sendDeviceEventWithName:streamId body:payload];
150
+                    free(buffer);
151
+                    return ;
126 152
                 }
127
-                // when encoding is ASCII, send byte array data
128
-                else if ( [[self.encoding lowercaseString] isEqualToString:@"ascii"] ) {
129
-                    // RCTBridge only emits string data, so we have to create JSON byte array string
130
-                    NSMutableArray * asciiArray = [NSMutableArray array];
131
-                    unsigned char *bytePtr;
132
-                    if (chunkData.length > 0)
153
+                NSInputStream * stream = [[NSInputStream alloc] initWithFileAtPath:path];
154
+                [stream open];
155
+                while((read = [stream read:buffer maxLength:bufferSize]) > 0)
156
+                {
157
+                    [[self class] emitDataChunks:[NSData dataWithBytes:buffer length:read] encoding:encoding streamId:streamId event:event];
158
+                    if(tick > 0)
133 159
                     {
134
-                        bytePtr = (unsigned char *)[chunkData bytes];
135
-                        NSInteger byteLen = chunkData.length/sizeof(uint8_t);
136
-                        for (int i = 0; i < byteLen; i++)
137
-                        {
138
-                            [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
139
-                        }
160
+                        usleep(backoff);
140 161
                     }
141
-                    
142
-                    [self.bridge.eventDispatcher
143
-                     sendDeviceEventWithName:streamEventCode
144
-                     body: @{
145
-                             @"event": FS_EVENT_DATA,
146
-                             @"detail": asciiArray
147
-                             }
148
-                     ];
149
-                    return;
150 162
                 }
151
-                // convert byte array to base64 data chunks
152
-                else if ( [[self.encoding lowercaseString] isEqualToString:@"base64"] ) {
153
-                    encodedChunk = [chunkData base64EncodedStringWithOptions:0];
154
-                }
155
-                // unknown encoding, send error event
156
-                else {
157
-                    [self.bridge.eventDispatcher
158
-                     sendDeviceEventWithName:streamEventCode
159
-                     body:@{
160
-                            @"event": FS_EVENT_ERROR,
161
-                            @"detail": @"unrecognized encoding"
162
-                            }
163
-                     ];
164
-                    return;
163
+                [stream close];
164
+            }
165
+            else if (asset != nil)
166
+            {
167
+                int cursor = 0;
168
+                NSError * err;
169
+                while((read = [asset getBytes:buffer fromOffset:cursor length:bufferSize error:&err]) > 0)
170
+                {
171
+                    cursor += read;
172
+                    [[self class] emitDataChunks:[NSData dataWithBytes:buffer length:read] encoding:encoding streamId:streamId event:event];
173
+                    if(tick > 0)
174
+                    {
175
+                        usleep(backoff);
176
+                    }
165 177
                 }
166
-                
167
-                [self.bridge.eventDispatcher
168
-                 sendDeviceEventWithName:streamEventCode
169
-                 body:@{
170
-                        @"event": FS_EVENT_DATA,
171
-                        @"detail": encodedChunk
172
-                        }
173
-                 ];
174 178
             }
175
-            free(buffer);
176
-        });
179
+            else
180
+            {
181
+                NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": @"RNFetchBlob.readStream unable to resolve URI" };
182
+                [event sendDeviceEventWithName:streamId body:payload];
183
+            }
184
+            // release buffer
185
+            if(buffer != nil)
186
+                free(buffer);
187
+            
188
+        }
189
+        @catch (NSError * err)
190
+        {
191
+            NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": [NSString stringWithFormat:@"RNFetchBlob.readStream error %@", [err description]] };
192
+            [event sendDeviceEventWithName:streamId body:payload];
193
+        }
194
+        @finally
195
+        {
196
+            NSDictionary * payload = @{ @"event": FS_EVENT_END, @"detail": @"" };
197
+            [event sendDeviceEventWithName:streamId body:payload];
198
+        }
177 199
         
178
-    };
200
+    }];
179 201
     
180
-    ALAssetsLibraryAccessFailureBlock failureblock  = ^(NSError *error)
202
+}
203
+
204
+// send read stream chunks via native event emitter
205
++ (void) emitDataChunks:(NSData *)data encoding:(NSString *) encoding streamId:(NSString *)streamId event:(RCTEventDispatcher *)event
206
+{
207
+    NSString * encodedChunk = @"";
208
+    if([[encoding lowercaseString] isEqualToString:@"utf8"])
181 209
     {
182
-        
183
-    };
184
-    
185
-    if(assetUrl && [assetUrl length])
210
+        NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] };
211
+        [event sendDeviceEventWithName:streamId body:payload];
212
+    }
213
+    else if ([[encoding lowercaseString] isEqualToString:@"base64"])
186 214
     {
187
-        NSURL *asseturl = [NSURL URLWithString:assetUrl];
188
-        ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
189
-        [assetslibrary assetForURL:asseturl
190
-                       resultBlock:resultblock
191
-                      failureBlock:failureblock];
215
+        NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : [data base64EncodedStringWithOptions:0] };
216
+        [event sendDeviceEventWithName:streamId body:payload];
217
+    }
218
+    else if([[encoding lowercaseString] isEqualToString:@"ascii"])
219
+    {
220
+        // RCTBridge only emits string data, so we have to create JSON byte array string
221
+        NSMutableArray * asciiArray = [NSMutableArray array];
222
+        unsigned char *bytePtr;
223
+        if (data.length > 0)
224
+        {
225
+            bytePtr = (unsigned char *)[data bytes];
226
+            NSInteger byteLen = data.length/sizeof(uint8_t);
227
+            for (int i = 0; i < byteLen; i++)
228
+            {
229
+                [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
230
+            }
231
+        }
232
+
233
+        NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : asciiArray };
234
+        [event sendDeviceEventWithName:streamId body:payload];
192 235
     }
236
+    
237
+    
193 238
 }
194 239
 
195 240
 # pragma write file from file
196 241
 
197
-+ (NSNumber *) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append
242
++ (NSNumber *) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append callback:(void(^)(NSString * errMsg, NSNumber *size))callback
198 243
 {
199
-    NSInputStream * is = [[NSInputStream alloc] initWithFileAtPath:src];
200
-    NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:append];
201
-    [is open];
202
-    [os open];
203
-    uint8_t buffer[10240];
204
-    long written = 0;
205
-    int read = [is read:buffer maxLength:10240];
206
-    written += read;
207
-    while(read > 0) {
208
-        [os write:buffer maxLength:read];
209
-        read = [is read:buffer maxLength:10240];
210
-        written += read;
211
-    }
212
-    [os close];
213
-    [is close];
214
-    return [NSNumber numberWithLong:written];
244
+    [[self class] getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
245
+        if(path != nil)
246
+        {
247
+            __block NSInputStream * is = [[NSInputStream alloc] initWithFileAtPath:path];
248
+            __block NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:append];
249
+            [is open];
250
+            [os open];
251
+            uint8_t buffer[10240];
252
+            __block long written = 0;
253
+            int read = [is read:buffer maxLength:10240];
254
+            written += read;
255
+            while(read > 0) {
256
+                [os write:buffer maxLength:read];
257
+                read = [is read:buffer maxLength:10240];
258
+                written += read;
259
+            }
260
+            [os close];
261
+            [is close];
262
+            __block NSNumber * size = [NSNumber numberWithLong:written];
263
+            callback(nil, size);
264
+        }
265
+        else if(asset != nil)
266
+        {
267
+            
268
+            __block NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:append];
269
+            int read = 0;
270
+            int cursor = 0;
271
+            __block long written = 0;
272
+            uint8_t buffer[10240];
273
+            [os open];
274
+            while((read = [asset getBytes:buffer fromOffset:cursor length:10240 error:nil]) > 0)
275
+            {
276
+                cursor += read;
277
+                [os write:buffer maxLength:read];
278
+            }
279
+            __block NSNumber * size = [NSNumber numberWithLong:written];
280
+            [os close];
281
+            callback(nil, size);
282
+        }
283
+        else
284
+            callback(@"failed to resolve path", nil);
285
+    }];
286
+    
287
+    return 0;
215 288
 }
216 289
 
217 290
 # pragma mark - write file
@@ -230,17 +303,21 @@ NSMutableDictionary *fileStreams = nil;
230 303
             [fm createFileAtPath:path contents:nil attributes:nil];
231 304
         }
232 305
         if(err != nil) {
233
-            reject(@"RNFetchBlob writeFile Error", @"could not create file at path", path);
306
+            reject(@"RNFetchBlob writeFile Error", @"could not create file at path", nil);
234 307
             return;
235 308
         }
236 309
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
237 310
         NSData * content = nil;
238
-        if([encoding containsString:@"base64"]) {
311
+        if([encoding RNFBContainsString:@"base64"]) {
239 312
             content = [[NSData alloc] initWithBase64EncodedString:data options:0];
240 313
         }
241 314
         else if([encoding isEqualToString:@"uri"]) {
242
-            NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append];
243
-            resolve(size);
315
+            NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
316
+                if(errMsg != nil)
317
+                    reject(@"RNFetchBlob writeFile Error", errMsg, nil);
318
+                else
319
+                    resolve(size);
320
+            }];
244 321
             return;
245 322
         }
246 323
         else {
@@ -337,7 +414,7 @@ NSMutableDictionary *fileStreams = nil;
337 414
             {
338 415
                 BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
339 416
                 if(!exists) {
340
-                    reject(@"RNFetchBlobFS readFile error", @"file not exists", path);
417
+                    reject(@"RNFetchBlobFS readFile error", @"file not exists", [[NSError alloc]init]);
341 418
                     return;
342 419
                 }
343 420
                 fileContent = [NSData dataWithContentsOfFile:path];
@@ -392,7 +469,8 @@ NSMutableDictionary *fileStreams = nil;
392 469
 # pragma mark - stat
393 470
 
394 471
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error {
395
-    NSMutableDictionary *stat = [[NSMutableDictionary alloc] init];
472
+
473
+    
396 474
     BOOL isDir = NO;
397 475
     NSFileManager * fm = [NSFileManager defaultManager];
398 476
     if([fm fileExistsAtPath:path isDirectory:&isDir] == NO) {
@@ -409,30 +487,31 @@ NSMutableDictionary *fileStreams = nil;
409 487
              @"path" : path,
410 488
              @"lastModified" : [NSString stringWithFormat:@"%d", [lastModified timeIntervalSince1970]],
411 489
              @"type" : isDir ? @"directory" : @"file"
412
-             };
490
+            };
491
+    
413 492
 }
414 493
 
415 494
 # pragma mark - exists
416 495
 
417
-+ (BOOL) exists:(NSString *) path {
418
-    return [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:NULL];
419
-}
420
-
421
-- (id)init {
422
-    self = [super init];
423
-    return self;
424
-}
425
-
426
-- (id)initWithCallback:(RCTResponseSenderBlock)callback {
427
-    self = [super init];
428
-    self.callback = callback;
429
-    return self;
430
-}
431
-
432
-- (id)initWithBridgeRef:(RCTBridge *)bridgeRef {
433
-    self = [super init];
434
-    self.bridge = bridgeRef;
435
-    return self;
496
++ (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback
497
+{
498
+    [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
499
+        if(path != nil)
500
+        {
501
+            BOOL isDir = NO;
502
+            BOOL exists = NO;
503
+            exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
504
+            callback(@[@(exists), @(isDir)]);
505
+        }
506
+        else if(asset != nil)
507
+        {
508
+            callback(@[@YES, @NO]);
509
+        }
510
+        else
511
+        {
512
+            callback(@[@NO, @NO]);
513
+        }
514
+    }];
436 515
 }
437 516
 
438 517
 # pragma mark - open file stream
@@ -498,35 +577,6 @@ NSMutableDictionary *fileStreams = nil;
498 577
     
499 578
 }
500 579
 
501
-- (void)readWithPath:(NSString *)path useEncoding:(NSString *)encoding bufferSize:(int) bufferSize {
502
-    
503
-    self.inStream = [[NSInputStream alloc] initWithFileAtPath:path];
504
-    self.inStream.delegate = self;
505
-    self.encoding = encoding;
506
-    self.path = path;
507
-    self.bufferSize = bufferSize;
508
-    
509
-    if([path hasPrefix:AL_PREFIX])
510
-    {
511
-        [self startAssetReadStream:path];
512
-        return;
513
-    }
514
-    
515
-    // normalize file path
516
-    path = [[self class] getPathOfAsset:path];
517
-    
518
-    // NSStream needs a runloop so let's create a run loop for it
519
-    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
520
-    // start NSStream is a runloop
521
-    dispatch_async(queue, ^ {
522
-        [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
523
-                            forMode:NSDefaultRunLoopMode];
524
-        [inStream open];
525
-        [[NSRunLoop currentRunLoop] run];
526
-        
527
-    });
528
-}
529
-
530 580
 // Slice a file into another file, generally for support Blob implementation.
531 581
 + (void)slice:(NSString *)path
532 582
          dest:(NSString *)dest
@@ -536,53 +586,100 @@ NSMutableDictionary *fileStreams = nil;
536 586
      resolver:(RCTPromiseResolveBlock)resolve
537 587
      rejecter:(RCTPromiseRejectBlock)reject
538 588
 {
539
-    long expected = [end longValue] - [start longValue];
540
-    long read = 0;
541
-    NSFileHandle * handle = [NSFileHandle fileHandleForReadingAtPath:path];
542
-    NSFileManager * fm = [NSFileManager defaultManager];
543
-    NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
544
-    [os open];
545
-    // abort for the source file not exists
546
-    if([fm fileExistsAtPath:path] == NO)
547
-    {
548
-        reject(@"RNFetchBlob slice failed : the file does not exists", path, nil);
549
-        return;
550
-    }
551
-    long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
552
-    long max = MIN(size, [end longValue]);
553
-    
554
-    if(![fm fileExistsAtPath:dest]) {
555
-        [fm createFileAtPath:dest contents:@"" attributes:nil];
556
-    }
557
-    [handle seekToFileOffset:[start longValue]];
558
-    while(read < expected)
589
+    [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset)
559 590
     {
560
-        
561
-        NSData * chunk;
562
-        long chunkSize = 0;
563
-        if([start longValue] + read + 10240 > max)
591
+        if(path != nil)
592
+        {
593
+            long expected = [end longValue] - [start longValue];
594
+            long read = 0;
595
+            NSFileHandle * handle = [NSFileHandle fileHandleForReadingAtPath:path];
596
+            NSFileManager * fm = [NSFileManager defaultManager];
597
+            NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
598
+            [os open];
599
+            // abort for the source file not exists
600
+            if([fm fileExistsAtPath:path] == NO)
601
+            {
602
+                reject(@"RNFetchBlob slice failed : the file does not exists", path, nil);
603
+                return;
604
+            }
605
+            long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
606
+            long max = MIN(size, [end longValue]);
607
+            
608
+            if(![fm fileExistsAtPath:dest]) {
609
+                [fm createFileAtPath:dest contents:@"" attributes:nil];
610
+            }
611
+            [handle seekToFileOffset:[start longValue]];
612
+            while(read < expected)
613
+            {
614
+                
615
+                NSData * chunk;
616
+                long chunkSize = 0;
617
+                if([start longValue] + read + 10240 > max)
618
+                {
619
+                    NSLog(@"read chunk %lu", max - read - [start longValue]);
620
+                    chunkSize = max - read - [start longValue];
621
+                    chunk = [handle readDataOfLength:chunkSize];
622
+                }
623
+                else
624
+                {
625
+                    NSLog(@"read chunk %lu", 10240);
626
+                    chunkSize = 10240;
627
+                    chunk = [handle readDataOfLength:10240];
628
+                }
629
+                if([chunk length] <= 0)
630
+                    break;
631
+                long remain = expected - read;
632
+                
633
+                [os write:[chunk bytes] maxLength:chunkSize];
634
+                read += [chunk length];
635
+            }
636
+            [handle closeFile];
637
+            [os close];
638
+            resolve(dest);
639
+        }
640
+        else if (asset != nil)
564 641
         {
565
-            NSLog(@"read chunk %lu", max - read - [start longValue]);
566
-            chunkSize = max - read - [start longValue];
567
-            chunk = [handle readDataOfLength:chunkSize];
642
+            long expected = [end longValue] - [start longValue];
643
+            long read = 0;
644
+            long chunkRead = 0;
645
+            NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
646
+            [os open];
647
+            long size = asset.size;
648
+            long max = MIN(size, [end longValue]);
649
+            
650
+            while(read < expected)
651
+            {
652
+                
653
+                uint8_t * chunk[10240];
654
+                long chunkSize = 0;
655
+                if([start longValue] + read + 10240 > max)
656
+                {
657
+                    NSLog(@"read chunk %lu", max - read - [start longValue]);
658
+                    chunkSize = max - read - [start longValue];
659
+                    chunkRead = [asset getBytes:chunk fromOffset:[start longValue] + read length:chunkSize error:nil];
660
+                }
661
+                else
662
+                {
663
+                    NSLog(@"read chunk %lu", 10240);
664
+                    chunkSize = 10240;
665
+                    chunkRead = [asset getBytes:chunk fromOffset:[start longValue] + read length:chunkSize error:nil];
666
+                }
667
+                if( chunkRead <= 0)
668
+                    break;
669
+                long remain = expected - read;
670
+                
671
+                [os write:chunk maxLength:chunkSize];
672
+                read += chunkRead;
673
+            }
674
+            [os close];
675
+            resolve(dest);
568 676
         }
569 677
         else
570 678
         {
571
-            NSLog(@"read chunk %lu", 10240);
572
-            chunkSize = 10240;
573
-            chunk = [handle readDataOfLength:10240];
679
+            reject(@"slice error",  [NSString stringWithFormat: @"could not resolve URI %@", path ], nil);
574 680
         }
575
-        if([chunk length] <= 0)
576
-            break;
577
-        long remain = expected - read;
578
-    
579
-        [os write:[chunk bytes] maxLength:chunkSize];
580
-        read += [chunk length];
581
-    }
582
-    [handle closeFile];
583
-    [os close];
584
-    resolve(dest);
585
-    
681
+        
682
+    }];
586 683
 }
587 684
 
588 685
 // close file read stream
@@ -597,118 +694,6 @@ NSMutableDictionary *fileStreams = nil;
597 694
     
598 695
 }
599 696
 
600
-#pragma mark RNFetchBlobFS read stream delegate
601
-
602
-- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
603
-    
604
-    NSString * streamEventCode = [NSString stringWithFormat:@"RNFetchBlobStream+%@", self.path];
605
-    
606
-    switch(eventCode) {
607
-            
608
-            // write stream event
609
-        case NSStreamEventHasSpaceAvailable:
610
-        {
611
-            
612
-            
613
-        }
614
-            
615
-            // read stream incoming chunk
616
-        case NSStreamEventHasBytesAvailable:
617
-        {
618
-            NSMutableData * chunkData = [[NSMutableData alloc] init];
619
-            NSInteger chunkSize = 4096;
620
-            if([[self.encoding lowercaseString] isEqualToString:@"base64"])
621
-                chunkSize = 4095;
622
-            if(self.bufferSize > 0)
623
-                chunkSize = self.bufferSize;
624
-            uint8_t buf[chunkSize];
625
-            unsigned int len = 0;
626
-            len = [(NSInputStream *)stream read:buf maxLength:chunkSize];
627
-            // still have data in stream
628
-            if(len) {
629
-                [chunkData appendBytes:buf length:len];
630
-                // dispatch data event
631
-                NSString * encodedChunk = [NSString alloc];
632
-                if( [[self.encoding lowercaseString] isEqualToString:@"utf8"] ) {
633
-                    encodedChunk = [encodedChunk initWithData:chunkData encoding:NSUTF8StringEncoding];
634
-                }
635
-                // when encoding is ASCII, send byte array data
636
-                else if ( [[self.encoding lowercaseString] isEqualToString:@"ascii"] ) {
637
-                    // RCTBridge only emits string data, so we have to create JSON byte array string
638
-                    NSMutableArray * asciiArray = [NSMutableArray array];
639
-                    unsigned char *bytePtr;
640
-                    if (chunkData.length > 0)
641
-                    {
642
-                        bytePtr = (unsigned char *)[chunkData bytes];
643
-                        NSInteger byteLen = chunkData.length/sizeof(uint8_t);
644
-                        for (int i = 0; i < byteLen; i++)
645
-                        {
646
-                            [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
647
-                        }
648
-                    }
649
-                    
650
-                    [self.bridge.eventDispatcher
651
-                     sendDeviceEventWithName:streamEventCode
652
-                     body: @{
653
-                             @"event": FS_EVENT_DATA,
654
-                             @"detail": asciiArray
655
-                             }
656
-                     ];
657
-                    return;
658
-                }
659
-                // convert byte array to base64 data chunks
660
-                else if ( [[self.encoding lowercaseString] isEqualToString:@"base64"] ) {
661
-                    encodedChunk = [chunkData base64EncodedStringWithOptions:0];
662
-                }
663
-                // unknown encoding, send error event
664
-                else {
665
-                    [self.bridge.eventDispatcher
666
-                     sendDeviceEventWithName:streamEventCode
667
-                     body:@{
668
-                            @"event": FS_EVENT_ERROR,
669
-                            @"detail": @"unrecognized encoding"
670
-                            }
671
-                     ];
672
-                    return;
673
-                }
674
-                
675
-                [self.bridge.eventDispatcher
676
-                 sendDeviceEventWithName:streamEventCode
677
-                 body:@{
678
-                        @"event": FS_EVENT_DATA,
679
-                        @"detail": encodedChunk
680
-                        }
681
-                 ];
682
-            }
683
-            // end of stream
684
-            else {
685
-                [self.bridge.eventDispatcher
686
-                 sendDeviceEventWithName:streamEventCode
687
-                 body:@{
688
-                        @"event": FS_EVENT_END,
689
-                        @"detail": @""
690
-                        }
691
-                 ];
692
-            }
693
-            break;
694
-        }
695
-            
696
-            // stream error
697
-        case NSStreamEventErrorOccurred:
698
-        {
699
-            [self.bridge.eventDispatcher
700
-             sendDeviceEventWithName:streamEventCode
701
-             body:@{
702
-                    @"event": FS_EVENT_ERROR,
703
-                    @"detail": @"RNFetchBlob error when read file with stream, file may not exists"
704
-                    }
705
-             ];
706
-            break;
707
-        }
708
-            
709
-    }
710
-    
711
-}
712 697
 
713 698
 # pragma mark - get absolute path of resource
714 699
 
@@ -733,4 +718,21 @@ NSMutableDictionary *fileStreams = nil;
733 718
     }
734 719
 }
735 720
 
721
++ (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest
722
+{
723
+    int read = 0;
724
+    int cursor = 0;
725
+    Byte * buffer = (Byte *)malloc(10240);
726
+    NSOutputStream * ostream = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
727
+    [ostream open];
728
+    while((read = [rep getBytes:buffer fromOffset:cursor length:10240 error:nil]) > 0)
729
+    {
730
+        cursor+=10240;
731
+        [ostream write:buffer maxLength:read];
732
+    }
733
+    [ostream close];
734
+    free(buffer);
735
+    return;
736
+}
737
+
736 738
 @end

+ 15
- 8
src/ios/RNFetchBlobNetwork.m 查看文件

@@ -6,7 +6,6 @@
6 6
 //  Copyright © 2016 wkh237. All rights reserved.
7 7
 //
8 8
 
9
-#import "RCTConvert.h"
10 9
 #import "RCTLog.h"
11 10
 #import <Foundation/Foundation.h>
12 11
 #import "RCTBridge.h"
@@ -15,6 +14,7 @@
15 14
 #import "RNFetchBlobNetwork.h"
16 15
 #import "RNFetchBlobConst.h"
17 16
 #import "RNFetchBlobReqBuilder.h"
17
+#import "IOS7Polyfill.h"
18 18
 #import <CommonCrypto/CommonDigest.h>
19 19
 
20 20
 ////////////////////////////////////////
@@ -36,7 +36,7 @@ NSMutableDictionary * uploadProgressTable;
36 36
     long bodyLength;
37 37
     NSMutableDictionary * respInfo;
38 38
     NSInteger respStatus;
39
-    BOOL isInrement;
39
+    NSMutableArray * redirects;
40 40
 }
41 41
 
42 42
 @end
@@ -126,7 +126,8 @@ NSOperationQueue *taskQueue;
126 126
     self.expectedBytes = 0;
127 127
     self.receivedBytes = 0;
128 128
     self.options = options;
129
-    isInrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue];
129
+    redirects = [[NSMutableArray alloc] init];
130
+    [redirects addObject:req.URL.absoluteString];
130 131
 
131 132
     NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
132 133
     NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
@@ -207,11 +208,11 @@ NSOperationQueue *taskQueue;
207 208
         if(respCType != nil)
208 209
         {
209 210
             NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
210
-            if([respCType containsString:@"text/"])
211
+            if([respCType RNFBContainsString:@"text/"])
211 212
             {
212 213
                 respType = @"text";
213 214
             }
214
-            else if([respCType containsString:@"application/json"])
215
+            else if([respCType RNFBContainsString:@"application/json"])
215 216
             {
216 217
                 respType = @"json";
217 218
             }
@@ -219,7 +220,7 @@ NSOperationQueue *taskQueue;
219 220
             else if( extraBlobCTypes !=  nil) {
220 221
                 for(NSString * substr in extraBlobCTypes)
221 222
                 {
222
-                    if([respCType containsString:[substr lowercaseString]])
223
+                    if([respCType RNFBContainsString:[substr lowercaseString]])
223 224
                     {
224 225
                         respType = @"blob";
225 226
                         respFile = YES;
@@ -244,6 +245,7 @@ NSOperationQueue *taskQueue;
244 245
                      @"taskId": taskId,
245 246
                      @"state": @"2",
246 247
                      @"headers": headers,
248
+                     @"redirects": redirects,
247 249
                      @"respType" : respType,
248 250
                      @"timeout" : @NO,
249 251
                      @"status": [NSString stringWithFormat:@"%d", statusCode ]
@@ -287,12 +289,12 @@ NSOperationQueue *taskQueue;
287 289
     NSNumber * received = [NSNumber numberWithLong:[data length]];
288 290
     receivedBytes += [received longValue];
289 291
     NSString * chunkString = @"";
290
-    
292
+
291 293
     if(isInrement == YES)
292 294
     {
293 295
         chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
294 296
     }
295
-    
297
+
296 298
     if(respFile == NO)
297 299
     {
298 300
         [respData appendData:data];
@@ -450,5 +452,10 @@ NSOperationQueue *taskQueue;
450 452
     }
451 453
 }
452 454
 
455
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
456
+{
457
+    [redirects addObject:[request.URL absoluteString]];
458
+    completionHandler(request);
459
+}
453 460
 
454 461
 @end

+ 28
- 9
src/ios/RNFetchBlobReqBuilder.m 查看文件

@@ -12,6 +12,7 @@
12 12
 #import "RNFetchBlobConst.h"
13 13
 #import "RNFetchBlobFS.h"
14 14
 #import "RCTLog.h"
15
+#import "IOS7Polyfill.h"
15 16
 
16 17
 @interface RNFetchBlobReqBuilder()
17 18
 {
@@ -36,12 +37,12 @@
36 37
     
37 38
     // send request
38 39
     __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString: encodedUrl]];
39
-    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
40
+    __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]];
40 41
     NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
41 42
     NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
42 43
 
43 44
     // generate boundary
44
-    NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
45
+    __block NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
45 46
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
46 47
         __block NSMutableData * postData = [[NSMutableData alloc] init];
47 48
         // combine multipart/form-data body
@@ -90,6 +91,7 @@
90 91
             // generate octet-stream body
91 92
             if(body != nil) {
92 93
                 __block NSString * cType = [[self class] getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders];
94
+                __block NSString * transferEncoding = [[self class] getHeaderIgnoreCases:@"transfer-encoding" fromHeaders:mheaders];
93 95
                 // when headers does not contain a key named "content-type" (case ignored), use default content type
94 96
                 if(cType == nil)
95 97
                 {
@@ -111,14 +113,21 @@
111 113
                         return;
112 114
                     }
113 115
                     size = [[[NSFileManager defaultManager] attributesOfItemAtPath:orgPath error:nil] fileSize];
114
-                    [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]];
116
+                    if(transferEncoding != nil && [[transferEncoding lowercaseString] isEqualToString:@"chunked"])
117
+                    {
118
+                        [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]];
119
+                    }
120
+                    else
121
+                    {
122
+                        [request setHTTPBody:[NSData dataWithContentsOfFile:orgPath ]];
123
+                    }
115 124
                 }
116 125
                 // otherwise convert it as BASE64 data string
117 126
                 else {
118 127
                     
119 128
                     __block NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders];
120 129
                     // when content-type is application/octet* decode body string using BASE64 decoder
121
-                    if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] containsString:@";base64"])
130
+                    if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] RNFBContainsString:@";base64"])
122 131
                     {
123 132
                         __block NSString * ncType = [[cType stringByReplacingOccurrencesOfString:@";base64" withString:@""]stringByReplacingOccurrencesOfString:@";BASE64" withString:@""];
124 133
                         if([mheaders valueForKey:@"content-type"] != nil)
@@ -148,7 +157,7 @@
148 157
 
149 158
 +(void) buildFormBody:(NSArray *)form boundary:(NSString *)boundary onComplete:(void(^)(NSData * formData))onComplete
150 159
 {
151
-    NSMutableData * formData = [[NSMutableData alloc] init];
160
+    __block NSMutableData * formData = [[NSMutableData alloc] init];
152 161
     if(form == nil)
153 162
         onComplete(nil);
154 163
     else
@@ -159,7 +168,7 @@
159 168
         void __block (^getFieldData)(id field) = ^(id field)
160 169
         {
161 170
             NSString * name = [field valueForKey:@"name"];
162
-            NSString * content = [field valueForKey:@"data"];
171
+            __block NSString * content = [field valueForKey:@"data"];
163 172
             NSString * contentType = [field valueForKey:@"type"];
164 173
             // skip when the form field `name` or `data` is empty
165 174
             if(content == nil || name == nil)
@@ -197,10 +206,14 @@
197 206
                             i++;
198 207
                             if(i < count)
199 208
                             {
200
-                                getFieldData([form objectAtIndex:i]);
209
+                                __block NSDictionary * nextField = [form objectAtIndex:i];
210
+                                getFieldData(nextField);
201 211
                             }
202 212
                             else
213
+                            {
203 214
                                 onComplete(formData);
215
+                                getFieldData = nil;
216
+                            }
204 217
                         }];
205 218
                         return ;
206 219
                     }
@@ -213,17 +226,23 @@
213 226
                 [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]];
214 227
                 [formData appendData:blobData];
215 228
                 [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
229
+                blobData = nil;
216 230
             }
217 231
             i++;
218 232
             if(i < count)
219 233
             {
220
-                getFieldData([form objectAtIndex:i]);
234
+                __block NSDictionary * nextField = [form objectAtIndex:i];
235
+                getFieldData(nextField);
221 236
             }
222 237
             else
238
+            {
223 239
                 onComplete(formData);
240
+                getFieldData = nil;
241
+            }
224 242
 
225 243
         };
226
-        getFieldData([form objectAtIndex:i]);
244
+        __block NSDictionary * nextField = [form objectAtIndex:i];
245
+        getFieldData(nextField);
227 246
     }
228 247
 }
229 248
 

+ 3
- 2
src/package.json 查看文件

@@ -1,13 +1,14 @@
1 1
 {
2 2
   "name": "react-native-fetch-blob",
3
-  "version": "0.9.4-beta.1",
3
+  "version": "0.9.4",
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": {
7 7
     "test": "echo \"Error: no test specified\" && exit 1"
8 8
   },
9 9
   "dependencies": {
10
-    "base-64": "0.1.0"
10
+    "base-64": "0.1.0",
11
+    "glob": "^7.0.6"
11 12
   },
12 13
   "keywords": [
13 14
     "react-native",

+ 22
- 6
src/polyfill/Blob.js 查看文件

@@ -50,6 +50,13 @@ export default class Blob extends EventTarget {
50 50
     return this._ref
51 51
   }
52 52
 
53
+  static setLog(level:number) {
54
+    if(number === -1)
55
+      log.disable()
56
+    else
57
+      log.level(level)
58
+  }
59
+
53 60
   /**
54 61
    * RNFetchBlob Blob polyfill, create a Blob directly from file path, BASE64
55 62
    * encoded data, and string. The conversion is done implicitly according to
@@ -58,8 +65,10 @@ export default class Blob extends EventTarget {
58 65
    * @param  {any} data Content of Blob object
59 66
    * @param  {any} mime Content type settings of Blob object, `text/plain`
60 67
    *                    by default
68
+   * @param  {boolean} defer When this argument set to `true`, blob constructor
69
+   *                         will not invoke blob created event automatically.
61 70
    */
62
-  constructor(data:any, cType:any) {
71
+  constructor(data:any, cType:any, defer:boolean) {
63 72
     super()
64 73
     cType = cType || {}
65 74
     this.cacheName = getBlobName()
@@ -75,6 +84,7 @@ export default class Blob extends EventTarget {
75 84
       let size = 0
76 85
       this._ref = String(data.getRNFetchBlobRef())
77 86
       let orgPath = this._ref
87
+
78 88
       p = fs.exists(orgPath)
79 89
             .then((exist) =>  {
80 90
               if(exist)
@@ -121,10 +131,14 @@ export default class Blob extends EventTarget {
121 131
       log.verbose('create Blob cache file from file path', data)
122 132
       this._ref = String(data).replace('RNFetchBlob-file://', '')
123 133
       let orgPath = this._ref
124
-      p = fs.stat(orgPath)
125
-            .then((stat) =>  {
134
+      if(defer)
135
+        return
136
+      else {
137
+        p = fs.stat(orgPath)
138
+              .then((stat) =>  {
126 139
                 return Promise.resolve(stat.size)
127
-            })
140
+              })
141
+      }
128 142
     }
129 143
     // content from variable need create file
130 144
     else if(typeof data === 'string') {
@@ -217,7 +231,7 @@ export default class Blob extends EventTarget {
217 231
     let resPath = blobCacheDir + getBlobName()
218 232
     let pass = false
219 233
     log.debug('fs.slice new blob will at', resPath)
220
-    let result = new Blob(RNFetchBlob.wrap(resPath), { type : contentType })
234
+    let result = new Blob(RNFetchBlob.wrap(resPath), { type : contentType }, true)
221 235
     fs.slice(this._ref, resPath, start, end).then((dest) => {
222 236
       log.debug('fs.slice done', dest)
223 237
       result._invokeOnCreateEvent()
@@ -252,7 +266,9 @@ export default class Blob extends EventTarget {
252 266
     if(this._closed)
253 267
       return Promise.reject('Blob has been released.')
254 268
     this._closed = true
255
-    return fs.unlink(this._ref)
269
+    return fs.unlink(this._ref).catch((err) => {
270
+      console.warn(err)
271
+    })
256 272
   }
257 273
 
258 274
   _invokeOnCreateEvent() {

+ 1
- 1
src/polyfill/File.js 查看文件

@@ -9,7 +9,7 @@ export default class File extends Blob {
9 9
 
10 10
   name : string = '';
11 11
 
12
-  static build(name:string, data:any, cType):Promise<File> {
12
+  static build(name:string, data:any, cType:string):Promise<File> {
13 13
     return new Promise((resolve, reject) => {
14 14
       new File(data, cType).onCreated((f) => {
15 15
         f.name = name

+ 7
- 0
src/polyfill/XMLHttpRequest.js 查看文件

@@ -86,6 +86,13 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
86 86
     return DONE
87 87
   }
88 88
 
89
+  static setLog(level:number) {
90
+    if(number === -1)
91
+      log.disable()
92
+    else
93
+      log.level(level)
94
+  }
95
+
89 96
   static addBinaryContentType(substr:string) {
90 97
     for(let i in XMLHttpRequest.binaryContentTypes) {
91 98
       if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) {

+ 13
- 0
src/react-native-fetch-blob.podspec 查看文件

@@ -0,0 +1,13 @@
1
+Pod::Spec.new do |s|
2
+  s.name             = "react-native-fetch-blob"
3
+  s.version          = "0.9.4"
4
+  s.summary          = "A project committed to make file acess and data transfer easier, effiecient for React Native developers."
5
+  s.requires_arc = true
6
+  s.license      = 'MIT'
7
+  s.homepage     = 'n/a'
8
+  s.authors      = { "wkh237" => "xeiyan@gmail.com" }
9
+  s.source       = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.9.4-beta.3'}
10
+  s.source_files = 'ios/**/*.{h,m}'
11
+  s.platform     = :ios, "7.0"
12
+  s.dependency 'React/Core'
13
+end

+ 57
- 62
src/scripts/prelink.js 查看文件

@@ -1,76 +1,71 @@
1
-var fs = require('fs');
1
+try {
2
+  var fs = require('fs');
3
+  var glob = require('glob');
4
+  var addAndroidPermissions = process.env.RNFB_ANDROID_PERMISSIONS == 'true';
5
+  var MANIFEST_PATH = glob.sync(process.cwd() + '/android/app/src/main/**/AndroidManifest.xml')[0];
6
+  var PACKAGE_JSON = process.cwd() + '/package.json';
7
+  var package = JSON.parse(fs.readFileSync(PACKAGE_JSON));
8
+  var APP_NAME = package.name;
9
+  var PACKAGE_GRADLE = process.cwd() + '/node_modules/react-native-fetch-blob/android/build.gradle'
10
+  var VERSION = checkVersion();
2 11
 
3
-var MANIFEST_PATH = process.cwd() + '/android/app/src/main/AndroidManifest.xml';
4
-var PACKAGE_JSON = process.cwd() + '/package.json';
12
+  console.log('RNFetchBlob detected app version => ' + VERSION);
5 13
 
6
-var hasNecessaryFile = fs.existsSync(MANIFEST_PATH) && fs.existsSync(PACKAGE_JSON);
7
-
8
-if (!hasNecessaryFile) {
9
-  throw 'RNFetchBlob could not link Android automatically, some files could not be found.'
10
-}
11
-
12
-var package = JSON.parse(fs.readFileSync(PACKAGE_JSON));
13
-var APP_NAME = package.name;
14
-var APPLICATION_MAIN = process.cwd() + '/android/app/src/main/java/com/' + APP_NAME.toLocaleLowerCase() + '/MainApplication.java';
15
-var PACKAGE_GRADLE = process.cwd() + '/node_modules/react-native-fetch-blob/android/build.gradle'
16
-
17
-var VERSION = checkVersion();
18
-console.log('RNFetchBlob detected app version .. ' + VERSION);
19
-
20
-if(VERSION >= 0.29) {
21
-  console.log('RNFetchBlob patching MainApplication.java .. ');
22
-  if(!fs.existsSync(APPLICATION_MAIN)) {
23
-    throw 'RNFetchBlob could not link Android automatically, MainApplication.java not found in path : ' + APPLICATION_MAIN
24
-  }
25
-  var main = fs.readFileSync(APPLICATION_MAIN);
26
-  if(String(main).match('new RNFetchBlobPackage()') !== null) {
27
-    console.log('skipped');
28
-    return
14
+  if(VERSION < 0.28) {
15
+    console.log('You project version is '+ VERSION + ' which may not compatible to react-native-fetch-blob 7.0+, please consider upgrade your application template to react-native 0.27+.')
16
+    // add OkHttp3 dependency fo pre 0.28 project
17
+    var main = fs.readFileSync(PACKAGE_GRADLE);
18
+    console.log('adding OkHttp3 dependency to pre 0.28 project .. ')
19
+    main = String(main).replace('//{RNFetchBlob_PRE_0.28_DEPDENDENCY}', "compile 'com.squareup.okhttp3:okhttp:3.4.1'");
20
+    fs.writeFileSync(PACKAGE_GRADLE, main);
21
+    console.log('adding OkHttp3 dependency to pre 0.28 project .. ok')
29 22
   }
30
-  main = String(main).replace('new MainReactPackage()', 'new RNFetchBlobPackage(),\n           new MainReactPackage()');
31
-  main = String(main).replace('import com.facebook.react.ReactApplication;', 'import com.facebook.react.ReactApplication;\nimport com.RNFetchBlob.RNFetchBlobPackage;')
32 23
 
33
-  fs.writeFileSync(APPLICATION_MAIN, main);
34
-  console.log('RNFetchBlob patching MainApplication.java .. ok')
24
+  console.log('Add Android permissions => ' + (addAndroidPermissions == "true"))
35 25
 
36
-}
26
+  if(addAndroidPermissions) {
37 27
 
38
-if(VERSION < 0.28) {
39
-  console.log('You project version is '+ VERSION + 'which does not meet requirement of react-native-fetch-blob 7.0+, please upgrade your application template to react-native 0.27+, otherwise Android application will not working.')
40
-  // add OkHttp3 dependency fo 0.28- project
41
-  var main = fs.readFileSync(PACKAGE_GRADLE);
42
-  console.log('adding OkHttp3 dependency to pre 0.28 project .. ')
43
-  main = String(main).replace('//{RNFetchBlob_PRE_0.28_DEPDENDENCY}', "compile 'com.squareup.okhttp3:okhttp:3.4.1'");
44
-  fs.writeFileSync(PACKAGE_GRADLE, main);
45
-  console.log('adding OkHttp3 dependency to pre 0.28 project .. ok')
46
-}
28
+    // set file access permission for Android < 6.0
29
+    fs.readFile(MANIFEST_PATH, function(err, data) {
47 30
 
48
-// set file access permission for Android < 6.0
49
-fs.readFile(MANIFEST_PATH, function(err, data) {
31
+      if(err)
32
+        console.log('failed to locate AndroidManifest.xml file, you may have to add file access permission manually.');
33
+      else {
50 34
 
51
-  if(err)
52
-    console.log('failed to locate AndroidManifest.xml file, you may have to add file access permission manually.');
53
-  else {
35
+        console.log('RNFetchBlob patching AndroidManifest.xml .. ');
36
+        // append fs permission
37
+        data = String(data).replace(
38
+          '<uses-permission android:name="android.permission.INTERNET" />',
39
+          '<uses-permission android:name="android.permission.INTERNET" />\n    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> '
40
+        )
41
+        // append DOWNLOAD_COMPLETE intent permission
42
+        data = String(data).replace(
43
+          '<category android:name="android.intent.category.LAUNCHER" />',
44
+          '<category android:name="android.intent.category.LAUNCHER" />\n     <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>'
45
+        )
46
+        fs.writeFileSync(MANIFEST_PATH, data);
47
+        console.log('RNFetchBlob patching AndroidManifest.xml .. ok');
54 48
 
55
-    console.log('RNFetchBlob patching AndroidManifest.xml .. ');
56
-    // append fs permission
57
-    data = String(data).replace(
58
-      '<uses-permission android:name="android.permission.INTERNET" />',
59
-      '<uses-permission android:name="android.permission.INTERNET" />\n    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> '
60
-    )
61
-    // append DOWNLOAD_COMPLETE intent permission
62
-    data = String(data).replace(
63
-      '<category android:name="android.intent.category.LAUNCHER" />',
64
-      '<category android:name="android.intent.category.LAUNCHER" />\n     <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>'
65
-    )
66
-    fs.writeFileSync(MANIFEST_PATH, data);
67
-    console.log('RNFetchBlob patching AndroidManifest.xml .. ok');
49
+      }
68 50
 
51
+    })
52
+  }
53
+  else {
54
+    console.log(
55
+      '\033[95mreact-native-fetch-blob \033[97mwill not automatically add Android permissions after \033[92m0.9.4 '+
56
+      '\033[97mplease run the following command if you want to add default permissions :\n\n' +
57
+      '\033[96m\tRNFB_ANDROID_PERMISSION=true react-native link \n')
69 58
   }
70 59
 
71
-})
60
+  function checkVersion() {
61
+    console.log('RNFetchBlob checking app version ..');
62
+    return parseFloat(/\d\.\d+(?=\.)/.exec(package.dependencies['react-native']));
63
+  }
72 64
 
73
-function checkVersion() {
74
-  console.log('RNFetchBlob checking app version ..');
75
-  return parseFloat(/\d\.\d+(?=\.)/.exec(package.dependencies['react-native']));
65
+} catch(err) {
66
+  console.log(
67
+    '\033[95mreact-native-fetch-blob\033[97m link \033[91mFAILED \033[97m\nCould not automatically link package :'+
68
+    err.stack +
69
+    'please follow the instructions to manually link the library : ' +
70
+    '\033[4mhttps://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package\n')
76 71
 }

+ 2
- 1
src/utils/log.js 查看文件

@@ -9,6 +9,7 @@ export default class Log {
9 9
   }
10 10
 
11 11
   level(val:number) {
12
+    this._isEnable = true
12 13
     this._level = val
13 14
   }
14 15
 
@@ -33,7 +34,7 @@ export default class Log {
33 34
   }
34 35
 
35 36
   error(...args) {
36
-    this._isEnable && this._level > -1 && console.log(this._name, 'error:', ...args)
37
+    this._isEnable && this._level > -1 && console.warn(this._name, 'error:', ...args)
37 38
   }
38 39
 
39 40
 }

二進制
test-server/public/1600k-img-dummy.jpg 查看文件


+ 6
- 1
test-server/server.js 查看文件

@@ -65,6 +65,11 @@ app.use(function(req, res, next) {
65 65
   next();
66 66
 })
67 67
 
68
+app.all('/upload', (req, res) => {
69
+  console.log(req.headers)
70
+  res.send(req.headers)
71
+})
72
+
68 73
 app.get('/unicode', (req, res) => {
69 74
   res.send({ data:'你好!'})
70 75
 })
@@ -149,7 +154,7 @@ app.all('/xhr-header', (req, res) => {
149 154
   res.send(req.headers)
150 155
 })
151 156
 
152
-app.post('/upload', bodyParser.urlencoded({ extended : true }), (req, res) => {
157
+app.post('/upload_urlencode', bodyParser.urlencoded({ extended : true }), (req, res) => {
153 158
   console.log(JSON.stringify(req.headers))
154 159
   console.log(JSON.stringify(req.body))
155 160
   res.status(200).send(req.body)

+ 2
- 0
test/test-0.7.0.js 查看文件

@@ -45,6 +45,7 @@ describe('Upload and download large file', (report, done) => {
45 45
     if(Date.now() - deb < 1000)
46 46
       return
47 47
     deb = Date.now()
48
+    console.log('download', now, total)
48 49
     report(<Info uid="200" key="progress">
49 50
       <Text>
50 51
         {`download ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s) ${(100*now/total).toFixed(2)}%`}
@@ -72,6 +73,7 @@ describe('Upload and download large file', (report, done) => {
72 73
       if(Date.now() - deb < 1000)
73 74
         return
74 75
       deb = Date.now()
76
+      console.log('upload', now, total)
75 77
       report(<Info uid="300" key="upload progress">
76 78
         <Text>
77 79
           {`upload ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s) ${(100*now/total).toFixed(2)}%`}

+ 127
- 0
test/test-0.9.4.js 查看文件

@@ -9,6 +9,7 @@ import {
9 9
   Platform,
10 10
   Dimensions,
11 11
   Image,
12
+  TouchableOpacity,
12 13
 } from 'react-native';
13 14
 
14 15
 window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
@@ -61,3 +62,129 @@ describe('issue #106', (report, done) => {
61 62
     })
62 63
 
63 64
 })
65
+
66
+describe('issue #111 get redirect destination', (report, done) => {
67
+  RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/redirect`)
68
+  .then((res) => {
69
+    console.log(res.info())
70
+    report(
71
+      <Assert key="redirect history should tracable"
72
+        expect={2}
73
+        actual={res.info().redirects.length}/>,
74
+      <Assert key="redirect history verify"
75
+        expect={[`${TEST_SERVER_URL}/redirect`, `${TEST_SERVER_URL}/public/github.png`]}
76
+        comparer={Comparer.equalToArray}
77
+        actual={res.info().redirects}/>,
78
+    )
79
+    done()
80
+  })
81
+})
82
+
83
+describe('chunked encoding option test', (report, done) => {
84
+
85
+  let path = null
86
+  let base64 = null
87
+
88
+  RNFetchBlob
89
+    // .config({ fileCache : true })
90
+    .fetch('GET', `${TEST_SERVER_URL}/public/1600k-img-dummy.jpg`)
91
+    .then((res) => {
92
+      base64 = res.base64()
93
+      return RNFetchBlob
94
+        .fetch('POST', `${TEST_SERVER_URL}/upload`, {
95
+          'Content-Type' : 'application/octet-stream;BASE64'
96
+        }, base64)
97
+    })
98
+    .then((res) => {
99
+      let headers = res.info().headers
100
+      console.log(res.text())
101
+      report(<Assert key="request should not use chunked encoding"
102
+        expect={undefined}
103
+        actual={headers['transfer-encoding']}/>)
104
+      fs.unlink(path)
105
+      done()
106
+    })
107
+})
108
+
109
+describe('#118 readStream performance prepare the file', (report, done) => {
110
+  let cache = null
111
+  let size = 0
112
+  let size2 = 0
113
+  let tick = Date.now()
114
+  let tick2 = Date.now()
115
+  let start = -1
116
+  let start2 = -1
117
+  let count = 0
118
+
119
+  let task = RNFetchBlob.config({fileCache : true})
120
+    .fetch('GET', `${TEST_SERVER_URL}/public/22mb-dummy`)
121
+  task.progress((current, total) => {
122
+    report(<Info key="prepare file" uid="prepare">
123
+      <Text key="pg"> {Math.floor(current/total*100)}% </Text>
124
+    </Info>)
125
+  })
126
+  task.then((res) => {
127
+      report(<Info key="preparation complete"><Text>start in 3 seconds</Text></Info>)
128
+      cache = res.path()
129
+      setTimeout(readFile, 2500)
130
+      function readFile() {
131
+        fs.readStream(cache, 'utf8', 102400, 10)
132
+          .then((stream) => {
133
+            stream.open()
134
+            start = Date.now()
135
+            stream.onData((chunk) => {
136
+              count++
137
+              size += chunk.length
138
+              if(Date.now() - tick > 500) {
139
+                console.log(size, ' read')
140
+                tick = Date.now()
141
+                report(
142
+                  <Info key="size" uid="100">
143
+                    <Text key="AA">File 1 {size}/22000000 bytes read</Text>
144
+                    <Text key="BB">File 2 {size2}/22000000 bytes read</Text>
145
+                  </Info>)
146
+              }
147
+            })
148
+            stream.onEnd(() => {
149
+              report(
150
+                <Info key="size" uid="100"><Text>{size} bytes read</Text></Info>,
151
+                <Info key="elapsed"><Text>{Date.now() - start} ms</Text></Info>,
152
+                <Info key="events"><Text>{count} times</Text></Info>,
153
+                <Assert key="JS thread is not blocked" expect={true} actual={true}/>,)
154
+              fs.stat(cache).then((stat) => {
155
+                report(
156
+                  <Info key="info"><Text>{JSON.stringify(stat)}</Text></Info>)
157
+                fs.unlink(cache)
158
+                done()
159
+              })
160
+            })
161
+          })
162
+      }
163
+    })
164
+})
165
+
166
+describe('issue #120 upload progress should work when sending body as-is', (report, done) => {
167
+
168
+  let data = RNTest.prop('image')
169
+  let tick = 0
170
+
171
+  let task = RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
172
+    Authorization : `Bearer ${DROPBOX_TOKEN}`,
173
+    'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+FILENAME+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
174
+    'Content-Type' : 'text/plain; charset=dropbox-cors-hack',
175
+  }, data)
176
+
177
+  task.uploadProgress((current, total) => {
178
+    tick ++
179
+  })
180
+  .then((res) => {
181
+    let resp = res.json()
182
+    report(
183
+      <Info key="viewer"><Text>{JSON.stringify(resp)}</Text></Info>,
184
+      <Assert key="event should be triggered"
185
+        expect={tick}
186
+        comparer={Comparer.greater} actual={0}/>)
187
+    done()
188
+  })
189
+
190
+})

+ 60
- 11
test/test-firebase.js 查看文件

@@ -57,7 +57,8 @@ describe('firebase login', (report, done) => {
57 57
     <Info key="user content" uid="user data">
58 58
       <Text>{JSON.stringify(user)}</Text>
59 59
     </Info>)
60
-    done()
60
+    if(user)
61
+      done()
61 62
   })
62 63
 })
63 64
 
@@ -108,16 +109,16 @@ describe('upload using file path', (report, done) => {
108 109
       report(<Info key="test image">
109 110
         <Image style={styles.image} source={{uri : prefix + resp.path()}}/>
110 111
       </Info>)
111
-      let blob = new Blob(RNFetchBlob.wrap(resp.path()), { type : 'image/jpg' })
112
-      blob.onCreated(() => {
113
-        firebase.storage().ref('rnfbtest')
114
-          .child(tier2FileName)
115
-          .put(blob, { contentType : 'image/jpg' })
116
-          .then(() => {
117
-            report(<Assert key="upload finished" />)
118
-            done()
119
-          })
120
-      })
112
+      return Blob.build(RNFetchBlob.wrap(resp.path()), { type : 'image/jpg' })
113
+    })
114
+    .then((blob) => {
115
+      return firebase.storage().ref('rnfbtest')
116
+        .child(tier2FileName)
117
+        .put(blob, { contentType : 'image/jpg' })
118
+    })
119
+    .then(() => {
120
+      report(<Assert key="upload finished" />)
121
+      done()
121 122
     })
122 123
 })
123 124
 
@@ -175,3 +176,51 @@ describe('upload from storage', (report, done) => {
175 176
     console.log(err)
176 177
   }
177 178
 })
179
+
180
+Platform.OS === 'ios' && describe('upload from CameraRoll', (report, done) => {
181
+
182
+    CameraRoll.getPhotos({first : 10})
183
+    .then((resp) => {
184
+      let url = resp.edges[0].node.image.uri
185
+      console.log('CameraRoll',url)
186
+      return Blob.build(RNFetchBlob.wrap(url), {type:'image/jpg'})
187
+    })
188
+    .then((b) => {
189
+      blob = b
190
+      console.log('start upload ..')
191
+      return firebase.storage()
192
+        .ref('rnfbtest').child(`camra-roll-${Platform.OS}-${Date.now()}.jpg`)
193
+        .put(b, {contentType : 'image/jpg'})
194
+    })
195
+    .then((snapshot) => {
196
+      report(<Assert key="upload sucess" expect={true} actual={true}/>)
197
+      done()
198
+    })
199
+})
200
+
201
+
202
+Platform.OS === 'android' && describe('upload from CameraRoll', (report, done) => {
203
+
204
+  let blob
205
+  RNFetchBlob.config({
206
+      addAndroidDownloads : { useDownloadManager : true }
207
+    })
208
+    .fetch('GET', `${TEST_SERVER_URL}/public/1600k-img-dummy.jpg`)
209
+    .then((res) => CameraRoll.getPhotos({first : 10}))
210
+    .then((resp) => {
211
+      let url = resp.edges[0].node.image.uri
212
+      console.log('CameraRoll',url)
213
+      return Blob.build(RNFetchBlob.wrap(url), {type:'image/jpg'})
214
+    })
215
+    .then((b) => {
216
+      blob = b
217
+      return firebase.storage()
218
+        .ref('rnfbtest').child(`camra-roll-${Platform.OS}-${Date.now()}.jpg`)
219
+        .put(b, {contentType : 'image/jpg'})
220
+    })
221
+    .then((snapshot) => {
222
+      report(<Assert key="upload sucess" expect={true} actual={true}/>)
223
+      blob.close()
224
+      done()
225
+    })
226
+})

+ 16
- 17
test/test-init.js 查看文件

@@ -59,22 +59,21 @@ describe('GET image from server', (report, done) => {
59 59
 })
60 60
 
61 61
 
62
-// require('./test-0.1.x-0.4.x')
63
-// require('./test-0.5.1')
64
-// require('./test-0.5.2')
65
-// require('./test-0.6.0')
66
-// require('./test-0.6.2')
67
-// require('./test-0.7.0')
68
-// require('./test-0.8.0')
69
-// require('./test-0.9.0')
70
-// require('./test-0.9.2')
71
-require('./test-0.10.0')
72
-// require('./test-0.9.4')
73
-// require('./test-fetch')
74
-// require('./test-fs')
75
-// require('./test-xmlhttp')
76
-// require('./test-blob')
77
-// require('./test-firebase')
78
-// require('./test-android')
62
+require('./test-0.1.x-0.4.x')
63
+require('./test-0.5.1')
64
+require('./test-0.5.2')
65
+require('./test-0.6.0')
66
+require('./test-0.6.2')
67
+require('./test-0.7.0')
68
+require('./test-0.8.0')
69
+require('./test-0.9.0')
70
+require('./test-0.9.2')
71
+require('./test-0.9.4')
72
+require('./test-fetch')
73
+require('./test-fs')
74
+require('./test-xmlhttp')
75
+require('./test-blob')
76
+require('./test-firebase')
77
+require('./test-android')
79 78
 // require('./test-stress')
80 79
 // require('./benchmark')

+ 2
- 2
test/test-xmlhttp.js 查看文件

@@ -157,7 +157,7 @@ describe('request headers records should be cleared by open()', (report, done) =
157 157
     if(this.readyState == 4) {
158 158
       report(<Assert key="headers should be cleared by open()"
159 159
         expect={"200"}
160
-        actual={this.response.value}/>)
160
+        actual={JSON.parse(this.response).value}/>)
161 161
       done()
162 162
     }
163 163
   }
@@ -243,7 +243,7 @@ describe('upload progress event test', (report, done) => {
243 243
       report(
244 244
         <Assert key="reponse should correct"
245 245
           expect={time}
246
-          actual={Math.floor(xhr.response.time)}/>,
246
+          actual={Math.floor(JSON.parse(xhr.response).time)}/>,
247 247
         <Assert key="responseType should correct"
248 248
           expect={'json'}
249 249
           actual={xhr.responseType}/>)