Kaynağa Gözat

Merge branch '0.9.7' into 0.10.0

Ben Hsieh 8 yıl önce
ebeveyn
işleme
9b1edd2226
100 değiştirilmiş dosya ile 497 ekleme ve 208 silme
  1. 2
    0
      .github/ISSUE_TEMPLATE
  2. 1
    1
      .github/PULL_REQUEST_TEMPLATE
  3. 33
    5
      README.md
  4. 3
    2
      package.json
  5. 79
    92
      src/README.md
  6. 6
    4
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
  7. 12
    8
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
  8. 39
    0
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java
  9. 10
    9
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
  10. 4
    6
      src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobDefaultResp.java
  11. 25
    16
      src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
  12. 21
    20
      src/fs.js
  13. 59
    5
      src/index.js
  14. 6
    0
      src/ios/RNFetchBlob.xcodeproj/project.pbxproj
  15. 9
    5
      src/ios/RNFetchBlob/RNFetchBlob.m
  16. 1
    0
      src/ios/RNFetchBlobConst.h
  17. 2
    1
      src/ios/RNFetchBlobConst.m
  18. 4
    0
      src/ios/RNFetchBlobNetwork.h
  19. 68
    24
      src/ios/RNFetchBlobNetwork.m
  20. 39
    0
      src/ios/RNFetchBlobProgress.h
  21. 57
    0
      src/ios/RNFetchBlobProgress.m
  22. 2
    1
      src/ios/RNFetchBlobReqBuilder.m
  23. 1
    1
      src/package.json
  24. 2
    2
      src/polyfill/Blob.js
  25. 10
    4
      src/polyfill/XMLHttpRequest.js
  26. 2
    2
      src/react-native-fetch-blob.podspec
  27. BIN
      test-server/cat_fu_mp4/img00001.jpeg
  28. BIN
      test-server/cat_fu_mp4/img00002.jpeg
  29. BIN
      test-server/cat_fu_mp4/img00003.jpeg
  30. BIN
      test-server/cat_fu_mp4/img00004.jpeg
  31. BIN
      test-server/cat_fu_mp4/img00005.jpeg
  32. BIN
      test-server/cat_fu_mp4/img00006.jpeg
  33. BIN
      test-server/cat_fu_mp4/img00007.jpeg
  34. BIN
      test-server/cat_fu_mp4/img00008.jpeg
  35. BIN
      test-server/cat_fu_mp4/img00009.jpeg
  36. BIN
      test-server/cat_fu_mp4/img00010.jpeg
  37. BIN
      test-server/cat_fu_mp4/img00011.jpeg
  38. BIN
      test-server/cat_fu_mp4/img00012.jpeg
  39. BIN
      test-server/cat_fu_mp4/img00013.jpeg
  40. BIN
      test-server/cat_fu_mp4/img00014.jpeg
  41. BIN
      test-server/cat_fu_mp4/img00015.jpeg
  42. BIN
      test-server/cat_fu_mp4/img00016.jpeg
  43. BIN
      test-server/cat_fu_mp4/img00017.jpeg
  44. BIN
      test-server/cat_fu_mp4/img00018.jpeg
  45. BIN
      test-server/cat_fu_mp4/img00019.jpeg
  46. BIN
      test-server/cat_fu_mp4/img00020.jpeg
  47. BIN
      test-server/cat_fu_mp4/img00021.jpeg
  48. BIN
      test-server/cat_fu_mp4/img00022.jpeg
  49. BIN
      test-server/cat_fu_mp4/img00023.jpeg
  50. BIN
      test-server/cat_fu_mp4/img00024.jpeg
  51. BIN
      test-server/cat_fu_mp4/img00025.jpeg
  52. BIN
      test-server/cat_fu_mp4/img00026.jpeg
  53. BIN
      test-server/cat_fu_mp4/img00027.jpeg
  54. BIN
      test-server/cat_fu_mp4/img00028.jpeg
  55. BIN
      test-server/cat_fu_mp4/img00029.jpeg
  56. BIN
      test-server/cat_fu_mp4/img00030.jpeg
  57. BIN
      test-server/cat_fu_mp4/img00031.jpeg
  58. BIN
      test-server/cat_fu_mp4/img00032.jpeg
  59. BIN
      test-server/cat_fu_mp4/img00033.jpeg
  60. BIN
      test-server/cat_fu_mp4/img00034.jpeg
  61. BIN
      test-server/cat_fu_mp4/img00035.jpeg
  62. BIN
      test-server/cat_fu_mp4/img00036.jpeg
  63. BIN
      test-server/cat_fu_mp4/img00037.jpeg
  64. BIN
      test-server/cat_fu_mp4/img00038.jpeg
  65. BIN
      test-server/cat_fu_mp4/img00039.jpeg
  66. BIN
      test-server/cat_fu_mp4/img00040.jpeg
  67. BIN
      test-server/cat_fu_mp4/img00041.jpeg
  68. BIN
      test-server/cat_fu_mp4/img00042.jpeg
  69. BIN
      test-server/cat_fu_mp4/img00043.jpeg
  70. BIN
      test-server/cat_fu_mp4/img00044.jpeg
  71. BIN
      test-server/cat_fu_mp4/img00045.jpeg
  72. BIN
      test-server/cat_fu_mp4/img00046.jpeg
  73. BIN
      test-server/cat_fu_mp4/img00047.jpeg
  74. BIN
      test-server/cat_fu_mp4/img00048.jpeg
  75. BIN
      test-server/cat_fu_mp4/img00049.jpeg
  76. BIN
      test-server/cat_fu_mp4/img00050.jpeg
  77. BIN
      test-server/cat_fu_mp4/img00051.jpeg
  78. BIN
      test-server/cat_fu_mp4/img00052.jpeg
  79. BIN
      test-server/cat_fu_mp4/img00053.jpeg
  80. BIN
      test-server/cat_fu_mp4/img00054.jpeg
  81. BIN
      test-server/cat_fu_mp4/img00055.jpeg
  82. BIN
      test-server/cat_fu_mp4/img00056.jpeg
  83. BIN
      test-server/cat_fu_mp4/img00057.jpeg
  84. BIN
      test-server/cat_fu_mp4/img00058.jpeg
  85. BIN
      test-server/cat_fu_mp4/img00059.jpeg
  86. BIN
      test-server/cat_fu_mp4/img00060.jpeg
  87. BIN
      test-server/cat_fu_mp4/img00061.jpeg
  88. BIN
      test-server/cat_fu_mp4/img00062.jpeg
  89. BIN
      test-server/cat_fu_mp4/img00063.jpeg
  90. BIN
      test-server/cat_fu_mp4/img00064.jpeg
  91. BIN
      test-server/cat_fu_mp4/img00065.jpeg
  92. BIN
      test-server/cat_fu_mp4/img00066.jpeg
  93. BIN
      test-server/cat_fu_mp4/img00067.jpeg
  94. BIN
      test-server/cat_fu_mp4/img00068.jpeg
  95. BIN
      test-server/cat_fu_mp4/img00069.jpeg
  96. BIN
      test-server/cat_fu_mp4/img00070.jpeg
  97. BIN
      test-server/cat_fu_mp4/img00071.jpeg
  98. BIN
      test-server/cat_fu_mp4/img00072.jpeg
  99. BIN
      test-server/cat_fu_mp4/img00073.jpeg
  100. 0
    0
      test-server/cat_fu_mp4/img00074.jpeg

+ 2
- 0
.github/ISSUE_TEMPLATE Dosyayı Görüntüle

@@ -1,3 +1,5 @@
1 1
 Hi ! Thank you for reporting an issue, but we would like to remind you, we have a trouble shooting page in our wiki. You may want to take a look on that page :p 
2 2
 
3
+* please provide the version of installed library and RN project.
4
+* a sample code snippet/repository is very helpful to spotting the problem.
3 5
 * issues which have been tagged as 'needs feedback', will be closed after 2 weeks if receive no feedbacks.

+ 1
- 1
.github/PULL_REQUEST_TEMPLATE Dosyayı Görüntüle

@@ -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.4
4
+2. Bug fix request to "Bug Fix Branch" 0.9.6
5 5
 3. Correct README.md can directly to master

+ 33
- 5
README.md Dosyayı Görüntüle

@@ -4,6 +4,8 @@
4 4
 
5 5
 A project committed to make file acess and data transfer easier, efficient for React Native developers.
6 6
 
7
+> The npm package is inside `src` folder, if you're going to install using github repository do not point to here directly
8
+
7 9
 ## Features
8 10
 - Transfer data directly from/to storage without BASE64 bridging
9 11
 - File API supports normal files, Asset files, and CameraRoll files
@@ -11,8 +13,6 @@ A project committed to make file acess and data transfer easier, efficient for R
11 13
 - File stream support for dealing with large file
12 14
 - Blob, File, XMLHttpRequest polyfills that make browser-based library available in RN (experimental)
13 15
 
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
-
16 16
 ## TOC
17 17
 * [About](#user-content-about)
18 18
 * [Installation](#user-content-installation)
@@ -157,6 +157,8 @@ To sum up :
157 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 158
 - To send the body as-is, simply use a `Content-Type` header not containing `;BASE64` or `application/octet`.
159 159
 
160
+> It is Worth to mentioning that the HTTP request uses cache by default, if you're going to disable it simply add a Cache Control header `'Cache-Control' : 'no-store'` 
161
+
160 162
 > 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`.
161 163
 
162 164
 ### Download example : Fetch files that needs authorization token
@@ -268,7 +270,7 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
268 270
     }),
269 271
     'Content-Type' : 'application/octet-stream',
270 272
     // here's the body you're going to send, should be a BASE64 encoded string
271
-    // (you can use "base64" APIs to make one).
273
+    // (you can use "base64"(refer to the library 'mathiasbynens/base64') APIs to make one).
272 274
     // The data will be converted to "byte array"(say, blob) before request sent.  
273 275
   }, base64ImageString)
274 276
   .then((res) => {
@@ -378,7 +380,7 @@ What if you want to append a file to form data ? Just like [upload a file from s
378 380
 
379 381
 ### Upload/Download progress
380 382
 
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.
383
+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. 
382 384
 
383 385
 ```js
384 386
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -401,6 +403,30 @@ In `version >= 0.4.2` it is possible to know the upload/download progress. After
401 403
     })
402 404
 ```
403 405
 
406
+In `0.9.6`, you can specify an optional first argument which contains `count` and `interval` to limit progress event frequency (this will be done in native context in order to reduce RCT bridge overhead). Notice that `count` argument will not work if the server does not provide response content length.
407
+
408
+
409
+```js
410
+  RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
411
+      ... some headers,
412
+      'Content-Type' : 'octet-stream'
413
+    }, base64DataString)
414
+    // listen to upload progress event, emit every 250ms
415
+    .uploadProgress({ interval : 250 },(written, total) => {
416
+        console.log('uploaded', written / total)
417
+    })
418
+    // listen to download progress event, every 10%
419
+    .progress({ count : 10 }, (received, total) => {
420
+        console.log('progress', received / total)
421
+    })
422
+    .then((resp) => {
423
+      // ...
424
+    })
425
+    .catch((err) => {
426
+      // ...
427
+    })
428
+```
429
+
404 430
 ### Cancel Request
405 431
 
406 432
 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.
@@ -709,10 +735,12 @@ Here's a [sample app](https://github.com/wkh237/rn-firebase-storage-upload-sampl
709 735
 
710 736
 ## Performance Tips
711 737
 
712
-**Reduce RCT Bridge and BASE64 Overheard**
738
+**Read Stream Event Overhead**
713 739
 
714 740
 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 741
 
742
+**Reduce RCT Bridge and BASE64 Overhead**
743
+
716 744
 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.
717 745
 
718 746
 <img src="img/performance_1.png" style="width : 100%"/>

+ 3
- 2
package.json Dosyayı Görüntüle

@@ -1,6 +1,7 @@
1 1
 {
2
-  "name": "fetchblob-dev",
3
-  "version": "0.9.5-beta.2",
2
+  "name": "react-native-fetch-blob-dev-env",
3
+  "description" : "RNFB development environment, not dist package",
4
+  "version": "0.9.6",
4 5
   "private": true,
5 6
   "scripts": {
6 7
     "start": "node node_modules/react-native/local-cli/cli.js start",

+ 79
- 92
src/README.md Dosyayı Görüntüle

@@ -1,8 +1,10 @@
1
-# react-native-fetch-blob [![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) [![npm](https://img.shields.io/npm/l/react-native-fetch-blob.svg?maxAge=2592000&style=flat-square)]()
1
+# react-native-fetch-blob
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)]()
2 3
 
3
-# [Visit our Github for latest document](https://github.com/wkh237/react-native-fetch-blob)
4 4
 
5
-A project committed to make file acess and data transfer easier, effiecient for React Native developers.
5
+A project committed to make file acess and data transfer easier, efficient for React Native developers.
6
+
7
+# [Please visit our Github Page for latest document](https://github.com/wkh237/react-native-fetch-blob)
6 8
 
7 9
 ## Features
8 10
 - Transfer data directly from/to storage without BASE64 bridging
@@ -11,7 +13,6 @@ A project committed to make file acess and data transfer easier, effiecient for
11 13
 - File stream support for dealing with large file
12 14
 - Blob, File, XMLHttpRequest polyfills that make browser-based library available in RN (experimental)
13 15
 
14
-
15 16
 ## TOC
16 17
 * [About](#user-content-about)
17 18
 * [Installation](#user-content-installation)
@@ -24,6 +25,7 @@ A project committed to make file acess and data transfer easier, effiecient for
24 25
  * [Cancel HTTP request](#user-content-cancel-request)
25 26
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
26 27
  * [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
28
+ * [Transfer Encoding](#user-content-transfer-encoding)
27 29
  * [RNFetchBlob as Fetch](#user-content-rnfetchblob-as-fetch)
28 30
 * [File System](#user-content-file-system)
29 31
  * [File access](#user-content-file-access)
@@ -37,9 +39,9 @@ A project committed to make file acess and data transfer easier, effiecient for
37 39
 
38 40
 ## About
39 41
 
40
-This project was initially for solving the issue [facebook/react-native#854](https://github.com/facebook/react-native/issues/854), because React Native lack of `Blob` implementation and it will cause some problem when transfering binary data. Now, this project is committed to make file access and transfer more easier, effiecient for React Native developers. We've implemented highly customizable filesystem and network module which plays well together. For example, upload and download data directly from/to storage which is much more efficient in some cases(especially for large ones). The file system supports file stream, so you don't have to worry about OOM problem when accessing large files.
42
+This project was initially for solving the issue [facebook/react-native#854](https://github.com/facebook/react-native/issues/854), because React Native lack of `Blob` implementation and it will cause some problem when transferring binary data. Now, this project is committed to make file access and transfer more easier, efficient for React Native developers. We've implemented highly customizable filesystem and network module which plays well together. For example, upload and download data directly from/to storage which is much more efficient in some cases(especially for large ones). The file system supports file stream, so you don't have to worry about OOM problem when accessing large files.
41 43
 
42
-In `0.8.0` we introduced experimential Web API polyfills that make it possible to use browser-based libraries in React Native, for example, [FireBase JS SDK](https://github.com/wkh237/rn-firebase-storage-upload-sample)
44
+In `0.8.0` we introduced experimental Web API polyfills that make it possible to use browser-based libraries in React Native, such as, [FireBase JS SDK](https://github.com/wkh237/rn-firebase-storage-upload-sample)
43 45
 
44 46
 
45 47
 ## Installation
@@ -50,40 +52,40 @@ Install package from npm
50 52
 npm install --save react-native-fetch-blob
51 53
 ```
52 54
 
53
-Link package using [rnpm](https://github.com/rnpm/rnpm)
55
+Or if using CocoaPods, add the pod to your `Podfile`, for example:
54 56
 
55
-```sh
56
-rnpm link
57 57
 ```
58
+pod 'react-native-fetch-blob,
59
+    :path => '../node_modules/react-native-fetch-blob
60
+```
61
+
62
+**Automatically Link Native Modules**
58 63
 
59
-### Manually link the package (Android)
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.
60 65
 
61
-If rnpm link command failed to link the package automatically, you might try manually link the package.
66
+```
67
+react-native link
68
+```
62 69
 
63
-Open `android/settings.gradle`, and add these lines which will app RNFetchBlob Android project dependency to your app.
70
+As for projects < 0.29 you need `rnpm` to link native packages
64 71
 
65
-```diff
66
-include ':app'      
67
-+ include ':react-native-fetch-blob'                                                                                                  
68
-+ project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')                        
72
+```sh
73
+rnpm link
69 74
 ```
70 75
 
71
-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
72 77
 
73
-```diff
74
-...
75
-+ import com.RNFetchBlob.RNFetchBlobPackage;                                                                                 
76
-...
77
-protected List<ReactPackage> getPackages() {
78
-      return Arrays.<ReactPackage>asList(
79
-          new MainReactPackage(),
80
-+          new RNFetchBlobPackage()                                                                                         
81
-      );
82
-    }
83
-  };
84
-...
78
+```sh
79
+RNFB_ANDROID_PERMISSIONS=true react-native link
85 80
 ```
86
-> 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.
87 89
 
88 90
 **Grant Permission to External storage for Android 5.0 or lower**
89 91
 
@@ -138,11 +140,10 @@ If you're using ES5 require statement to load the module, please add `default`.
138 140
 var RNFetchBlob = require('react-native-fetch-blob').default
139 141
 ```
140 142
 
141
-### HTTP Data Transfer
143
+## HTTP Data Transfer
142 144
 
143
----
144 145
 
145
-#### Regular Request
146
+### Regular Request
146 147
 
147 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
148 149
 
@@ -150,13 +151,17 @@ After `0.8.0` react-native-fetch-blob automatically decide how to send the body
150 151
 
151 152
 To sum up :
152 153
 
153
-- 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.
154
-- 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.
155
- - The body is a BASE64 encoded string, the `Content-Type` header filed must containing substring`;BASE64` or `application/octet`  
156
- - 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)`
157
-- 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
+> It is Worth to mentioning that the HTTP request uses cache by default, if you're going to disable it simply add a Cache Control header `'Cache-Control' : 'no-store'`
161
+
162
+> 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`.
158 163
 
159
-#### Download example : Fetch files that needs authorization token
164
+### Download example : Fetch files that needs authorization token
160 165
 
161 166
 Most simple way is download to memory and stored as BASE64 encoded string, this is handy when the response data is small.
162 167
 
@@ -182,9 +187,9 @@ RNFetchBlob.fetch('GET', 'http://www.example.com/images/img1.png', {
182 187
   })
183 188
 ```
184 189
 
185
-#### Download to storage directly
190
+### Download to storage directly
186 191
 
187
-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.
192
+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.
188 193
 
189 194
 **These files won't be removed automatically, please refer to [Cache File Management](#user-content-cache-file-management)**
190 195
 
@@ -206,7 +211,7 @@ RNFetchBlob
206 211
 
207 212
 **Set Temp File Extension**
208 213
 
209
-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`.
214
+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`.
210 215
 
211 216
 ```js
212 217
 RNFetchBlob
@@ -229,7 +234,7 @@ RNFetchBlob
229 234
 
230 235
 **Use Specific File Path**
231 236
 
232
-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.
237
+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.
233 238
 
234 239
 ```js
235 240
 let dirs = RNFetchBlob.fs.dirs
@@ -251,7 +256,7 @@ RNFetchBlob
251 256
 
252 257
 ####  Upload example : Dropbox [files-upload](https://www.dropbox.com/developers/documentation/http/documentation#files-upload) API
253 258
 
254
-`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.
259
+`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.
255 260
 
256 261
 ```js
257 262
 
@@ -265,7 +270,7 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
265 270
     }),
266 271
     'Content-Type' : 'application/octet-stream',
267 272
     // here's the body you're going to send, should be a BASE64 encoded string
268
-    // (you can use "base64" APIs to make one).
273
+    // (you can use "base64"(refer to the library 'mathiasbynens/base64') APIs to make one).
269 274
     // The data will be converted to "byte array"(say, blob) before request sent.  
270 275
   }, base64ImageString)
271 276
   .then((res) => {
@@ -276,9 +281,9 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
276 281
   })
277 282
 ```
278 283
 
279
-#### Upload a file from storage
284
+### Upload a file from storage
280 285
 
281
-If you're going to use a `file` request body, just wrap the path with `wrap` API.
286
+If you're going to use a `file` as request body, just wrap the path with `wrap` API.
282 287
 
283 288
 ```js
284 289
 RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
@@ -302,7 +307,7 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
302 307
   })
303 308
 ```
304 309
 
305
-#### Multipart/form-data example : Post form data with file and data
310
+### Multipart/form-data example : Post form data with file and data
306 311
 
307 312
 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).
308 313
 
@@ -334,7 +339,7 @@ Elements have property `filename` will be transformed into binary format, otherw
334 339
   })
335 340
 ```
336 341
 
337
-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.
342
+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)).
338 343
 
339 344
 ```js
340 345
 
@@ -373,9 +378,9 @@ What if you want to upload a file using form data ? Just like [upload a file fro
373 378
   })
374 379
 ```
375 380
 
376
-#### Upload/Download progress
381
+### Upload/Download progress
377 382
 
378
-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.
383
+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.
379 384
 
380 385
 ```js
381 386
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -398,7 +403,7 @@ In `version >= 0.4.2` it is possible to know the upload/download progress. After
398 403
     })
399 404
 ```
400 405
 
401
-#### Cancel Request
406
+### Cancel Request
402 407
 
403 408
 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.
404 409
 
@@ -537,9 +542,9 @@ Or show an image in image viewer
537 542
       android.actionViewIntent(PATH_OF_IMG, 'image/png')
538 543
 ```
539 544
 
540
-### File System
545
+## File System
541 546
 
542
-#### File Access
547
+### File Access
543 548
 
544 549
 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.
545 550
 
@@ -567,11 +572,11 @@ File Access APIs
567 572
 
568 573
 See [File API](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API) for more information
569 574
 
570
-#### File Stream
575
+### File Stream
571 576
 
572 577
 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**.
573 578
 
574
-When calling `readStream` method, you have to `open` the stream, and start to read data.
579
+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))
575 580
 
576 581
 ```js
577 582
 let data = ''
@@ -616,7 +621,7 @@ RNFetchBlob.fs.writeStream(
616 621
 
617 622
 ```
618 623
 
619
-#### Cache File Management
624
+### Cache File Management
620 625
 
621 626
 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
622 627
 
@@ -673,7 +678,15 @@ You can also grouping requests by using `session` API, and use `dispose` to remo
673 678
 
674 679
 ```
675 680
 
676
-#### Self-Signed SSL Server
681
+### Transfer Encoding
682
+
683
+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`.
684
+
685
+```js
686
+RNFetchBlob.fetch('POST', 'http://example.com/upload', { 'Transfer-Encoding' : 'Chunked' }, bodyData)
687
+```
688
+
689
+### Self-Signed SSL Server
677 690
 
678 691
 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`
679 692
 
@@ -687,7 +700,7 @@ RNFetchBlob.config({
687 700
 })
688 701
 ```
689 702
 
690
-### Web API Polyfills
703
+## Web API Polyfills
691 704
 
692 705
 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.
693 706
 
@@ -696,13 +709,15 @@ After `0.8.0` we've made some [Web API polyfills](https://github.com/wkh237/reac
696 709
 
697 710
 Here's a [sample app](https://github.com/wkh237/rn-firebase-storage-upload-sample) that uses polyfills to upload files to FireBase.
698 711
 
699
-### Performance Tips
712
+## Performance Tips
713
+
714
+**Read Stream Event Overhead**
700 715
 
701
----
716
+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.
702 717
 
703
-**Reduce RCT Bridge and BASE64 Overheard**
718
+**Reduce RCT Bridge and BASE64 Overhead**
704 719
 
705
-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.
720
+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.
706 721
 
707 722
 <img src="img/performance_1.png" style="width : 100%"/>
708 723
 
@@ -720,35 +735,7 @@ If you're going to concatenate files, you don't have to read the data to JS cont
720 735
 
721 736
 ## Changes
722 737
 
723
-| Version | |
724
-|---|---|
725
-| 0.9.1 | Fix Android Blob constructor asynchronous issue caused by 0.9.0 fs change |
726
-| 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 |
727
-| 0.8.1 | Remove Web API log and fix ios progress report function. |
728
-| 0.8.0 | Added Web API polyfills, support regular request, added timeout option. |
729
-| 0.7.5 | Fix installation script that make it compatible to react-native < 0.28 |
730
-| 0.7.4 | Fix app crash problem in version > 0.27 |
731
-| 0.7.3 | Fix OkHttp dependency issue in version < 0.29 |
732
-| 0.7.2 | Fix cancel request bug |
733
-| 0.7.1 | Fix #57 ios module could not compile on ios version <= 9.3 |
734
-| 0.7.0 | Add support of Android upload progress, and remove AsyncHttpClient dependency from Android native implementation. |
735
-| 0.6.4 | Fix rnpm link script. |
736
-| 0.6.3 | Fix performance issue on IOS, increase max concurrent request limitation from 1. |
737
-| 0.6.2 | Add support of asset file and camera roll files, Support custom MIME type when sending multipart request, thanks @smartt |
738
-| 0.6.1 | Fix #37 progress report API issue on IOS |
739
-| 0.6.0 | Add readFile and writeFile API for easier file access, also added Android download manager support. |
740
-| 0.5.8 | Fix #33 PUT request will always be sent as POST on Android |
741
-| 0.5.7 | Fix #31 #30 Xcode pre 7.3 build error |
742
-| 0.5.6 | Add support for IOS network status indicator. Fix file stream ASCII reader bug. |
743
-| 0.5.5 | Remove work in progress code added in 0.5.2 which may cause memory leaks. |
744
-| 0.5.4 | Fix #30 #31 build build error, and improve memory efficiency. |
745
-| 0.5.3 | Add API for access untrusted SSL server |
746
-| 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 |
747
-| 0.5.0 | Upload/download with direct access to file storage, and also added file access APIs |
748
-| 0.4.2 | Supports upload/download progress |
749
-| 0.4.1 | Fix upload form-data missing file extension problem on Android |
750
-| 0.4.0 | Add base-64 encode/decode library and API |
751
-| ~0.3.0 | Upload/Download octet-stream and form-data |
738
+See [release notes](https://github.com/wkh237/react-native-fetch-blob/releases)
752 739
 
753 740
 ### Development
754 741
 

+ 6
- 4
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java Dosyayı Görüntüle

@@ -236,13 +236,15 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
236 236
     }
237 237
 
238 238
     @ReactMethod
239
-    public void enableProgressReport(String taskId) {
240
-        RNFetchBlobReq.progressReport.put(taskId, true);
239
+    public void enableProgressReport(String taskId, int interval, int count) {
240
+        RNFetchBlobProgressConfig config = new RNFetchBlobProgressConfig(true, interval, count, RNFetchBlobProgressConfig.ReportType.Download);
241
+        RNFetchBlobReq.progressReport.put(taskId, config);
241 242
     }
242 243
 
243 244
     @ReactMethod
244
-    public void enableUploadProgressReport(String taskId) {
245
-        RNFetchBlobReq.uploadProgressReport.put(taskId, true);
245
+    public void enableUploadProgressReport(String taskId, int interval, int count) {
246
+        RNFetchBlobProgressConfig config = new RNFetchBlobProgressConfig(true, interval, count, RNFetchBlobProgressConfig.ReportType.Upload);
247
+        RNFetchBlobReq.uploadProgressReport.put(taskId, config);
246 248
     }
247 249
 
248 250
     @ReactMethod

+ 12
- 8
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java Dosyayı Görüntüle

@@ -31,6 +31,7 @@ public class RNFetchBlobBody extends RequestBody{
31 31
     RNFetchBlobReq.RequestType requestType;
32 32
     MediaType mime;
33 33
     File bodyCache;
34
+    int reported = 0;
34 35
     Boolean chunkedEncoding = false;
35 36
 
36 37
 
@@ -370,14 +371,17 @@ public class RNFetchBlobBody extends RequestBody{
370 371
      * @param written
371 372
      */
372 373
     private void emitUploadProgress(int written) {
373
-        WritableMap args = Arguments.createMap();
374
-        args.putString("taskId", mTaskId);
375
-        args.putString("written", String.valueOf(written));
376
-        args.putString("total", String.valueOf(contentLength));
377
-
378
-        // emit event to js context
379
-        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
380
-                .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
374
+        RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);
375
+        if(config != null && contentLength != 0 && config.shouldReport((float)written/contentLength)) {
376
+            WritableMap args = Arguments.createMap();
377
+            args.putString("taskId", mTaskId);
378
+            args.putString("written", String.valueOf(written));
379
+            args.putString("total", String.valueOf(contentLength));
380
+
381
+            // emit event to js context
382
+            RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
383
+                    .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
384
+        }
381 385
     }
382 386
 
383 387
 }

+ 39
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java Dosyayı Görüntüle

@@ -0,0 +1,39 @@
1
+package com.RNFetchBlob;
2
+
3
+/**
4
+ * Created by wkh237 on 2016/9/24.
5
+ */
6
+public class RNFetchBlobProgressConfig {
7
+
8
+    public enum ReportType {
9
+        Upload,
10
+        Download
11
+    };
12
+
13
+    long lastTick = 0;
14
+    int tick = 0;
15
+    int count = -1;
16
+    public int interval = -1;
17
+    public boolean enable = false;
18
+    public ReportType type = ReportType.Download;
19
+
20
+    RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
21
+        this.enable = report;
22
+        this.interval = interval;
23
+        this.type = type;
24
+        this.count = count;
25
+    }
26
+
27
+    public boolean shouldReport(float progress) {
28
+        boolean checkCount = true;
29
+        if(count > 0 && progress > 0)
30
+            checkCount = Math.floor(progress*count)> tick;
31
+        boolean result = (System.currentTimeMillis() - lastTick> interval) && enable && checkCount;
32
+        if(result) {
33
+            tick++;
34
+            lastTick = System.currentTimeMillis();
35
+        }
36
+        return result;
37
+    }
38
+
39
+}

+ 10
- 9
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java Dosyayı Görüntüle

@@ -70,8 +70,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
70 70
     }
71 71
 
72 72
     public static HashMap<String, Call> taskTable = new HashMap<>();
73
-    static HashMap<String, Boolean> progressReport = new HashMap<>();
74
-    static HashMap<String, Boolean> uploadProgressReport = new HashMap<>();
73
+    static HashMap<String, RNFetchBlobProgressConfig> progressReport = new HashMap<>();
74
+    static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
75 75
     static ConnectionPool pool = new ConnectionPool();
76 76
 
77 77
     ReactApplicationContext ctx;
@@ -92,7 +92,6 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
92 92
     ResponseFormat responseFormat = ResponseFormat.Auto;
93 93
     WritableMap respInfo;
94 94
     boolean timeout = false;
95
-
96 95
     ArrayList<String> redirects = new ArrayList<>();
97 96
 
98 97
     public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) {
@@ -221,7 +220,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
221 220
                 }
222 221
             }
223 222
 
224
-            if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
223
+            if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
225 224
                 String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
226 225
 
227 226
                 if(rawRequestBodyArray != null) {
@@ -282,7 +281,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
282 281
                     break;
283 282
 
284 283
                 case WithoutBody:
285
-                    if(method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("PUT"))
284
+                    if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch"))
286 285
                     {
287 286
                         builder.method(method, RequestBody.create(null, new byte[0]));
288 287
                     }
@@ -396,6 +395,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
396 395
                         DownloadManager dm = (DownloadManager)RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
397 396
                         dm.addCompletedDownload(title, desc, scannable, mime, destPath, contentLength, notification);
398 397
                     }
398
+
399 399
                     done(response);
400 400
                 }
401 401
             });
@@ -490,6 +490,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
490 490
                 } catch (Exception ignored) {
491 491
                     ignored.printStackTrace();
492 492
                 }
493
+                this.destPath = this.destPath.replace("?append=true", "");
493 494
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
494 495
                 break;
495 496
             default:
@@ -510,8 +511,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
510 511
      * @param taskId Task ID of the HTTP task.
511 512
      * @return Task ID of the target task
512 513
      */
513
-    public static boolean isReportProgress(String taskId) {
514
-        if(!progressReport.containsKey(taskId)) return false;
514
+    public static RNFetchBlobProgressConfig getReportProgress(String taskId) {
515
+        if(!progressReport.containsKey(taskId)) return null;
515 516
         return progressReport.get(taskId);
516 517
     }
517 518
 
@@ -520,8 +521,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
520 521
      * @param taskId Task ID of the HTTP task.
521 522
      * @return Task ID of the target task
522 523
      */
523
-    public static boolean isReportUploadProgress(String taskId) {
524
-        if(!uploadProgressReport.containsKey(taskId)) return false;
524
+    public static RNFetchBlobProgressConfig getReportUploadProgress(String taskId) {
525
+        if(!uploadProgressReport.containsKey(taskId)) return null;
525 526
         return uploadProgressReport.get(taskId);
526 527
     }
527 528
 

+ 4
- 6
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobDefaultResp.java Dosyayı Görüntüle

@@ -1,7 +1,7 @@
1 1
 package com.RNFetchBlob.Response;
2 2
 
3
-import com.RNFetchBlob.RNFetchBlob;
4 3
 import com.RNFetchBlob.RNFetchBlobConst;
4
+import com.RNFetchBlob.RNFetchBlobProgressConfig;
5 5
 import com.RNFetchBlob.RNFetchBlobReq;
6 6
 import com.facebook.react.bridge.Arguments;
7 7
 import com.facebook.react.bridge.ReactApplicationContext;
@@ -11,14 +11,10 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
11 11
 import java.io.IOException;
12 12
 import java.nio.charset.Charset;
13 13
 
14
-import okhttp3.Call;
15
-import okhttp3.Callback;
16 14
 import okhttp3.MediaType;
17
-import okhttp3.Response;
18 15
 import okhttp3.ResponseBody;
19 16
 import okio.Buffer;
20 17
 import okio.BufferedSource;
21
-import okio.ForwardingSource;
22 18
 import okio.Okio;
23 19
 import okio.Source;
24 20
 import okio.Timeout;
@@ -69,7 +65,9 @@ public class RNFetchBlobDefaultResp extends ResponseBody {
69 65
 
70 66
             long read =  mOriginalSource.read(sink, byteCount);
71 67
             bytesRead += read > 0 ? read : 0;
72
-            if(RNFetchBlobReq.isReportProgress(mTaskId)) {
68
+            RNFetchBlobProgressConfig reportConfig = RNFetchBlobReq.getReportProgress(mTaskId);
69
+            long cLen = contentLength();
70
+            if(reportConfig != null && cLen != 0 && reportConfig.shouldReport(bytesRead/contentLength())) {
73 71
                 WritableMap args = Arguments.createMap();
74 72
                 args.putString("taskId", mTaskId);
75 73
                 args.putString("written", String.valueOf(bytesRead));

+ 25
- 16
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java Dosyayı Görüntüle

@@ -3,6 +3,7 @@ package com.RNFetchBlob.Response;
3 3
 import android.util.Log;
4 4
 
5 5
 import com.RNFetchBlob.RNFetchBlobConst;
6
+import com.RNFetchBlob.RNFetchBlobProgressConfig;
6 7
 import com.RNFetchBlob.RNFetchBlobReq;
7 8
 import com.facebook.react.bridge.Arguments;
8 9
 import com.facebook.react.bridge.ReactApplicationContext;
@@ -41,10 +42,13 @@ public class RNFetchBlobFileResp extends ResponseBody {
41 42
         assert path != null;
42 43
         this.mPath = path;
43 44
         if (path != null) {
45
+            boolean appendToExistingFile = path.contains("?append=true");
46
+            path = path.replace("?append=true", "");
47
+            mPath = path;
44 48
             File f = new File(path);
45 49
             if(f.exists() == false)
46 50
                 f.createNewFile();
47
-            ofStream = new FileOutputStream(new File(path));
51
+            ofStream = new FileOutputStream(new File(path), appendToExistingFile);
48 52
         }
49 53
     }
50 54
 
@@ -67,22 +71,27 @@ public class RNFetchBlobFileResp extends ResponseBody {
67 71
     private class ProgressReportingSource implements Source {
68 72
         @Override
69 73
         public long read(Buffer sink, long byteCount) throws IOException {
70
-            byte [] bytes = new byte[(int) byteCount];
71
-            long read = originalBody.byteStream().read(bytes, 0, (int) byteCount);
72
-            bytesDownloaded += read > 0 ? read : 0;
73
-            Log.i("bytes downloaded", String.valueOf(byteCount) +"/"+ String.valueOf(read) + "=" + String.valueOf(bytesDownloaded));
74
-            if(read > 0 ) {
75
-                ofStream.write(bytes, 0, (int) read);
74
+            try {
75
+                byte[] bytes = new byte[(int) byteCount];
76
+                long read = originalBody.byteStream().read(bytes, 0, (int) byteCount);
77
+                bytesDownloaded += read > 0 ? read : 0;
78
+                Log.i("bytes downloaded", String.valueOf(byteCount) + "/" + String.valueOf(read) + "=" + String.valueOf(bytesDownloaded));
79
+                if (read > 0) {
80
+                    ofStream.write(bytes, 0, (int) read);
81
+                }
82
+                RNFetchBlobProgressConfig reportConfig = RNFetchBlobReq.getReportProgress(mTaskId);
83
+                if (reportConfig != null && contentLength() != 0 &&reportConfig.shouldReport(bytesDownloaded / contentLength())) {
84
+                    WritableMap args = Arguments.createMap();
85
+                    args.putString("taskId", mTaskId);
86
+                    args.putString("written", String.valueOf(bytesDownloaded));
87
+                    args.putString("total", String.valueOf(contentLength()));
88
+                    rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
89
+                            .emit(RNFetchBlobConst.EVENT_PROGRESS, args);
90
+                }
91
+                return read;
92
+            } catch(Exception ex) {
93
+                return -1;
76 94
             }
77
-            if(RNFetchBlobReq.isReportProgress(mTaskId)) {
78
-                WritableMap args = Arguments.createMap();
79
-                args.putString("taskId", mTaskId);
80
-                args.putString("written", String.valueOf(bytesDownloaded));
81
-                args.putString("total", String.valueOf(contentLength()));
82
-                rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
83
-                        .emit(RNFetchBlobConst.EVENT_PROGRESS, args);
84
-            }
85
-            return read;
86 95
         }
87 96
 
88 97
         @Override

+ 21
- 20
src/fs.js Dosyayı Görüntüle

@@ -61,15 +61,15 @@ function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'ut
61 61
   return new Promise((resolve, reject) => {
62 62
     let handler = (err) => {
63 63
       if(err)
64
-      reject(err)
64
+        reject(new Error(err))
65 65
       else
66
-      resolve()
66
+        resolve()
67 67
     }
68 68
     if(encoding.toLowerCase() === 'ascii') {
69 69
       if(Array.isArray(data))
70 70
         RNFetchBlob.createFileASCII(path, data, handler)
71 71
       else
72
-        reject('`data` of ASCII file must be an array contains numbers')
72
+        reject(new Error('`data` of ASCII file must be an array contains numbers'))
73 73
     }
74 74
     else {
75 75
       RNFetchBlob.createFile(path, data, encoding, handler)
@@ -96,7 +96,7 @@ function writeStream(
96 96
   return new Promise((resolve, reject) => {
97 97
     RNFetchBlob.writeStream(path, encoding || 'base64', append || false, (err, streamId:string) => {
98 98
       if(err)
99
-        reject(err)
99
+        reject(new Error(err))
100 100
       else
101 101
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
102 102
     })
@@ -129,7 +129,7 @@ function mkdir(path:string):Promise {
129 129
   return new Promise((resolve, reject) => {
130 130
     RNFetchBlob.mkdir(path, (err, res) => {
131 131
       if(err)
132
-        reject(err)
132
+        reject(new Error(err))
133 133
       else
134 134
         resolve()
135 135
     })
@@ -145,7 +145,7 @@ function mkdir(path:string):Promise {
145 145
  */
146 146
 function readFile(path:string, encoding:string, bufferSize:?number):Promise<any> {
147 147
   if(typeof path !== 'string')
148
-    return Promise.reject('Invalid argument "path" ')
148
+    return Promise.reject(new Error('Invalid argument "path" '))
149 149
   return RNFetchBlob.readFile(path, encoding)
150 150
 }
151 151
 
@@ -162,12 +162,12 @@ function writeFile(path:string, data:string | Array<number>, encoding:?string):P
162 162
     return Promise.reject('Invalid argument "path" ')
163 163
   if(encoding.toLocaleLowerCase() === 'ascii') {
164 164
     if(!Array.isArray(data))
165
-      Promise.reject(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)
165
+      Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`))
166 166
     else
167 167
       return RNFetchBlob.writeFileArray(path, data, false);
168 168
   } else {
169 169
     if(typeof data !== 'string')
170
-      Promise.reject(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)
170
+      Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`))
171 171
     else
172 172
       return RNFetchBlob.writeFile(path, encoding, data, false);
173 173
   }
@@ -179,12 +179,12 @@ function appendFile(path:string, data:string | Array<number>, encoding:?string):
179 179
     return Promise.reject('Invalid argument "path" ')
180 180
   if(encoding.toLocaleLowerCase() === 'ascii') {
181 181
     if(!Array.isArray(data))
182
-      Promise.reject(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)
182
+      Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`))
183 183
     else
184 184
       return RNFetchBlob.writeFileArray(path, data, true);
185 185
   } else {
186 186
     if(typeof data !== 'string')
187
-      Promise.reject(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)
187
+      Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`))
188 188
     else
189 189
       return RNFetchBlob.writeFile(path, encoding, data, true);
190 190
   }
@@ -199,7 +199,7 @@ function stat(path:string):Promise<RNFetchBlobFile> {
199 199
   return new Promise((resolve, reject) => {
200 200
     RNFetchBlob.stat(path, (err, stat) => {
201 201
       if(err)
202
-        reject(err)
202
+        reject(new Error(err))
203 203
       else
204 204
         resolve(stat)
205 205
     })
@@ -215,7 +215,7 @@ function scanFile(pairs:any):Promise {
215 215
   return new Promise((resolve, reject) => {
216 216
     RNFetchBlob.scanFile(pairs, (err) => {
217 217
       if(err)
218
-        reject(err)
218
+        reject(new Error(err))
219 219
       else
220 220
         resolve()
221 221
     })
@@ -226,7 +226,7 @@ function cp(path:string, dest:string):Promise<boolean> {
226 226
   return new Promise((resolve, reject) => {
227 227
     RNFetchBlob.cp(path, dest, (err, res) => {
228 228
       if(err)
229
-        reject(err)
229
+        reject(new Error(err))
230 230
       else
231 231
         resolve(res)
232 232
     })
@@ -237,7 +237,7 @@ function mv(path:string, dest:string):Promise<boolean> {
237 237
   return new Promise((resolve, reject) => {
238 238
     RNFetchBlob.mv(path, dest, (err, res) => {
239 239
       if(err)
240
-        reject(err)
240
+        reject(new Error(err))
241 241
       else
242 242
         resolve(res)
243 243
     })
@@ -248,7 +248,7 @@ function lstat(path:string):Promise<Array<RNFetchBlobFile>> {
248 248
   return new Promise((resolve, reject) => {
249 249
     RNFetchBlob.lstat(path, (err, stat) => {
250 250
       if(err)
251
-        reject(err)
251
+        reject(new Error(err))
252 252
       else
253 253
         resolve(stat)
254 254
     })
@@ -259,7 +259,7 @@ function ls(path:string):Promise<Array<String>> {
259 259
   return new Promise((resolve, reject) => {
260 260
     RNFetchBlob.ls(path, (err, res) => {
261 261
       if(err)
262
-        reject(err)
262
+        reject(new Error(err))
263 263
       else
264 264
         resolve(res)
265 265
     })
@@ -274,8 +274,9 @@ function ls(path:string):Promise<Array<String>> {
274 274
 function unlink(path:string):Promise {
275 275
   return new Promise((resolve, reject) => {
276 276
     RNFetchBlob.unlink(path, (err) => {
277
-      if(err)
278
-        reject(err)
277
+      if(err) {
278
+        reject(new Error(err))
279
+      }
279 280
       else
280 281
         resolve()
281 282
     })
@@ -295,7 +296,7 @@ function exists(path:string):Promise<bool, bool> {
295 296
         resolve(exist)
296 297
       })
297 298
     } catch(err) {
298
-      reject(err)
299
+      reject(new Error(err))
299 300
     }
300 301
   })
301 302
 
@@ -331,7 +332,7 @@ function isDir(path:string):Promise<bool, bool> {
331 332
         resolve(isDir)
332 333
       })
333 334
     } catch(err) {
334
-      reject(err)
335
+      reject(new Error(err))
335 336
     }
336 337
   })
337 338
 

+ 59
- 5
src/index.js Dosyayı Görüntüle

@@ -204,7 +204,7 @@ function fetch(...args:any):Promise {
204 204
   // create task ID for receiving progress event
205 205
   let taskId = getUUID()
206 206
   let options = this || {}
207
-  let subscription, subscriptionUpload, stateEvent
207
+  let subscription, subscriptionUpload, stateEvent, partEvent
208 208
   let respInfo = {}
209 209
   let [method, url, headers, body] = [...args]
210 210
 
@@ -241,6 +241,10 @@ function fetch(...args:any):Promise {
241 241
       console.log(e , 'EXPIRED!!')
242 242
       if(e.taskId === taskId && promise.onExpire) {
243 243
         promise.onExpire(e)
244
+
245
+    partEvent = emitter.addListener('RNFetchBlobServerPush', (e) => {
246
+      if(e.taskId === taskId && promise.onPartData) {
247
+        promise.onPartData(e.chunk)
244 248
       }
245 249
     })
246 250
 
@@ -269,9 +273,11 @@ function fetch(...args:any):Promise {
269 273
       subscription.remove()
270 274
       subscriptionUpload.remove()
271 275
       stateEvent.remove()
276
+      partEvent.remove()
272 277
       delete promise['progress']
273 278
       delete promise['uploadProgress']
274 279
       delete promise['stateChange']
280
+      delete promise['part']
275 281
       delete promise['cancel']
276 282
       // delete promise['expire']
277 283
       promise.cancel = () => {}
@@ -295,14 +301,44 @@ function fetch(...args:any):Promise {
295 301
 
296 302
   // extend Promise object, add `progress`, `uploadProgress`, and `cancel`
297 303
   // method for register progress event handler and cancel request.
298
-  promise.progress = (fn) => {
304
+  // Add second parameter for performance purpose #140
305
+  // When there's only one argument pass to this method, use default `interval`
306
+  // and `count`, otherwise use the given on.
307
+  // TODO : code refactor, move `uploadProgress` and `progress` to StatefulPromise
308
+  promise.progress = (...args) => {
309
+    let interval = 250
310
+    let count = -1
311
+    let fn = () => {}
312
+    if(args.length === 2) {
313
+      interval = args[0].interval || interval
314
+      count = args[0].count || count
315
+      fn = args[1]
316
+    }
317
+    else {
318
+      fn = args[0]
319
+    }
299 320
     promise.onProgress = fn
300
-    RNFetchBlob.enableProgressReport(taskId)
321
+    RNFetchBlob.enableProgressReport(taskId, interval, count)
301 322
     return promise
302 323
   }
303
-  promise.uploadProgress = (fn) => {
324
+  promise.uploadProgress = (...args) => {
325
+    let interval = 250
326
+    let count = -1
327
+    let fn = () => {}
328
+    if(args.length === 2) {
329
+      interval = args[0].interval || interval
330
+      count = args[0].count || count
331
+      fn = args[1]
332
+    }
333
+    else {
334
+      fn = args[0]
335
+    }
304 336
     promise.onUploadProgress = fn
305
-    RNFetchBlob.enableUploadProgressReport(taskId)
337
+    RNFetchBlob.enableUploadProgressReport(taskId, interval, count)
338
+    return promise
339
+  }
340
+  promise.part = (fn) => {
341
+    promise.onPartData = fn
306 342
     return promise
307 343
   }
308 344
   promise.stateChange = (fn) => {
@@ -356,6 +392,24 @@ class FetchBlobResponse {
356 392
     this.info = ():RNFetchBlobResponseInfo => {
357 393
       return this.respInfo
358 394
     }
395
+
396
+    this.array = ():Promise<Array> => {
397
+      let cType = info.headers['Content-Type'] || info.headers['content-type']
398
+      return new Promise((resolve, reject) => {
399
+        switch(this.type) {
400
+          case 'base64':
401
+            // TODO : base64 to array buffer
402
+          break
403
+          case 'path':
404
+            fs.readFile(this.data, 'ascii').then(resolve)
405
+          break
406
+          default:
407
+            // TODO : text to array buffer
408
+          break
409
+        }
410
+      })
411
+    }
412
+
359 413
     /**
360 414
      * Convert result to javascript RNFetchBlob object.
361 415
      * @return {Promise<Blob>} Return a promise resolves Blob object.

+ 6
- 0
src/ios/RNFetchBlob.xcodeproj/project.pbxproj Dosyayı Görüntüle

@@ -12,6 +12,7 @@
12 12
 		A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; };
13 13
 		A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = A15C30131CD25C330074CB35 /* RNFetchBlob.m */; };
14 14
 		A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */ = {isa = PBXBuildFile; fileRef = A15C30111CD25C330074CB35 /* RNFetchBlob.h */; };
15
+		A19B48251D98102400E6868A /* RNFetchBlobProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = A19B48241D98102400E6868A /* RNFetchBlobProgress.m */; };
15 16
 		A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */; };
16 17
 /* End PBXBuildFile section */
17 18
 
@@ -37,6 +38,8 @@
37 38
 		A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFetchBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
38 39
 		A15C30111CD25C330074CB35 /* RNFetchBlob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNFetchBlob.h; path = RNFetchBlob/RNFetchBlob.h; sourceTree = "<group>"; };
39 40
 		A15C30131CD25C330074CB35 /* RNFetchBlob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFetchBlob.m; path = RNFetchBlob/RNFetchBlob.m; sourceTree = "<group>"; };
41
+		A19B48231D98100800E6868A /* RNFetchBlobProgress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobProgress.h; sourceTree = "<group>"; };
42
+		A19B48241D98102400E6868A /* RNFetchBlobProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobProgress.m; sourceTree = "<group>"; };
40 43
 		A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobReqBuilder.h; sourceTree = "<group>"; };
41 44
 		A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobReqBuilder.m; sourceTree = "<group>"; };
42 45
 		A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOS7Polyfill.h; sourceTree = "<group>"; };
@@ -63,6 +66,8 @@
63 66
 		A15C30051CD25C330074CB35 = {
64 67
 			isa = PBXGroup;
65 68
 			children = (
69
+				A19B48241D98102400E6868A /* RNFetchBlobProgress.m */,
70
+				A19B48231D98100800E6868A /* RNFetchBlobProgress.h */,
66 71
 				A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */,
67 72
 				A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */,
68 73
 				A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */,
@@ -147,6 +152,7 @@
147 152
 				A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */,
148 153
 				A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */,
149 154
 				A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */,
155
+				A19B48251D98102400E6868A /* RNFetchBlobProgress.m in Sources */,
150 156
 				A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */,
151 157
 				A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */,
152 158
 			);

+ 9
- 5
src/ios/RNFetchBlob/RNFetchBlob.m Dosyayı Görüntüle

@@ -13,6 +13,7 @@
13 13
 #import "RNFetchBlobNetwork.h"
14 14
 #import "RNFetchBlobConst.h"
15 15
 #import "RNFetchBlobReqBuilder.h"
16
+#import "RNFetchBlobProgress.h"
16 17
 
17 18
 
18 19
 __strong RCTBridge * bridgeRef;
@@ -216,7 +217,7 @@ RCT_EXPORT_METHOD(unlink:(NSString *)path callback:(RCTResponseSenderBlock) call
216 217
     NSError * error = nil;
217 218
     NSString * tmpPath = nil;
218 219
     [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
219
-    if(error == nil)
220
+    if(error == nil || [[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
220 221
         callback(@[[NSNull null]]);
221 222
     else
222 223
         callback(@[[NSString stringWithFormat:@"failed to unlink file or path at %@", path]]);
@@ -417,13 +418,16 @@ RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBl
417 418
 }
418 419
 
419 420
 #pragma mark - net.enableProgressReport
420
-RCT_EXPORT_METHOD(enableProgressReport:(NSString *)taskId {
421
-    [RNFetchBlobNetwork enableProgressReport:taskId];
421
+RCT_EXPORT_METHOD(enableProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count  {
422
+    
423
+    RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count];
424
+    [RNFetchBlobNetwork enableProgressReport:taskId config:cfg];
422 425
 })
423 426
 
424 427
 #pragma mark - net.enableUploadProgressReport
425
-RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId {
426
-    [RNFetchBlobNetwork enableUploadProgress:taskId];
428
+RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count{
429
+    RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Upload interval:interval count:count];
430
+    [RNFetchBlobNetwork enableUploadProgress:taskId config:cfg];
427 431
 })
428 432
 
429 433
 #pragma mark - fs.slice

+ 1
- 0
src/ios/RNFetchBlobConst.h Dosyayı Görüntüle

@@ -19,6 +19,7 @@ extern NSString *const MSG_EVENT_ERROR;
19 19
 
20 20
 extern NSString *const EVENT_EXPIRE;
21 21
 extern NSString *const EVENT_PROGRESS;
22
+extern NSString *const EVENT_SERVER_PUSH;
22 23
 extern NSString *const EVENT_PROGRESS_UPLOAD;
23 24
 extern NSString *const EVENT_STATE_CHANGE;
24 25
 

+ 2
- 1
src/ios/RNFetchBlobConst.m Dosyayı Görüntüle

@@ -21,6 +21,7 @@ extern NSString *const CONFIG_KEY = @"key";
21 21
 extern NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes";
22 22
 
23 23
 extern NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
24
+extern NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush";
24 25
 extern NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
25 26
 extern NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
26 27
 extern NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
@@ -40,4 +41,4 @@ extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
40 41
 // response type
41 42
 extern NSString *const RESP_TYPE_BASE64 = @"base64";
42 43
 extern NSString *const RESP_TYPE_UTF8 = @"utf8";
43
-extern NSString *const RESP_TYPE_PATH = @"path";
44
+extern NSString *const RESP_TYPE_PATH = @"path";

+ 4
- 0
src/ios/RNFetchBlobNetwork.h Dosyayı Görüntüle

@@ -11,6 +11,7 @@
11 11
 
12 12
 #import <Foundation/Foundation.h>
13 13
 #import "RCTBridgeModule.h"
14
+#import "RNFetchBlobProgress.h"
14 15
 #import "RNFetchBlobFS.h"
15 16
 
16 17
 typedef void(^CompletionHander)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error);
@@ -21,6 +22,7 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse
21 22
 @property (nullable, nonatomic) NSString * taskId;
22 23
 @property (nonatomic) int expectedBytes;
23 24
 @property (nonatomic) int receivedBytes;
25
+@property (nonatomic) BOOL isServerPush;
24 26
 @property (nullable, nonatomic) NSMutableData * respData;
25 27
 @property (strong, nonatomic) RCTResponseSenderBlock callback;
26 28
 @property (nullable, nonatomic) RCTBridge * bridge;
@@ -40,6 +42,8 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse
40 42
 - (nullable id) init;
41 43
 - (void) sendRequest;
42 44
 - (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
45
++ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config;
46
++ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config;
43 47
 
44 48
 
45 49
 

+ 68
- 24
src/ios/RNFetchBlobNetwork.m Dosyayı Görüntüle

@@ -18,6 +18,7 @@
18 18
 #import "RNFetchBlobReqBuilder.h"
19 19
 #import "IOS7Polyfill.h"
20 20
 #import <CommonCrypto/CommonDigest.h>
21
+#import "RNFetchBlobProgress.h"
21 22
 
22 23
 ////////////////////////////////////////
23 24
 //
@@ -40,7 +41,8 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
40 41
 @interface RNFetchBlobNetwork ()
41 42
 {
42 43
     BOOL * respFile;
43
-    BOOL * isIncrement;
44
+    BOOL isNewPart;
45
+    NSMutableData * partBuffer;
44 46
     NSString * destPath;
45 47
     NSOutputStream * writeStream;
46 48
     long bodyLength;
@@ -93,14 +95,14 @@ NSOperationQueue *taskQueue;
93 95
     return self;
94 96
 }
95 97
 
96
-+ (void) enableProgressReport:(NSString *) taskId
98
++ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
97 99
 {
98
-    [progressTable setValue:@YES forKey:taskId];
100
+    [progressTable setValue:config forKey:taskId];
99 101
 }
100 102
 
101
-+ (void) enableUploadProgress:(NSString *) taskId
103
++ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config
102 104
 {
103
-    [uploadProgressTable setValue:@YES forKey:taskId];
105
+    [uploadProgressTable setValue:config forKey:taskId];
104 106
 }
105 107
 
106 108
 // removing case from headers
@@ -145,7 +147,7 @@ NSOperationQueue *taskQueue;
145 147
     isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue];
146 148
     redirects = [[NSMutableArray alloc] init];
147 149
     [redirects addObject:req.URL.absoluteString];
148
-    
150
+
149 151
     // set response format
150 152
     NSString * rnfbResp = [req.allHTTPHeaderFields valueForKey:@"RNFB-Response"];
151 153
     if([[rnfbResp lowercaseString] isEqualToString:@"base64"])
@@ -165,7 +167,7 @@ NSOperationQueue *taskQueue;
165 167
     // the session trust any SSL certification
166 168
 //    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
167 169
     NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
168
-    
170
+
169 171
     // set request timeout
170 172
     float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
171 173
     if(timeout > 0)
@@ -202,7 +204,7 @@ NSOperationQueue *taskQueue;
202 204
         respData = [[NSMutableData alloc] init];
203 205
         respFile = NO;
204 206
     }
205
-    
207
+
206 208
     __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
207 209
     [taskTable setObject:task forKey:taskId];
208 210
     [task resume];
@@ -211,16 +213,16 @@ NSOperationQueue *taskQueue;
211 213
     if([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES)
212 214
         [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
213 215
     __block UIApplication * app = [UIApplication sharedApplication];
214
-    
216
+
215 217
     // #115 handling task expired when application entering backgound for a long time
216 218
     [app beginBackgroundTaskWithName:taskId expirationHandler:^{
217 219
         NSLog([NSString stringWithFormat:@"session %@ expired event emit", taskId ]);
218 220
         [expirationTable setObject:task forKey:taskId];
219 221
         [app endBackgroundTask:task];
220
-        
222
+
221 223
     }];
222
-    
223
-    
224
+
225
+
224 226
 }
225 227
 
226 228
 // #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
@@ -259,6 +261,26 @@ NSOperationQueue *taskQueue;
259 261
     {
260 262
         NSDictionary *headers = [httpResponse allHeaderFields];
261 263
         NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
264
+        if(self.isServerPush == NO)
265
+        {
266
+            self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
267
+        }
268
+        if(self.isServerPush)
269
+        {
270
+            if(partBuffer != nil)
271
+            {
272
+                [self.bridge.eventDispatcher
273
+                 sendDeviceEventWithName:EVENT_SERVER_PUSH
274
+                 body:@{
275
+                        @"taskId": taskId,
276
+                        @"chunk": [partBuffer base64EncodedStringWithOptions:0],
277
+                        }
278
+                 ];
279
+            }
280
+            partBuffer = [[NSMutableData alloc] init];
281
+            completionHandler(NSURLSessionResponseAllow);
282
+            return;
283
+        }
262 284
         if(respCType != nil)
263 285
         {
264 286
             NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
@@ -320,10 +342,21 @@ NSOperationQueue *taskQueue;
320 342
         @try{
321 343
             NSFileManager * fm = [NSFileManager defaultManager];
322 344
             NSString * folder = [destPath stringByDeletingLastPathComponent];
323
-            if(![fm fileExistsAtPath:folder]) {
345
+            if(![fm fileExistsAtPath:folder])
346
+            {
324 347
                 [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
325 348
             }
326
-            [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
349
+            BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
350
+            // For solving #141 append response data if the file already exists
351
+            // base on PR#139 @kejinliang
352
+            if(appendToExistingFile)
353
+            {
354
+                destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
355
+            }
356
+            if (![fm fileExistsAtPath:destPath])
357
+            {
358
+                [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
359
+            }
327 360
             writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:YES];
328 361
             [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
329 362
             [writeStream open];
@@ -337,9 +370,17 @@ NSOperationQueue *taskQueue;
337 370
     completionHandler(NSURLSessionResponseAllow);
338 371
 }
339 372
 
373
+
340 374
 // download progress handler
341 375
 - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
342 376
 {
377
+    // For #143 handling multipart/x-mixed-replace response
378
+    if(self.isServerPush)
379
+    {
380
+        [partBuffer appendData:data];
381
+        return ;
382
+    }
383
+
343 384
     NSNumber * received = [NSNumber numberWithLong:[data length]];
344 385
     receivedBytes += [received longValue];
345 386
     NSString * chunkString = @"";
@@ -357,8 +398,11 @@ NSOperationQueue *taskQueue;
357 398
     {
358 399
         [writeStream write:[data bytes] maxLength:[data length]];
359 400
     }
360
-
361
-    if([progressTable valueForKey:taskId] == @YES)
401
+    RNFetchBlobProgress * pconfig = [progressTable valueForKey:taskId];
402
+    if(expectedBytes == 0)
403
+        return;
404
+    NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
405
+    if(pconfig != nil && [pconfig shouldReport:now])
362 406
     {
363 407
         [self.bridge.eventDispatcher
364 408
          sendDeviceEventWithName:EVENT_PROGRESS
@@ -380,6 +424,7 @@ NSOperationQueue *taskQueue;
380 424
         session = nil;
381 425
 }
382 426
 
427
+
383 428
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
384 429
 {
385 430
 
@@ -399,11 +444,6 @@ NSOperationQueue *taskQueue;
399 444
     {
400 445
         errMsg = [error localizedDescription];
401 446
     }
402
-    // Fix #72 response with status code 200 ~ 299 considered as success
403
-    else if(respStatus> 299 || respStatus < 200)
404
-    {
405
-        errMsg = [NSString stringWithFormat:@"Request failed, status %d", respStatus];
406
-    }
407 447
     else
408 448
     {
409 449
         if(respFile == YES)
@@ -419,7 +459,7 @@ NSOperationQueue *taskQueue;
419 459
             // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
420 460
             // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
421 461
             NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
422
-            
462
+
423 463
             if(responseFormat == BASE64)
424 464
             {
425 465
                 rnfbRespType = RESP_TYPE_BASE64;
@@ -467,13 +507,17 @@ NSOperationQueue *taskQueue;
467 507
 // upload progress handler
468 508
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
469 509
 {
470
-    if([uploadProgressTable valueForKey:taskId] == @YES) {
510
+    RNFetchBlobProgress * pconfig = [uploadProgressTable valueForKey:taskId];
511
+    if(totalBytesExpectedToWrite == 0)
512
+        return;
513
+    NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
514
+    if(pconfig != nil && [pconfig shouldReport:now]) {
471 515
         [self.bridge.eventDispatcher
472 516
          sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
473 517
          body:@{
474 518
                 @"taskId": taskId,
475 519
                 @"written": [NSString stringWithFormat:@"%d", totalBytesWritten],
476
-                @"total": [NSString stringWithFormat:@"%d", bodyLength]
520
+                @"total": [NSString stringWithFormat:@"%d", totalBytesExpectedToWrite]
477 521
                 }
478 522
          ];
479 523
     }

+ 39
- 0
src/ios/RNFetchBlobProgress.h Dosyayı Görüntüle

@@ -0,0 +1,39 @@
1
+//
2
+//  RNFetchBlobProgress.h
3
+//  RNFetchBlob
4
+//
5
+//  Created by Ben Hsieh on 2016/9/25.
6
+//  Copyright © 2016年 wkh237.github.io. All rights reserved.
7
+//
8
+
9
+#ifndef RNFetchBlobProgress_h
10
+#define RNFetchBlobProgress_h
11
+
12
+#import <Foundation/Foundation.h>
13
+
14
+typedef NS_ENUM(NSUInteger, ProgressType) {
15
+    Download,
16
+    Upload,
17
+};
18
+
19
+@interface RNFetchBlobProgress : NSObject
20
+{
21
+    NSNumber * count;
22
+    NSNumber * interval;
23
+    ProgressType type;
24
+    BOOL enable;
25
+    
26
+}
27
+
28
+@property (nonatomic) NSNumber * count;
29
+@property (nonatomic) NSNumber * interval;
30
+@property (nonatomic) NSInteger type;
31
+@property (nonatomic) BOOL enable;
32
+
33
+-(id)initWithType:(ProgressType)type interval:(NSNumber*)interval count:(NSNumber *)count;
34
+-(BOOL)shouldReport:(NSNumber *) nextProgress;
35
+
36
+
37
+@end
38
+
39
+#endif /* RNFetchBlobProgress_h */

+ 57
- 0
src/ios/RNFetchBlobProgress.m Dosyayı Görüntüle

@@ -0,0 +1,57 @@
1
+//
2
+//  RNFetchBlobProgress.m
3
+//  RNFetchBlob
4
+//
5
+//  Created by Ben Hsieh on 2016/9/25.
6
+//  Copyright © 2016年 wkh237.github.io. All rights reserved.
7
+//
8
+
9
+#import "RNFetchBlobProgress.h"
10
+
11
+@interface RNFetchBlobProgress ()
12
+{
13
+    float progress;
14
+    int tick;
15
+    double lastTick;
16
+}
17
+@end
18
+
19
+@implementation RNFetchBlobProgress
20
+
21
+-(id)initWithType:(ProgressType)type interval:(NSNumber *)interval count:(NSNumber *)count
22
+{
23
+    self = [super init];
24
+    self.count = count;
25
+    self.interval = [NSNumber numberWithFloat:[interval floatValue] /1000];
26
+    self.type = type;
27
+    self.enable = YES;
28
+    lastTick = 0;
29
+    tick = 1;
30
+    return self;
31
+}
32
+
33
+-(BOOL)shouldReport:(NSNumber *)nextProgress
34
+{
35
+    BOOL result = YES;
36
+    float countF = [self.count floatValue];
37
+    if(countF > 0 && [nextProgress floatValue] > 0)
38
+    {
39
+        result = (int)(floorf([nextProgress floatValue]*countF)) >= tick;
40
+    }
41
+    
42
+    NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
43
+    // NSTimeInterval is defined as double
44
+    NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp];
45
+    float delta = [timeStampObj doubleValue] - lastTick;
46
+    BOOL shouldReport = delta > [self.interval doubleValue] && self.enable && result;
47
+    if(shouldReport)
48
+    {
49
+        tick++;
50
+        lastTick = [timeStampObj doubleValue];
51
+    }
52
+    return shouldReport;
53
+    
54
+}
55
+
56
+
57
+@end

+ 2
- 1
src/ios/RNFetchBlobReqBuilder.m Dosyayı Görüntüle

@@ -119,7 +119,8 @@
119 119
                     }
120 120
                     else
121 121
                     {
122
-                        [request setHTTPBody:[NSData dataWithContentsOfFile:orgPath ]];
122
+                        __block NSData * bodyBytes = [NSData dataWithContentsOfFile:orgPath ];
123
+                        [request setHTTPBody:bodyBytes];
123 124
                     }
124 125
                 }
125 126
                 // otherwise convert it as BASE64 data string

+ 1
- 1
src/package.json Dosyayı Görüntüle

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "react-native-fetch-blob",
3
-  "version": "0.9.5-beta.2",
3
+  "version": "0.9.6",
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": {

+ 2
- 2
src/polyfill/Blob.js Dosyayı Görüntüle

@@ -51,7 +51,7 @@ export default class Blob extends EventTarget {
51 51
   }
52 52
 
53 53
   static setLog(level:number) {
54
-    if(number === -1)
54
+    if(level === -1)
55 55
       log.disable()
56 56
     else
57 57
       log.level(level)
@@ -224,7 +224,7 @@ export default class Blob extends EventTarget {
224 224
    * @param  {string} contentType Optional, content type of new Blob object
225 225
    * @return {Blob}
226 226
    */
227
-  slice(start:?number, end:?number, contentType='':?string):Blob {
227
+  slice(start:?number, end:?number, contentType:?string=''):Blob {
228 228
     if(this._closed)
229 229
       throw 'Blob has been released.'
230 230
     log.verbose('slice called', start, end, contentType)

+ 10
- 4
src/polyfill/XMLHttpRequest.js Dosyayı Görüntüle

@@ -35,7 +35,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
35 35
   _response : any = '';
36 36
   _responseText : any = '';
37 37
   _responseHeaders : any = {};
38
-  _responseType : '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' = '';
38
+  _responseType : '' | 'arraybuffer' | 'blob'  | 'json' | 'text' = '';
39 39
   // TODO : not suppoted ATM
40 40
   _responseURL : null = '';
41 41
   _responseXML : null = '';
@@ -87,7 +87,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
87 87
   }
88 88
 
89 89
   static setLog(level:number) {
90
-    if(number === -1)
90
+    if(level === -1)
91 91
       log.disable()
92 92
     else
93 93
       log.level(level)
@@ -272,7 +272,6 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
272 272
     if(e.state === "2") {
273 273
       this._responseHeaders = e.headers
274 274
       this._statusText = e.status
275
-      this._responseType = e.respType || ''
276 275
       this._status = Math.floor(e.status)
277 276
       this._dispatchReadStateChange(XMLHttpRequest.HEADERS_RECEIVED)
278 277
     }
@@ -337,7 +336,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
337 336
     if(resp) {
338 337
       let info = resp.respInfo || {}
339 338
       log.debug(this._url, info, info.respType)
340
-      switch(info.respType) {
339
+      switch(this._responseType) {
341 340
         case 'blob' :
342 341
           resp.blob().then((b) => {
343 342
             this._responseText = resp.text()
@@ -345,6 +344,13 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
345 344
             responseDataReady()
346 345
           })
347 346
         break;
347
+        case 'arraybuffer':
348
+          // TODO : to array buffer
349
+        break
350
+        case 'json':
351
+          this._response = resp.json()
352
+          this._responseText = resp.text()
353
+        break
348 354
         default :
349 355
           this._responseText = resp.text()
350 356
           this._response = this.responseText

+ 2
- 2
src/react-native-fetch-blob.podspec Dosyayı Görüntüle

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

BIN
test-server/cat_fu_mp4/img00001.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00002.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00003.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00004.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00005.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00006.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00007.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00008.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00009.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00010.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00011.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00012.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00013.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00014.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00015.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00016.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00017.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00018.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00019.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00020.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00021.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00022.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00023.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00024.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00025.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00026.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00027.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00028.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00029.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00030.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00031.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00032.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00033.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00034.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00035.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00036.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00037.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00038.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00039.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00040.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00041.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00042.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00043.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00044.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00045.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00046.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00047.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00048.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00049.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00050.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00051.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00052.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00053.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00054.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00055.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00056.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00057.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00058.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00059.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00060.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00061.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00062.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00063.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00064.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00065.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00066.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00067.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00068.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00069.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00070.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00071.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00072.jpeg Dosyayı Görüntüle


BIN
test-server/cat_fu_mp4/img00073.jpeg Dosyayı Görüntüle


+ 0
- 0
test-server/cat_fu_mp4/img00074.jpeg Dosyayı Görüntüle


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor