Browse Source

Merge branch '0.9.2' into 0.10.0

Ben Hsieh 8 years ago
parent
commit
9b7a81bd0e

+ 2
- 2
.github/PULL_REQUEST_TEMPLATE View File

1
 Thank you for making a pull request ! Just a gentle reminder :)
1
 Thank you for making a pull request ! Just a gentle reminder :)
2
 
2
 
3
-1. If the PR is offering a feature please make the request to our "Feature Branch" 0.7.0
4
-2. Bug fix request to "Bug Fix Branch" 0.6.4
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
5
 3. Correct README.md can directly to master
5
 3. Correct README.md can directly to master

+ 5
- 3
README.md View File

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
 
4
 
4
-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.
5
 
6
 
6
 ## Features
7
 ## Features
7
 - Transfer data directly from/to storage without BASE64 bridging
8
 - Transfer data directly from/to storage without BASE64 bridging
37
 
38
 
38
 ## About
39
 ## About
39
 
40
 
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.
41
+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, 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
 
42
 
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)
43
 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)
43
 
44
 
722
 
723
 
723
 | Version | |
724
 | Version | |
724
 |---|---|
725
 |---|---|
726
+| 0.9.2 | Add Blob.slice() method implementation #89 |
725
 | 0.9.1 | Fix Android Blob constructor asynchronous issue caused by 0.9.0 fs change |
727
 | 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 |
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 |
727
 | 0.8.1 | Remove Web API log and fix ios progress report function. |
729
 | 0.8.1 | Remove Web API log and fix ios progress report function. |

+ 1
- 1
package.json View File

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

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

223
     }
223
     }
224
 
224
 
225
     @ReactMethod
225
     @ReactMethod
226
-    public void slice(String src, String dest, int start, int end, String encode, Callback callback) {
227
-        RNFetchBlobFS.slice(src, dest, start, end, encode, callback);
226
+    public void slice(String src, String dest, int start, int end, Promise promise) {
227
+        RNFetchBlobFS.slice(src, dest, start, end, "", promise);
228
     }
228
     }
229
 
229
 
230
     @ReactMethod
230
     @ReactMethod

+ 131
- 81
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

13
 import java.io.ByteArrayInputStream;
13
 import java.io.ByteArrayInputStream;
14
 import java.io.File;
14
 import java.io.File;
15
 import java.io.FileInputStream;
15
 import java.io.FileInputStream;
16
+import java.io.FileNotFoundException;
17
+import java.io.FileOutputStream;
16
 import java.io.IOException;
18
 import java.io.IOException;
17
 import java.io.InputStream;
19
 import java.io.InputStream;
20
+import java.nio.ByteBuffer;
21
+import java.nio.MappedByteBuffer;
18
 import java.util.ArrayList;
22
 import java.util.ArrayList;
19
 import java.util.HashMap;
23
 import java.util.HashMap;
20
 
24
 
33
 public class RNFetchBlobBody extends RequestBody{
37
 public class RNFetchBlobBody extends RequestBody{
34
 
38
 
35
     InputStream requestStream;
39
     InputStream requestStream;
36
-    long contentLength;
40
+    long contentLength = 0;
37
     long bytesWritten = 0;
41
     long bytesWritten = 0;
38
     ReadableArray form;
42
     ReadableArray form;
39
     String mTaskId;
43
     String mTaskId;
40
     String rawBody;
44
     String rawBody;
41
     RNFetchBlobReq.RequestType requestType;
45
     RNFetchBlobReq.RequestType requestType;
42
     MediaType mime;
46
     MediaType mime;
47
+    File bodyCache;
48
+
43
 
49
 
44
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
50
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
45
         this.mTaskId = taskId;
51
         this.mTaskId = taskId;
46
         this.form = form;
52
         this.form = form;
47
         requestType = type;
53
         requestType = type;
48
         mime = contentType;
54
         mime = contentType;
55
+        try {
56
+            bodyCache = createMultipartBodyCache();
57
+            contentLength = bodyCache.length();
58
+        } catch (IOException e) {
59
+            e.printStackTrace();
60
+        }
49
     }
61
     }
50
 
62
 
51
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
63
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
53
         requestType = type;
65
         requestType = type;
54
         this.rawBody = rawBody;
66
         this.rawBody = rawBody;
55
         mime = contentType;
67
         mime = contentType;
68
+        if(rawBody != null) {
69
+            if(requestType == RNFetchBlobReq.RequestType.AsIs)
70
+                contentLength = rawBody.length();
71
+            else
72
+                contentLength = caculateOctetContentLength();
73
+        }
56
     }
74
     }
57
 
75
 
76
+    @Override
77
+    public long contentLength() {
78
+        return contentLength;
79
+    }
58
     @Override
80
     @Override
59
     public MediaType contentType() {
81
     public MediaType contentType() {
60
         return mime;
82
         return mime;
67
         BufferedSink buffer = Okio.buffer(source);
89
         BufferedSink buffer = Okio.buffer(source);
68
         switch (requestType) {
90
         switch (requestType) {
69
             case Form:
91
             case Form:
70
-                writeFormData(sink);
92
+                pipeStreamToSink(new FileInputStream(bodyCache), sink);
71
                 break;
93
                 break;
72
             case SingleFile:
94
             case SingleFile:
73
-                writeOctetData(sink);
95
+                if(requestStream != null)
96
+                    pipeStreamToSink(requestStream, sink);
74
                 break;
97
                 break;
75
             case AsIs:
98
             case AsIs:
76
 				writeRawData(sink);
99
 				writeRawData(sink);
79
         buffer.flush();
102
         buffer.flush();
80
     }
103
     }
81
 
104
 
82
-	private void writeFormData(BufferedSink sink) throws IOException {
105
+    boolean clearRequestBody() {
106
+        try {
107
+            if (bodyCache != null && bodyCache.exists()) {
108
+                bodyCache.delete();
109
+            }
110
+        } catch(Exception e) {
111
+            RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
112
+            return false;
113
+        }
114
+        return true;
115
+    }
116
+
117
+    private long caculateOctetContentLength() {
118
+        long total = 0;
119
+        // upload from storage
120
+        if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
121
+            String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
122
+            orgPath = RNFetchBlobFS.normalizePath(orgPath);
123
+            // upload file from assets
124
+            if (RNFetchBlobFS.isAsset(orgPath)) {
125
+                try {
126
+                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
127
+                    total += RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
128
+                    requestStream = RNFetchBlob.RCTContext.getAssets().open(assetName);
129
+                } catch (IOException e) {
130
+                    RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
131
+                }
132
+            } else {
133
+                File f = new File(RNFetchBlobFS.normalizePath(orgPath));
134
+                try {
135
+                    if(!f.exists())
136
+                        f.createNewFile();
137
+                    total += f.length();
138
+                    requestStream = new FileInputStream(f);
139
+                } catch (Exception e) {
140
+                    RNFetchBlobUtils.emitWarningEvent("RNetchBlob error when counting content length: " +e.getLocalizedMessage());
141
+                }
142
+            }
143
+        } else {
144
+            try {
145
+                byte[] bytes = Base64.decode(rawBody, 0);
146
+                requestStream = new ByteArrayInputStream(bytes);
147
+                total += requestStream.available();
148
+            } catch(Exception ex) {
149
+                RNFetchBlobUtils.emitWarningEvent("RNetchBlob error when counting content length: " +ex.getLocalizedMessage());
150
+            }
151
+        }
152
+        return total;
153
+    }
154
+
155
+    /**
156
+     * Create a temp file that contains content of multipart form data content
157
+     * @return The cache file object
158
+     * @throws IOException
159
+     */
160
+    private File createMultipartBodyCache() throws IOException {
83
         String boundary = "RNFetchBlob-" + mTaskId;
161
         String boundary = "RNFetchBlob-" + mTaskId;
162
+
163
+        File outputDir = RNFetchBlob.RCTContext.getCacheDir(); // context being the Activity pointer
164
+        File outputFile = File.createTempFile("rnfb-form-tmp", "", outputDir);
165
+        FileOutputStream os = new FileOutputStream(outputFile);
166
+
84
         ArrayList<FormField> fields = countFormDataLength();
167
         ArrayList<FormField> fields = countFormDataLength();
85
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
168
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
169
+
86
         for(int i = 0;i < fields.size(); i++) {
170
         for(int i = 0;i < fields.size(); i++) {
87
             FormField field = fields.get(i);
171
             FormField field = fields.get(i);
88
             String data = field.data;
172
             String data = field.data;
95
             if (field.filename != null) {
179
             if (field.filename != null) {
96
                 header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
180
                 header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
97
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
181
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
98
-                sink.write(header.getBytes());
182
+                os.write(header.getBytes());
99
                 // file field header end
183
                 // file field header end
100
                 // upload from storage
184
                 // upload from storage
101
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
185
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
106
                         try {
190
                         try {
107
                             String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
191
                             String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
108
                             InputStream in = ctx.getAssets().open(assetName);
192
                             InputStream in = ctx.getAssets().open(assetName);
109
-                            pipeStreamToSink(in, sink);
193
+                            pipeStreamToFileStream(in, os);
110
                         } catch (IOException e) {
194
                         } catch (IOException e) {
111
-                            Log.e("RNFetchBlob", "Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
195
+                            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
112
                         }
196
                         }
113
                     }
197
                     }
114
                     // data from normal files
198
                     // data from normal files
116
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
200
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
117
                         if(file.exists()) {
201
                         if(file.exists()) {
118
                             FileInputStream fs = new FileInputStream(file);
202
                             FileInputStream fs = new FileInputStream(file);
119
-                            pipeStreamToSink(fs, sink);
203
+                            pipeStreamToFileStream(fs, os);
120
                         }
204
                         }
121
                         else {
205
                         else {
122
-                            Log.e("RNFetchBlob", "Failed to create form data from path :" + orgPath + "file not exists.");
206
+                            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob Failed to create form data from path :" + orgPath + ", file not exists.");
123
                         }
207
                         }
124
                     }
208
                     }
125
                 }
209
                 }
126
                 // base64 embedded file content
210
                 // base64 embedded file content
127
                 else {
211
                 else {
128
                     byte[] b = Base64.decode(data, 0);
212
                     byte[] b = Base64.decode(data, 0);
129
-                    sink.write(b);
213
+                    os.write(b);
130
                     bytesWritten += b.length;
214
                     bytesWritten += b.length;
131
                     emitUploadProgress();
215
                     emitUploadProgress();
132
                 }
216
                 }
136
             else {
220
             else {
137
                 header += "Content-Disposition: form-data; name=" + name + "\r\n";
221
                 header += "Content-Disposition: form-data; name=" + name + "\r\n";
138
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
222
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
139
-                sink.write(header.getBytes());
223
+                os.write(header.getBytes());
140
                 byte[] fieldData = field.data.getBytes();
224
                 byte[] fieldData = field.data.getBytes();
141
                 bytesWritten += fieldData.length;
225
                 bytesWritten += fieldData.length;
142
-                sink.write(fieldData);
226
+                os.write(fieldData);
143
             }
227
             }
144
             // form end
228
             // form end
145
-            sink.write("\r\n".getBytes());
229
+            os.write("\r\n".getBytes());
146
         }
230
         }
147
         // close the form
231
         // close the form
148
         byte[] end = ("--" + boundary + "--\r\n").getBytes();
232
         byte[] end = ("--" + boundary + "--\r\n").getBytes();
149
-        sink.write(end);
233
+        os.write(end);
234
+        os.flush();
235
+        os.close();
236
+        return outputFile;
150
     }
237
     }
151
 
238
 
152
-    /**
153
-     * Write octet stream data to request body
154
-     * @param sink
155
-     */
156
-    private void writeOctetData(BufferedSink sink) throws IOException {
157
-        // upload from storage
158
-        if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
159
-            String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
160
-            orgPath = RNFetchBlobFS.normalizePath(orgPath);
161
-            // upload file from assets
162
-            if (RNFetchBlobFS.isAsset(orgPath)) {
163
-                try {
164
-                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
165
-                    contentLength = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
166
-                    requestStream = RNFetchBlob.RCTContext.getAssets().open(assetName);
167
-                } catch (IOException e) {
168
-//                        e.printStackTrace();
169
-                }
170
-            } else {
171
-                File f = new File(RNFetchBlobFS.normalizePath(orgPath));
172
-                try {
173
-                    if(!f.exists())
174
-                        f.createNewFile();
175
-                    contentLength = f.length();
176
-                    requestStream = new FileInputStream(f);
177
-                } catch (Exception e) {
178
-//                        callback.invoke(e.getLocalizedMessage(), null);
179
-                }
180
-            }
181
-        } else {
182
-            try {
183
-                byte[] bytes = Base64.decode(rawBody, 0);
184
-                contentLength = bytes.length;
185
-                requestStream = new ByteArrayInputStream(bytes);
186
-            } catch(Exception ex) {
187
-
188
-                Log.e("error", ex.getLocalizedMessage());
189
-            }
190
-        }
191
-        if(requestStream != null)
192
-            pipeStreamToSink(requestStream, sink);
193
-
194
-    }
195
-
196
-
197
 	/**
239
 	/**
198
      * Write data to request body as-is
240
      * Write data to request body as-is
199
      * @param sink
241
      * @param sink
200
      */
242
      */
201
 	private void writeRawData(BufferedSink sink) throws IOException {
243
 	private void writeRawData(BufferedSink sink) throws IOException {
202
-		sink.write(rawBody.getBytes());
244
+        byte[] bytes = rawBody.getBytes();
245
+        contentLength = bytes.length;
246
+		sink.write(bytes);
203
 	}
247
 	}
204
 
248
 
205
     /**
249
     /**
210
      */
254
      */
211
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
255
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
212
         byte [] chunk = new byte[10240];
256
         byte [] chunk = new byte[10240];
213
-        int read = stream.read(chunk, 0, 10240);
214
-        if(read > 0) {
215
-            sink.write(chunk, 0, read);
216
-        }
217
-        bytesWritten += read;
218
-        while(read > 0) {
219
-            read = stream.read(chunk, 0, 10240);
257
+        int read;
258
+        while((read = stream.read(chunk, 0, 10240)) > 0) {
220
             if(read > 0) {
259
             if(read > 0) {
221
                 sink.write(chunk, 0, read);
260
                 sink.write(chunk, 0, read);
222
-                bytesWritten += read;
223
-                emitUploadProgress();
224
             }
261
             }
225
-
226
         }
262
         }
227
         stream.close();
263
         stream.close();
228
     }
264
     }
229
 
265
 
230
-    private void writeBufferToSink(byte [] bytes, BufferedSink sink) throws IOException {
231
-        bytesWritten += bytes.length;
232
-        sink.write(bytes);
233
-        emitUploadProgress();
266
+    /**
267
+     * Pipe input stream to a file
268
+     * @param is    The input stream
269
+     * @param os    The output stream to a file
270
+     * @throws IOException
271
+     */
272
+    private void pipeStreamToFileStream(InputStream is, FileOutputStream os) throws IOException {
273
+
274
+        byte[] buf = new byte[10240];
275
+        int len;
276
+        while ((len = is.read(buf)) > 0) {
277
+            os.write(buf, 0, len);
278
+        }
279
+        is.close();
234
     }
280
     }
235
 
281
 
236
     /**
282
     /**
242
         ArrayList<FormField> list = new ArrayList<>();
288
         ArrayList<FormField> list = new ArrayList<>();
243
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
289
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
244
         for(int i = 0;i < form.size(); i++) {
290
         for(int i = 0;i < form.size(); i++) {
245
-            ReadableMap field = form.getMap(i);
246
-            list.add(new FormField(field));
247
-            String data = field.getString("data");
248
-            if (field.hasKey("filename")) {
291
+            FormField field = new FormField(form.getMap(i));
292
+            list.add(field);
293
+            String data = field.data;
294
+            if(data == null) {
295
+                RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
296
+            }
297
+            else if (field.filename != null) {
249
                 // upload from storage
298
                 // upload from storage
250
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
299
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
251
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
300
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
257
                             long length = ctx.getAssets().open(assetName).available();
306
                             long length = ctx.getAssets().open(assetName).available();
258
                             total += length;
307
                             total += length;
259
                         } catch (IOException e) {
308
                         } catch (IOException e) {
260
-
309
+                            RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
261
                         }
310
                         }
262
                     }
311
                     }
263
                     // general files
312
                     // general files
274
             }
323
             }
275
             // data field
324
             // data field
276
             else {
325
             else {
277
-                total += field.getString("data").length();
326
+                total += field.data != null ? field.data.getBytes().length : 0;
278
             }
327
             }
279
         }
328
         }
280
         contentLength = total;
329
         contentLength = total;
301
             else {
350
             else {
302
                 mime = filename == null ? "text/plain" : "application/octet-stream";
351
                 mime = filename == null ? "text/plain" : "application/octet-stream";
303
             }
352
             }
304
-            if(rawData.hasKey("data"))
353
+            if(rawData.hasKey("data")) {
305
                 data = rawData.getString("data");
354
                 data = rawData.getString("data");
355
+            }
306
         }
356
         }
307
     }
357
     }
308
 
358
 

+ 1
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java View File

9
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
9
     public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload";
10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
11
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
11
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
12
+    public static final String EVENT_MESSAGE = "RNFetchBlobMessage";
12
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
13
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
13
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
14
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
14
     public static final String FILE_PREFIX_CONTENT = "content://";
15
     public static final String FILE_PREFIX_CONTENT = "content://";

+ 26
- 22
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

13
 import android.provider.MediaStore;
13
 import android.provider.MediaStore;
14
 import android.util.Base64;
14
 import android.util.Base64;
15
 
15
 
16
+import com.RNFetchBlob.Utils.PathResolver;
16
 import com.facebook.react.bridge.Arguments;
17
 import com.facebook.react.bridge.Arguments;
17
 import com.facebook.react.bridge.Callback;
18
 import com.facebook.react.bridge.Callback;
18
 import com.facebook.react.bridge.Promise;
19
 import com.facebook.react.bridge.Promise;
448
             in = inputStreamFromPath(path);
449
             in = inputStreamFromPath(path);
449
             out = new FileOutputStream(dest);
450
             out = new FileOutputStream(dest);
450
 
451
 
451
-            byte[] buf = new byte[1024];
452
+            byte[] buf = new byte[10240];
452
             int len;
453
             int len;
453
             while ((len = in.read(buf)) > 0) {
454
             while ((len = in.read(buf)) > 0) {
454
                 out.write(buf, 0, len);
455
                 out.write(buf, 0, len);
535
      * @param start Start byte offset in source file
536
      * @param start Start byte offset in source file
536
      * @param end   End byte offset
537
      * @param end   End byte offset
537
      * @param encode
538
      * @param encode
538
-     * @param callback
539
      */
539
      */
540
-    public static void slice(String src, String dest, int start, int end, String encode, Callback callback) {
540
+    public static void slice(String src, String dest, int start, int end, String encode, Promise promise) {
541
         try {
541
         try {
542
-            long expected = end - start;
542
+            File source = new File(src);
543
+            if(!source.exists()) {
544
+                promise.reject("RNFetchBlob.slice error", "source file : " + src + " not exists");
545
+            }
546
+            long size = source.length();
547
+            long max = Math.min(size, end);
548
+            long expected = max - start;
543
             long now = 0;
549
             long now = 0;
544
             FileInputStream in = new FileInputStream(new File(src));
550
             FileInputStream in = new FileInputStream(new File(src));
545
             FileOutputStream out = new FileOutputStream(new File(dest));
551
             FileOutputStream out = new FileOutputStream(new File(dest));
547
             byte [] buffer = new byte[10240];
553
             byte [] buffer = new byte[10240];
548
             while(now < expected) {
554
             while(now < expected) {
549
                 long read = in.read(buffer, 0, 10240);
555
                 long read = in.read(buffer, 0, 10240);
556
+                long remain = expected - now;
550
                 if(read <= 0) {
557
                 if(read <= 0) {
551
                     break;
558
                     break;
552
                 }
559
                 }
560
+                out.write(buffer, 0, (int) Math.min(remain, read));
553
                 now += read;
561
                 now += read;
554
-                out.write(buffer, 0, (int) read);
555
             }
562
             }
556
             in.close();
563
             in.close();
564
+            out.flush();
557
             out.close();
565
             out.close();
558
-            callback.invoke(null, dest);
566
+            promise.resolve(dest);
559
         } catch (Exception e) {
567
         } catch (Exception e) {
560
             e.printStackTrace();
568
             e.printStackTrace();
569
+            promise.reject(e.getLocalizedMessage());
561
         }
570
         }
562
     }
571
     }
563
 
572
 
844
     }
853
     }
845
 
854
 
846
     public static boolean isAsset(String path) {
855
     public static boolean isAsset(String path) {
847
-        return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
856
+        if(path != null)
857
+            return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
858
+        return false;
848
     }
859
     }
849
 
860
 
850
     public static String normalizePath(String path) {
861
     public static String normalizePath(String path) {
851
-        if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
862
+        if(path == null)
863
+            return null;
864
+        Uri uri = Uri.parse(path);
865
+        if(uri.getScheme() == null) {
852
             return path;
866
             return path;
853
         }
867
         }
854
-        else if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_CONTENT)) {
855
-            String filePath = null;
856
-            Uri uri = Uri.parse(path);
857
-            if (uri != null && "content".equals(uri.getScheme())) {
858
-                ContentResolver resolver = RNFetchBlob.RCTContext.getContentResolver();
859
-                Cursor cursor = resolver.query(uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null);
860
-                cursor.moveToFirst();
861
-                filePath = cursor.getString(0);
862
-                cursor.close();
863
-            } else {
864
-                filePath = uri.getPath();
865
-            }
866
-            return filePath;
868
+        if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
869
+            return path;
867
         }
870
         }
868
-        return path;
871
+        else
872
+            return PathResolver.getRealPathFromURI(RNFetchBlob.RCTContext, uri);
869
     }
873
     }
870
 
874
 
871
 }
875
 }

+ 40
- 20
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

25
 import java.io.IOException;
25
 import java.io.IOException;
26
 import java.io.InputStream;
26
 import java.io.InputStream;
27
 import java.net.MalformedURLException;
27
 import java.net.MalformedURLException;
28
+import java.net.SocketException;
28
 import java.net.SocketTimeoutException;
29
 import java.net.SocketTimeoutException;
29
 import java.net.URL;
30
 import java.net.URL;
30
 import java.nio.ByteBuffer;
31
 import java.nio.ByteBuffer;
81
     Callback callback;
82
     Callback callback;
82
     long contentLength;
83
     long contentLength;
83
     long downloadManagerId;
84
     long downloadManagerId;
85
+    RNFetchBlobBody requestBody;
84
     RequestType requestType;
86
     RequestType requestType;
85
     ResponseType responseType;
87
     ResponseType responseType;
86
     WritableMap respInfo;
88
     WritableMap respInfo;
154
 
156
 
155
         // find cached result if `key` property exists
157
         // find cached result if `key` property exists
156
         String cacheKey = this.taskId;
158
         String cacheKey = this.taskId;
157
-		String ext = this.options.appendExt != "" ? "." + this.options.appendExt : "";
159
+		String ext = this.options.appendExt.isEmpty() ? "." + this.options.appendExt : "";
158
 
160
 
159
        	if (this.options.key != null) {
161
        	if (this.options.key != null) {
160
            cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
162
            cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
172
 
174
 
173
         if(this.options.path != null)
175
         if(this.options.path != null)
174
             this.destPath = this.options.path;
176
             this.destPath = this.options.path;
175
-        else if(this.options.fileCache == true)
177
+        else if(this.options.fileCache)
176
             this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
178
             this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
177
 
179
 
178
         OkHttpClient.Builder clientBuilder;
180
         OkHttpClient.Builder clientBuilder;
207
             if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
209
             if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
208
                 String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
210
                 String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
209
 
211
 
210
-                if(cType == null) {
212
+                if(rawRequestBodyArray != null) {
213
+                    requestType = RequestType.Form;
214
+                }
215
+                else if(cType.isEmpty()) {
211
                     builder.header("Content-Type", "application/octet-stream");
216
                     builder.header("Content-Type", "application/octet-stream");
212
                     requestType = RequestType.SingleFile;
217
                     requestType = RequestType.SingleFile;
213
                 }
218
                 }
235
             // set request body
240
             // set request body
236
             switch (requestType) {
241
             switch (requestType) {
237
                 case SingleFile:
242
                 case SingleFile:
238
-                    builder.method(method, new RNFetchBlobBody(
243
+                    requestBody = new RNFetchBlobBody(
239
                             taskId,
244
                             taskId,
240
                             requestType,
245
                             requestType,
241
                             rawRequestBody,
246
                             rawRequestBody,
242
                             MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
247
                             MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
243
-                    ));
248
+                    );
249
+                    builder.method(method, requestBody);
244
                     break;
250
                     break;
245
                 case AsIs:
251
                 case AsIs:
246
-                    builder.method(method, new RNFetchBlobBody(
252
+                    requestBody = new RNFetchBlobBody(
247
                             taskId,
253
                             taskId,
248
                             requestType,
254
                             requestType,
249
                             rawRequestBody,
255
                             rawRequestBody,
250
                             MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
256
                             MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
251
-                    ));
257
+                    );
258
+                    builder.method(method, requestBody);
252
                     break;
259
                     break;
253
                 case Form:
260
                 case Form:
254
-                    builder.method(method, new RNFetchBlobBody(
261
+                    String boundary = "RNFetchBlob-" + taskId;
262
+                    requestBody = new RNFetchBlobBody(
255
                             taskId,
263
                             taskId,
256
                             requestType,
264
                             requestType,
257
                             rawRequestBodyArray,
265
                             rawRequestBodyArray,
258
-                            MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
259
-                    ));
266
+                            MediaType.parse("multipart/form-data; boundary="+ boundary)
267
+                    );
268
+                    builder.method(method, requestBody);
260
                     break;
269
                     break;
261
 
270
 
262
                 case WithoutBody:
271
                 case WithoutBody:
271
 
280
 
272
             final Request req = builder.build();
281
             final Request req = builder.build();
273
 
282
 
274
-            // Create response body depends on the responseType
283
+            // Add request interceptor for upload progress event
275
             clientBuilder.addInterceptor(new Interceptor() {
284
             clientBuilder.addInterceptor(new Interceptor() {
276
                 @Override
285
                 @Override
277
                 public Response intercept(Chain chain) throws IOException {
286
                 public Response intercept(Chain chain) throws IOException {
300
                                 break;
309
                                 break;
301
                         }
310
                         }
302
                         return originalResponse.newBuilder().body(extended).build();
311
                         return originalResponse.newBuilder().body(extended).build();
303
-                    } catch(Exception ex) {
312
+                    }
313
+                    catch(SocketException e) {
314
+                        timeout = true;
315
+                    }
316
+                    catch (SocketTimeoutException e ){
304
                         timeout = true;
317
                         timeout = true;
318
+                    } catch(Exception ex) {
319
+                        RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + ex.getLocalizedMessage());
305
                     }
320
                     }
306
                     return chain.proceed(chain.request());
321
                     return chain.proceed(chain.request());
307
                 }
322
                 }
336
                     }
351
                     }
337
                     else
352
                     else
338
                         callback.invoke(e.getLocalizedMessage(), null, null);
353
                         callback.invoke(e.getLocalizedMessage(), null, null);
339
-                    removeTaskInfo();
354
+                    releaseTaskResource();
340
                 }
355
                 }
341
 
356
 
342
                 @Override
357
                 @Override
366
 
381
 
367
         } catch (Exception error) {
382
         } catch (Exception error) {
368
             error.printStackTrace();
383
             error.printStackTrace();
369
-            taskTable.remove(taskId);
384
+            releaseTaskResource();
370
             callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause());
385
             callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause());
371
         }
386
         }
372
     }
387
     }
374
     /**
389
     /**
375
      * Remove cached information of the HTTP task
390
      * Remove cached information of the HTTP task
376
      */
391
      */
377
-    private void removeTaskInfo() {
392
+    private void releaseTaskResource() {
378
         if(taskTable.containsKey(taskId))
393
         if(taskTable.containsKey(taskId))
379
             taskTable.remove(taskId);
394
             taskTable.remove(taskId);
380
         if(uploadProgressReport.containsKey(taskId))
395
         if(uploadProgressReport.containsKey(taskId))
381
             uploadProgressReport.remove(taskId);
396
             uploadProgressReport.remove(taskId);
382
         if(progressReport.containsKey(taskId))
397
         if(progressReport.containsKey(taskId))
383
             progressReport.remove(taskId);
398
             progressReport.remove(taskId);
399
+        if(requestBody != null)
400
+            requestBody.clearRequestBody();
384
     }
401
     }
385
 
402
 
386
     /**
403
     /**
439
                     // It uses customized response body which is able to report download progress
456
                     // It uses customized response body which is able to report download progress
440
                     // and write response data to destination path.
457
                     // and write response data to destination path.
441
                     resp.body().bytes();
458
                     resp.body().bytes();
442
-                } catch (Exception ignored) {}
459
+                } catch (Exception ignored) {
460
+                    ignored.printStackTrace();
461
+                }
443
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
462
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
444
                 break;
463
                 break;
445
             default:
464
             default:
450
                 }
469
                 }
451
                 break;
470
                 break;
452
         }
471
         }
453
-        removeTaskInfo();
454
-//        resp.close();
472
+        if(!resp.isSuccessful())
473
+            resp.body().close();
474
+        releaseTaskResource();
455
     }
475
     }
456
 
476
 
457
     /**
477
     /**
583
                         error = ex.getLocalizedMessage();
603
                         error = ex.getLocalizedMessage();
584
                     }
604
                     }
585
                 }
605
                 }
586
-                this.callback.invoke(error, RNFetchBlobConst.RNFB_RESPONSE_PATH, filePath);
587
-                c.close();
606
+                else
607
+                    this.callback.invoke("Download manager could not resolve downloaded file path.", RNFetchBlobConst.RNFB_RESPONSE_PATH, null);
588
             }
608
             }
589
         }
609
         }
590
     }
610
     }

+ 14
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java View File

1
 package com.RNFetchBlob;
1
 package com.RNFetchBlob;
2
 
2
 
3
+import com.facebook.react.bridge.Arguments;
4
+import com.facebook.react.bridge.WritableMap;
5
+import com.facebook.react.modules.core.DeviceEventManagerModule;
6
+
3
 import java.security.MessageDigest;
7
 import java.security.MessageDigest;
4
 import java.security.cert.CertificateException;
8
 import java.security.cert.CertificateException;
5
 
9
 
40
 
44
 
41
     }
45
     }
42
 
46
 
47
+    public static void emitWarningEvent(String data) {
48
+        WritableMap args = Arguments.createMap();
49
+        args.putString("event", "warn");
50
+        args.putString("detail", data);
51
+
52
+        // emit event to js context
53
+        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
54
+                .emit(RNFetchBlobConst.EVENT_MESSAGE, args);
55
+    }
56
+
43
     public static OkHttpClient.Builder getUnsafeOkHttpClient() {
57
     public static OkHttpClient.Builder getUnsafeOkHttpClient() {
44
         try {
58
         try {
45
             // Create a trust manager that does not validate certificate chains
59
             // Create a trust manager that does not validate certificate chains

+ 146
- 0
src/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java View File

1
+package com.RNFetchBlob.Utils;
2
+
3
+import android.content.Context;
4
+import android.database.Cursor;
5
+import android.net.Uri;
6
+import android.os.Build;
7
+import android.provider.DocumentsContract;
8
+import android.provider.MediaStore;
9
+import android.content.ContentUris;
10
+import android.os.Environment;
11
+
12
+public class PathResolver {
13
+    public static String getRealPathFromURI(final Context context, final Uri uri) {
14
+
15
+        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
16
+
17
+        // DocumentProvider
18
+        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
19
+            // ExternalStorageProvider
20
+            if (isExternalStorageDocument(uri)) {
21
+                final String docId = DocumentsContract.getDocumentId(uri);
22
+                final String[] split = docId.split(":");
23
+                final String type = split[0];
24
+
25
+                if ("primary".equalsIgnoreCase(type)) {
26
+                    return Environment.getExternalStorageDirectory() + "/" + split[1];
27
+                }
28
+
29
+                // TODO handle non-primary volumes
30
+            }
31
+            // DownloadsProvider
32
+            else if (isDownloadsDocument(uri)) {
33
+
34
+                final String id = DocumentsContract.getDocumentId(uri);
35
+                final Uri contentUri = ContentUris.withAppendedId(
36
+                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
37
+
38
+                return getDataColumn(context, contentUri, null, null);
39
+            }
40
+            // MediaProvider
41
+            else if (isMediaDocument(uri)) {
42
+                final String docId = DocumentsContract.getDocumentId(uri);
43
+                final String[] split = docId.split(":");
44
+                final String type = split[0];
45
+
46
+                Uri contentUri = null;
47
+                if ("image".equals(type)) {
48
+                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
49
+                } else if ("video".equals(type)) {
50
+                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
51
+                } else if ("audio".equals(type)) {
52
+                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
53
+                }
54
+
55
+                final String selection = "_id=?";
56
+                final String[] selectionArgs = new String[] {
57
+                        split[1]
58
+                };
59
+
60
+                return getDataColumn(context, contentUri, selection, selectionArgs);
61
+            }
62
+        }
63
+        // MediaStore (and general)
64
+        else if ("content".equalsIgnoreCase(uri.getScheme())) {
65
+
66
+            // Return the remote address
67
+            if (isGooglePhotosUri(uri))
68
+                return uri.getLastPathSegment();
69
+
70
+            return getDataColumn(context, uri, null, null);
71
+        }
72
+        // File
73
+        else if ("file".equalsIgnoreCase(uri.getScheme())) {
74
+            return uri.getPath();
75
+        }
76
+
77
+        return null;
78
+    }
79
+
80
+    /**
81
+     * Get the value of the data column for this Uri. This is useful for
82
+     * MediaStore Uris, and other file-based ContentProviders.
83
+     *
84
+     * @param context The context.
85
+     * @param uri The Uri to query.
86
+     * @param selection (Optional) Filter used in the query.
87
+     * @param selectionArgs (Optional) Selection arguments used in the query.
88
+     * @return The value of the _data column, which is typically a file path.
89
+     */
90
+    public static String getDataColumn(Context context, Uri uri, String selection,
91
+                                       String[] selectionArgs) {
92
+
93
+        Cursor cursor = null;
94
+        final String column = "_data";
95
+        final String[] projection = {
96
+                column
97
+        };
98
+
99
+        try {
100
+            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
101
+                    null);
102
+            if (cursor != null && cursor.moveToFirst()) {
103
+                final int index = cursor.getColumnIndexOrThrow(column);
104
+                return cursor.getString(index);
105
+            }
106
+        } finally {
107
+            if (cursor != null)
108
+                cursor.close();
109
+        }
110
+        return null;
111
+    }
112
+
113
+
114
+    /**
115
+     * @param uri The Uri to check.
116
+     * @return Whether the Uri authority is ExternalStorageProvider.
117
+     */
118
+    public static boolean isExternalStorageDocument(Uri uri) {
119
+        return "com.android.externalstorage.documents".equals(uri.getAuthority());
120
+    }
121
+
122
+    /**
123
+     * @param uri The Uri to check.
124
+     * @return Whether the Uri authority is DownloadsProvider.
125
+     */
126
+    public static boolean isDownloadsDocument(Uri uri) {
127
+        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
128
+    }
129
+
130
+    /**
131
+     * @param uri The Uri to check.
132
+     * @return Whether the Uri authority is MediaProvider.
133
+     */
134
+    public static boolean isMediaDocument(Uri uri) {
135
+        return "com.android.providers.media.documents".equals(uri.getAuthority());
136
+    }
137
+
138
+    /**
139
+     * @param uri The Uri to check.
140
+     * @return Whether the Uri authority is Google Photos.
141
+     */
142
+    public static boolean isGooglePhotosUri(Uri uri) {
143
+        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
144
+    }
145
+
146
+}

+ 23
- 0
src/fs.js View File

299
 
299
 
300
 }
300
 }
301
 
301
 
302
+function slice(src:string, dest:string, start:number, end:number):Promise {
303
+  let p = Promise.resolve()
304
+  let size = 0
305
+  function normalize(num, size) {
306
+    if(num < 0)
307
+      return Math.max(0, size + num)
308
+    if(!num && num !== 0)
309
+      return size
310
+    return num
311
+  }
312
+  if(start < 0 || end < 0 || !start || !end) {
313
+    p = p.then(() => stat(src))
314
+         .then((stat) => {
315
+           size = Math.floor(stat.size)
316
+           start = normalize(start || 0, size)
317
+           end = normalize(end, size)
318
+           return Promise.resolve()
319
+         })
320
+  }
321
+  return p.then(() => RNFetchBlob.slice(src, dest, start, end))
322
+}
323
+
302
 function isDir(path:string):Promise<bool, bool> {
324
 function isDir(path:string):Promise<bool, bool> {
303
 
325
 
304
   return new Promise((resolve, reject) => {
326
   return new Promise((resolve, reject) => {
333
   lstat,
355
   lstat,
334
   scanFile,
356
   scanFile,
335
   dirs,
357
   dirs,
358
+  slice,
336
   asset
359
   asset
337
 }
360
 }

+ 1
- 1
src/ios/RNFetchBlob/RNFetchBlob.m View File

362
     [RNFetchBlobNetwork enableUploadProgress:taskId];
362
     [RNFetchBlobNetwork enableUploadProgress:taskId];
363
 })
363
 })
364
 
364
 
365
-RCT_EXPORT_METHOD(slice:(NSString *)src dest:(NSString *)dest start:(NSNumber *)start end:(NSNumber *)end resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
365
+RCT_EXPORT_METHOD(slice:(NSString *)src dest:(NSString *)dest start:(nonnull NSNumber *)start end:(nonnull NSNumber *)end resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
366
 {
366
 {
367
     [RNFetchBlobFS slice:src dest:dest start:start end:end encode:@"" resolver:resolve rejecter:reject];
367
     [RNFetchBlobFS slice:src dest:dest start:start end:end encode:@"" resolver:resolve rejecter:reject];
368
 })
368
 })

+ 7
- 1
src/ios/RNFetchBlobFS.h View File

55
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
55
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
56
 + (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject onComplete:(void (^)(NSData * content))onComplete;
56
 + (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject onComplete:(void (^)(NSData * content))onComplete;
57
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
57
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
58
-+ (void) slice:(NSString *)path dest:(NSString *)dest start:(NSNumber *)start end:(NSNumber *)end encode:(NSString *)encode resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
58
++ (void)slice:(NSString *)path
59
+         dest:(NSString *)dest
60
+        start:(nonnull NSNumber *)start
61
+          end:(nonnull NSNumber *)end
62
+        encode:(NSString *)encode
63
+     resolver:(RCTPromiseResolveBlock)resolve
64
+     rejecter:(RCTPromiseRejectBlock)reject;
59
 //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append;
65
 //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append;
60
 
66
 
61
 // constructor
67
 // constructor

+ 28
- 14
src/ios/RNFetchBlobFS.m View File

525
 }
525
 }
526
 
526
 
527
 // Slice a file into another file, generally for support Blob implementation.
527
 // Slice a file into another file, generally for support Blob implementation.
528
-- (void)slice:(NSString *)path
528
++ (void)slice:(NSString *)path
529
          dest:(NSString *)dest
529
          dest:(NSString *)dest
530
-        start:(NSNumber *)start
531
-          end:(NSNumber *)end
532
-        encod:(NSString *)encode
530
+        start:(nonnull NSNumber *)start
531
+          end:(nonnull NSNumber *)end
532
+        encode:(NSString *)encode
533
      resolver:(RCTPromiseResolveBlock)resolve
533
      resolver:(RCTPromiseResolveBlock)resolve
534
      rejecter:(RCTPromiseRejectBlock)reject
534
      rejecter:(RCTPromiseRejectBlock)reject
535
 {
535
 {
542
     // abort for the source file not exists
542
     // abort for the source file not exists
543
     if([fm fileExistsAtPath:path] == NO)
543
     if([fm fileExistsAtPath:path] == NO)
544
     {
544
     {
545
-        reject(@"RNFetchBlob slice failed", @"the file does not exists", path);
545
+        reject(@"RNFetchBlob slice failed : the file does not exists", path, nil);
546
         return;
546
         return;
547
     }
547
     }
548
     long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
548
     long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
549
-    // abort for the file size is less than start
550
-    if(size < start)
551
-    {
552
-        reject(@"RNFetchBlob slice failed", @"start is greater than file size", @"");
553
-        return;
554
-    }
549
+    long max = MIN(size, [end longValue]);
550
+    
555
     if(![fm fileExistsAtPath:dest]) {
551
     if(![fm fileExistsAtPath:dest]) {
556
         [fm createFileAtPath:dest contents:@"" attributes:nil];
552
         [fm createFileAtPath:dest contents:@"" attributes:nil];
557
     }
553
     }
558
-    [handle seekToFileOffset:start];
554
+    [handle seekToFileOffset:[start longValue]];
559
     while(read < expected)
555
     while(read < expected)
560
     {
556
     {
561
-        NSData * chunk = [handle readDataOfLength:10240];
557
+        
558
+        NSData * chunk;
559
+        long chunkSize = 0;
560
+        if([start longValue] + read + 10240 > max)
561
+        {
562
+            NSLog(@"read chunk %lu", max - read - [start longValue]);
563
+            chunkSize = max - read - [start longValue];
564
+            chunk = [handle readDataOfLength:chunkSize];
565
+        }
566
+        else
567
+        {
568
+            NSLog(@"read chunk %lu", 10240);
569
+            chunkSize = 10240;
570
+            chunk = [handle readDataOfLength:10240];
571
+        }
572
+        if([chunk length] <= 0)
573
+            break;
574
+        long remain = expected - read;
575
+    
576
+        [os write:[chunk bytes] maxLength:chunkSize];
562
         read += [chunk length];
577
         read += [chunk length];
563
-        [os write:[chunk bytes] maxLength:10240];
564
     }
578
     }
565
     [handle closeFile];
579
     [handle closeFile];
566
     [os close];
580
     [os close];

+ 10
- 0
src/ios/RNFetchBlobReqBuilder.m View File

11
 #import "RNFetchBlobNetwork.h"
11
 #import "RNFetchBlobNetwork.h"
12
 #import "RNFetchBlobConst.h"
12
 #import "RNFetchBlobConst.h"
13
 #import "RNFetchBlobFS.h"
13
 #import "RNFetchBlobFS.h"
14
+#import "RCTLog.h"
14
 
15
 
15
 @interface RNFetchBlobReqBuilder()
16
 @interface RNFetchBlobReqBuilder()
16
 {
17
 {
154
     {
155
     {
155
         __block int i = 0;
156
         __block int i = 0;
156
         __block int count = [form count];
157
         __block int count = [form count];
158
+        // a recursive block that builds multipart body asynchornously
157
         void __block (^getFieldData)(id field) = ^(id field)
159
         void __block (^getFieldData)(id field) = ^(id field)
158
         {
160
         {
159
             NSString * name = [field valueForKey:@"name"];
161
             NSString * name = [field valueForKey:@"name"];
160
             NSString * content = [field valueForKey:@"data"];
162
             NSString * content = [field valueForKey:@"data"];
161
             NSString * contentType = [field valueForKey:@"type"];
163
             NSString * contentType = [field valueForKey:@"type"];
164
+            // skip when the form field `name` or `data` is empty
165
+            if(content == nil || name == nil)
166
+            {
167
+                i++;
168
+                getFieldData([form objectAtIndex:i]);
169
+                RCTLogWarn(@"RNFetchBlob multipart request builder has found a field without `data` or `name` property, the field will be removed implicitly.", field);
170
+                return;
171
+            }
162
             contentType = contentType == nil ? @"application/octet-stream" : contentType;
172
             contentType = contentType == nil ? @"application/octet-stream" : contentType;
163
             // field is a text field
173
             // field is a text field
164
             if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {
174
             if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {

+ 1
- 1
src/package.json View File

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

+ 55
- 41
src/polyfill/Blob.js View File

94
       this.multipartBoundary = boundary
94
       this.multipartBoundary = boundary
95
       let parts = data.getParts()
95
       let parts = data.getParts()
96
       let formArray = []
96
       let formArray = []
97
-      for(let i in parts) {
98
-        formArray.push('\r\n--'+boundary+'\r\n')
99
-        let part = parts[i]
100
-        for(let j in part.headers) {
101
-          formArray.push(j + ': ' +part.headers[j] + ';\r\n')
97
+      if(!parts) {
98
+        p = fs.writeFile(this._ref, '', 'utf8')
99
+      }
100
+      else {
101
+        for(let i in parts) {
102
+          formArray.push('\r\n--'+boundary+'\r\n')
103
+          let part = parts[i]
104
+          for(let j in part.headers) {
105
+            formArray.push(j + ': ' +part.headers[j] + ';\r\n')
106
+          }
107
+          formArray.push('\r\n')
108
+          if(part.isRNFetchBlobPolyfill)
109
+            formArray.push(part)
110
+          else
111
+            formArray.push(part.string)
102
         }
112
         }
103
-        formArray.push('\r\n')
104
-        if(part.isRNFetchBlobPolyfill)
105
-          formArray.push(part)
106
-        else
107
-          formArray.push(part.string)
113
+        log.verbose('FormData array', formArray)
114
+        formArray.push('\r\n--'+boundary+'--\r\n')
115
+        p = createMixedBlobData(this._ref, formArray)
108
       }
116
       }
109
-      log.verbose('FormData array', formArray)
110
-      formArray.push('\r\n--'+boundary+'--\r\n')
111
-      p = createMixedBlobData(this._ref, formArray)
112
     }
117
     }
113
     // if the data is a string starts with `RNFetchBlob-file://`, append the
118
     // if the data is a string starts with `RNFetchBlob-file://`, append the
114
     // Blob data from file path
119
     // Blob data from file path
181
     return this
186
     return this
182
   }
187
   }
183
 
188
 
189
+  markAsDerived() {
190
+    this._isDerived = true
191
+  }
192
+
193
+  get isDerived() {
194
+    return this._isDerived || false
195
+  }
196
+
184
   /**
197
   /**
185
    * Get file reference of the Blob object.
198
    * Get file reference of the Blob object.
186
    * @nonstandard
199
    * @nonstandard
197
    * @param  {string} contentType Optional, content type of new Blob object
210
    * @param  {string} contentType Optional, content type of new Blob object
198
    * @return {Blob}
211
    * @return {Blob}
199
    */
212
    */
200
-  slice(start:?number, end:?number, encoding:?string):Blob {
213
+  slice(start:?number, end:?number, contentType='':?string):Blob {
201
     if(this._closed)
214
     if(this._closed)
202
       throw 'Blob has been released.'
215
       throw 'Blob has been released.'
203
-    log.verbose('slice called', start, end, encoding)
204
-    console.warn('RNFB#Blob.slice() is not implemented yet, to read Blob content, use Blob.readBlob(encoding:string) instead.')
205
-    // TODO : fs.slice
206
-    // return fs.slice(this.cacheName, getBlobName(), contentType, start, end)
216
+    log.verbose('slice called', start, end, contentType)
217
+    let resPath = blobCacheDir + getBlobName()
218
+    let pass = false
219
+    log.debug('fs.slice new blob will at', resPath)
220
+    let result = new Blob(RNFetchBlob.wrap(resPath), { type : contentType })
221
+    fs.slice(this._ref, resPath, start, end).then((dest) => {
222
+      log.debug('fs.slice done', dest)
223
+      result._invokeOnCreateEvent()
224
+      pass = true
225
+    })
226
+    .catch((err) => {
227
+      pass = true
228
+    })
229
+    log.debug('slice returning new Blob')
230
+
231
+    return result
207
   }
232
   }
208
 
233
 
209
   /**
234
   /**
266
   let size = 0
291
   let size = 0
267
   for(let i in dataArray) {
292
   for(let i in dataArray) {
268
     let part = dataArray[i]
293
     let part = dataArray[i]
294
+    if(!part)
295
+      continue
269
     if(part.isRNFetchBlobPolyfill) {
296
     if(part.isRNFetchBlobPolyfill) {
270
       args.push([ref, part._ref, 'uri'])
297
       args.push([ref, part._ref, 'uri'])
271
     }
298
     }
279
       args.push([ref, part, 'ascii'])
306
       args.push([ref, part, 'ascii'])
280
   }
307
   }
281
   // start write blob data
308
   // start write blob data
282
-  // return p.then(() => {
283
-    for(let i in args) {
284
-      p = p.then(function(written){
285
-        let arg = this
286
-        if(written)
287
-          size += written
288
-        log.verbose('mixed blob write', args[i], written)
289
-        return fs.appendFile(...arg)
290
-      }.bind(args[i]))
291
-    }
292
-    return p.then(() => Promise.resolve(size))
293
-    // let promises = args.map((p) => {
294
-    //   log.verbose('mixed blob write', ...p)
295
-    //   return fs.appendFile.call(this, ...p)
296
-    // })
297
-    // return Promise.all(promises).then((sizes) => {
298
-    //   log.verbose('blob write size', sizes)
299
-    //   for(let i in sizes) {
300
-    //     size += sizes[i]
301
-    //   }
302
-    //   return Promise.resolve(size)
303
-    // })
304
-  // })
309
+  for(let i in args) {
310
+    p = p.then(function(written){
311
+      let arg = this
312
+      if(written)
313
+        size += written
314
+      log.verbose('mixed blob write', args[i], written)
315
+      return fs.appendFile(...arg)
316
+    }.bind(args[i]))
317
+  }
318
+  return p.then(() => Promise.resolve(size))
305
 }
319
 }

+ 4
- 0
src/polyfill/Fetch.js View File

99
     this.rnfbResp = resp
99
     this.rnfbResp = resp
100
   }
100
   }
101
 
101
 
102
+  rawResp() {
103
+    return Promise.resolve(this.rnfbResp)
104
+  }
105
+
102
   arrayBuffer(){
106
   arrayBuffer(){
103
     log.verbose('to arrayBuffer', this.rnfbRespInfo)
107
     log.verbose('to arrayBuffer', this.rnfbRespInfo)
104
     this.bodyUsed = true
108
     this.bodyUsed = true

+ 24
- 11
src/polyfill/XMLHttpRequest.js View File

11
 const log = new Log('XMLHttpRequest')
11
 const log = new Log('XMLHttpRequest')
12
 
12
 
13
 log.disable()
13
 log.disable()
14
-// log.level(3)
14
+// log.level(2)
15
 
15
 
16
 const UNSENT = 0
16
 const UNSENT = 0
17
 const OPENED = 1
17
 const OPENED = 1
103
   }
103
   }
104
 
104
 
105
   constructor() {
105
   constructor() {
106
-    super()
107
     log.verbose('XMLHttpRequest constructor called')
106
     log.verbose('XMLHttpRequest constructor called')
107
+    super()
108
   }
108
   }
109
 
109
 
110
 
110
 
130
    * @param  {any} body Body in RNfetchblob flavor
130
    * @param  {any} body Body in RNfetchblob flavor
131
    */
131
    */
132
   send(body) {
132
   send(body) {
133
-
133
+    this._body = body
134
     if(this._readyState !== XMLHttpRequest.OPENED)
134
     if(this._readyState !== XMLHttpRequest.OPENED)
135
       throw 'InvalidStateError : XMLHttpRequest is not opened yet.'
135
       throw 'InvalidStateError : XMLHttpRequest is not opened yet.'
136
     let promise = Promise.resolve()
136
     let promise = Promise.resolve()
141
     log.verbose(typeof body, body instanceof FormData)
141
     log.verbose(typeof body, body instanceof FormData)
142
 
142
 
143
     if(body instanceof Blob) {
143
     if(body instanceof Blob) {
144
+      log.debug('sending blob body', body._blobCreated)
144
       promise = new Promise((resolve, reject) => {
145
       promise = new Promise((resolve, reject) => {
145
           body.onCreated((blob) => {
146
           body.onCreated((blob) => {
146
-            body = RNFetchBlob.wrap(body.getRNFetchBlobRef())
147
-            resolve()
147
+              log.debug('body created send request')
148
+              body = RNFetchBlob.wrap(blob.getRNFetchBlobRef())
149
+              resolve()
148
           })
150
           })
149
         })
151
         })
150
     }
152
     }
158
     }
160
     }
159
 
161
 
160
     promise.then(() => {
162
     promise.then(() => {
163
+      log.debug('send request invoke', body)
164
+      for(let h in _headers) {
165
+        _headers[h] = _headers[h].toString()
166
+      }
161
       this._task = RNFetchBlob
167
       this._task = RNFetchBlob
162
                     .config({
168
                     .config({
163
                       auto: true,
169
                       auto: true,
224
   }
230
   }
225
 
231
 
226
   getResponseHeader(field:string):string | null {
232
   getResponseHeader(field:string):string | null {
227
-    log.verbose('XMLHttpRequest get header', field)
233
+    log.verbose('XMLHttpRequest get header', field, this._responseHeaders)
228
     if(!this._responseHeaders)
234
     if(!this._responseHeaders)
229
       return null
235
       return null
230
-    return this.responseHeaders[field] || null
236
+    return (this._responseHeaders[field] || this._responseHeaders[field.toLowerCase()]) || null
231
 
237
 
232
   }
238
   }
233
 
239
 
238
     let result = ''
244
     let result = ''
239
     let respHeaders = this.responseHeaders
245
     let respHeaders = this.responseHeaders
240
     for(let i in respHeaders) {
246
     for(let i in respHeaders) {
241
-      result += `${i}:${respHeaders[i]}\r\n`
247
+      result += `${i}: ${respHeaders[i]}${String.fromCharCode(0x0D,0x0A)}`
242
     }
248
     }
243
-    return result
249
+    return result.substr(0, result.length-2)
244
   }
250
   }
245
 
251
 
246
   _headerReceived(e) {
252
   _headerReceived(e) {
308
     }
314
     }
309
     if(resp) {
315
     if(resp) {
310
       let info = resp.respInfo || {}
316
       let info = resp.respInfo || {}
317
+      log.debug(this._url, info, info.respType)
311
       switch(info.respType) {
318
       switch(info.respType) {
312
         case 'json' :
319
         case 'json' :
320
+        try{
313
           this._responseText = resp.text()
321
           this._responseText = resp.text()
314
           this._response = resp.json()
322
           this._response = resp.json()
315
           responseDataReady()
323
           responseDataReady()
324
+        } catch(err) {
325
+        }
316
         break;
326
         break;
317
         case 'blob' :
327
         case 'blob' :
318
           resp.blob().then((b) => {
328
           resp.blob().then((b) => {
322
           })
332
           })
323
         break;
333
         break;
324
         default :
334
         default :
325
-        console.log(resp, resp.text())
326
           this._responseText = resp.text()
335
           this._responseText = resp.text()
327
           this._response = this.responseText
336
           this._response = this.responseText
328
           responseDataReady()
337
           responseDataReady()
339
   }
348
   }
340
 
349
 
341
   set onreadystatechange(fn:() => void) {
350
   set onreadystatechange(fn:() => void) {
342
-    log.verbose('XMLHttpRequest set onreadystatechange', fn.toString())
351
+    log.verbose('XMLHttpRequest set onreadystatechange', fn)
343
     this._onreadystatechange = fn
352
     this._onreadystatechange = fn
344
   }
353
   }
345
 
354
 
402
     return this._responseType
411
     return this._responseType
403
   }
412
   }
404
 
413
 
414
+  get isRNFBPolyfill() {
415
+    return true
416
+  }
417
+
405
 }
418
 }

BIN
test-server/public/500k-img-dummy.jpg View File


+ 7
- 0
test-server/server.js View File

34
     console.log('SSL test server running at port ',8124)
34
     console.log('SSL test server running at port ',8124)
35
 })
35
 })
36
 
36
 
37
+app.disable('etag')
38
+
37
 // http
39
 // http
38
 app.listen(8123, function(err){
40
 app.listen(8123, function(err){
39
   if(!err)
41
   if(!err)
136
   res.status(Math.floor(req.params.code)).send()
138
   res.status(Math.floor(req.params.code)).send()
137
 })
139
 })
138
 
140
 
141
+app.all('/content-length', (req, res) => {
142
+  console.log(req.headers)
143
+  res.send(req.headers['Content-Length'])
144
+})
145
+
139
 app.all('/xhr-header', (req, res) => {
146
 app.all('/xhr-header', (req, res) => {
140
   console.log(req.headers)
147
   console.log(req.headers)
141
   // res.header('Content-Type', 'application/json')
148
   // res.header('Content-Type', 'application/json')

+ 4
- 1
test/test-0.5.2.js View File

58
 describe('POST request with params', (report, done) => {
58
 describe('POST request with params', (report, done) => {
59
   let time = Date.now()
59
   let time = Date.now()
60
   RNFetchBlob.config({ fileCache : true })
60
   RNFetchBlob.config({ fileCache : true })
61
-    .fetch('POST', encodeURI(`${TEST_SERVER_URL}/params?time=${time}&name=RNFetchBlobParams&lang=中文`), {}, RNFetchBlob.base64.encode('123'))
61
+    .fetch('POST', encodeURI(`${TEST_SERVER_URL}/params?time=${time}&name=RNFetchBlobParams&lang=中文`),
62
+    {
63
+      'Content-Type' : 'image/png;BASE64'
64
+    }, RNFetchBlob.base64.encode('123'))
62
     .then((resp) => {
65
     .then((resp) => {
63
       let file = resp.path()
66
       let file = resp.path()
64
       return RNFetchBlob.fs.readStream(resp.path(), 'utf8')
67
       return RNFetchBlob.fs.readStream(resp.path(), 'utf8')

+ 3
- 1
test/test-0.6.2.js View File

183
   .config({fileCache : true})
183
   .config({fileCache : true})
184
   .fetch('GET', `${TEST_SERVER_URL}/public/beethoven.mp3`)
184
   .fetch('GET', `${TEST_SERVER_URL}/public/beethoven.mp3`)
185
   .then((resp) => {
185
   .then((resp) => {
186
-    return RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/mime`, null, [
186
+    return RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/mime`, {
187
+      'Content-Type' : 'multipart/form-data'
188
+    }, [
187
       { name : 'image', filename : 'image', type : 'image/jpeg', data : RNFetchBlob.base64.encode('123456') },
189
       { name : 'image', filename : 'image', type : 'image/jpeg', data : RNFetchBlob.base64.encode('123456') },
188
       { name : 'mp3', filename : 'mp3', type : 'application/mp3', data : RNFetchBlob.base64.encode('123456') },
190
       { name : 'mp3', filename : 'mp3', type : 'application/mp3', data : RNFetchBlob.base64.encode('123456') },
189
       { name : 'mp3', filename : 'mp3', data : RNFetchBlob.base64.encode('123456') },
191
       { name : 'mp3', filename : 'mp3', data : RNFetchBlob.base64.encode('123456') },

+ 2
- 1
test/test-0.7.0.js View File

36
   RNFetchBlob.config({
36
   RNFetchBlob.config({
37
     fileCache : true
37
     fileCache : true
38
   })
38
   })
39
-  .fetch('GET', `${TEST_SERVER_URL}/public/22mb-dummy`)
39
+  .fetch('GET', `${TEST_SERVER_URL}/public/1mb-dummy`)
40
   .progress((now, total) => {
40
   .progress((now, total) => {
41
     if(begin === -1)
41
     if(begin === -1)
42
       begin = Date.now()
42
       begin = Date.now()
114
       report(<Assert key="task cancelled rejection should be catachable"
114
       report(<Assert key="task cancelled rejection should be catachable"
115
         expect={true}
115
         expect={true}
116
         actual={true}/>)
116
         actual={true}/>)
117
+      done()
117
     })
118
     })
118
 
119
 
119
 })
120
 })

+ 1
- 1
test/test-0.9.0.js View File

24
 const describe = RNTest.config({
24
 const describe = RNTest.config({
25
   group : '0.9.0',
25
   group : '0.9.0',
26
   run : true,
26
   run : true,
27
-  expand : true,
27
+  expand : false,
28
   timeout : 20000,
28
   timeout : 20000,
29
 })
29
 })
30
 const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, FILENAME, DROPBOX_TOKEN, styles } = prop()
30
 const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, FILENAME, DROPBOX_TOKEN, styles } = prop()

+ 128
- 0
test/test-0.9.2.js View File

1
+import RNTest from './react-native-testkit/'
2
+import React from 'react'
3
+import RNFetchBlob from 'react-native-fetch-blob'
4
+import {
5
+  StyleSheet,
6
+  Text,
7
+  View,
8
+  ScrollView,
9
+  Platform,
10
+  Dimensions,
11
+  Image,
12
+} from 'react-native';
13
+
14
+window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
15
+window.Blob = RNFetchBlob.polyfill.Blob
16
+window.fetch = new RNFetchBlob.polyfill.Fetch({
17
+  auto : true,
18
+  binaryContentTypes : ['image/', 'video/', 'audio/']
19
+}).build()
20
+
21
+const fs = RNFetchBlob.fs
22
+const { Assert, Comparer, Info, prop } = RNTest
23
+const describe = RNTest.config({
24
+  group : '0.9.2',
25
+  run : true,
26
+  expand : true,
27
+  timeout : 20000,
28
+})
29
+const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, FILENAME, DROPBOX_TOKEN, styles } = prop()
30
+const dirs = RNFetchBlob.fs.dirs
31
+
32
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
33
+
34
+describe('content-length header test', (report, done) => {
35
+  RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/content-length`, {
36
+    'Content-Type' : 'text/plain',
37
+    'Content-Length' : '5'
38
+  }, 'hello')
39
+  .then((res) => {
40
+    report(
41
+      <Info key="response data">
42
+        <Text>{res.text()}</Text>
43
+      </Info>)
44
+    done()
45
+  })
46
+})
47
+
48
+describe('slice test', (report, done) => {
49
+  let str = "PASSSTRING"
50
+  let tmp = fs.dirs.DocumentDir + '/slice-tmp-'
51
+  let testData = [
52
+       {start:   4, contents: "STRING"},
53
+       {start:  12, contents: ""},
54
+       {start: 0, end:   4, contents: "PASS"},
55
+       {start: 0, end:  12, contents: "PASSSTRING"},
56
+       {start: 7, end:   4, contents: ""},
57
+       {start:  -6, contents: "STRING"},
58
+       {start: -12, contents: "PASSSTRING"},
59
+       {start: 0, end:  -6, contents: "PASS"},
60
+       {start: 0, end: -12, contents: ""},
61
+     ]
62
+  fs.writeFile(tmp, str, 'utf8')
63
+    .then(() => {
64
+      let promises = []
65
+      for(let t in testData) {
66
+        let p = fs.slice(tmp, tmp + t, testData[t].start, testData[t].end)
67
+        .then(function(num) {
68
+          console.log('slice finished', num)
69
+          return fs.readFile(tmp + num, 'utf8')
70
+          .then((data) => {
71
+            report(<Assert key={`assertion-${num}`} expect={testData[num].contents} actual={data}/>)
72
+            return Promise.resolve()
73
+          })
74
+        }.bind(this, t))
75
+        promises.push(p)
76
+      }
77
+      Promise.all(promises).then((res) => {
78
+        done()
79
+      })
80
+
81
+    })
82
+})
83
+
84
+describe('Upload multipart/form-data', (report, done) => {
85
+  let image = RNTest.prop('image')
86
+  RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/upload-form`, {
87
+      Authorization : "Bearer fsXcpmKPrHgAAAAAAAAAEGxFXwhejXM_E8fznZoXPhHbhbNhA-Lytbe6etp1Jznz",
88
+      'Content-Type' : 'multipart/form-data',
89
+    }, [
90
+      { name : 'test-img', filename : 'test-img.png', data: image},
91
+      { name : 'test-text', filename : 'test-text.txt', data: RNFetchBlob.base64.encode('hello.txt')},
92
+      { name : 'field1', data : 'hello !!'},
93
+      { name : 'field2', data : 'hello2 !!'}
94
+    ])
95
+  .then((resp) => {
96
+    console.log(resp.json())
97
+    resp = resp.json()
98
+
99
+    report(
100
+      <Assert key="check posted form data #1" expect="hello !!" actual={resp.fields.field1}/>,
101
+      <Assert key="check posted form data #2" expect="hello2 !!" actual={resp.fields.field2}/>,
102
+    )
103
+    done()
104
+  })
105
+})
106
+
107
+describe('app should not crash when sending formdata without data field', (report, done) => {
108
+
109
+  RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/upload-form`, {
110
+      Authorization : "Bearer fsXcpmKPrHgAAAAAAAAAEGxFXwhejXM_E8fznZoXPhHbhbNhA-Lytbe6etp1Jznz",
111
+      'Content-Type' : 'multipart/form-data',
112
+    }, [
113
+      { name : 'empty-file', filename : 'test-img.png'},
114
+      { name : 'empty-data'},
115
+      { name : 'field2', data : 'hello2 !!'}
116
+    ])
117
+  .then((resp) => {
118
+    console.log(resp.json())
119
+    resp = resp.json()
120
+
121
+    report(
122
+      <Assert key="check posted form data #1" expect={undefined} actual={resp.fields['empty-file']}/>,
123
+      <Assert key="check posted form data #2" expect={undefined} actual={resp.fields['empty-data']}/>,
124
+      <Assert key="check posted form data #3" expect="hello2 !!" actual={resp.fields.field2}/>,
125
+    )
126
+    done()
127
+  })
128
+})

+ 111
- 0
test/test-blob.js View File

154
         done()
154
         done()
155
       })
155
       })
156
 })
156
 })
157
+
158
+// since 0.9.2
159
+// test case from :
160
+// https://github.com/w3c/web-platform-tests/blob/master/FileAPI/blob/Blob-slice.html
161
+describe('#89 Blob.slice test', (report, done) => {
162
+
163
+  let blob1, blob2
164
+  let count = 0
165
+  let testData
166
+
167
+  Blob
168
+    .build(["squiggle"])
169
+    .then((b) => {
170
+      blob1 = b
171
+      return Blob.build(["steak"], {type: "content/type"})
172
+    })
173
+    .then((b) => {
174
+      blob2 = b
175
+      setTestData()
176
+      startTest()
177
+    })
178
+
179
+  function setTestData() {
180
+    testData = [
181
+      [
182
+        ["PASSSTRING"],
183
+        [{start:  -6, contents: "STRING"},
184
+         {start: -12, contents: "PASSSTRING"},
185
+         {start:   4, contents: "STRING"},
186
+         {start:  12, contents: ""},
187
+         {start: 0, end:  -6, contents: "PASS"},
188
+         {start: 0, end: -12, contents: ""},
189
+         {start: 0, end:   4, contents: "PASS"},
190
+         {start: 0, end:  12, contents: "PASSSTRING"},
191
+         {start: 7, end:   4, contents: ""}]
192
+      ],
193
+      // Test 3 strings
194
+      [
195
+        ["foo", "bar", "baz"],
196
+        [{start:  0, end:  9, contents: "foobarbaz"},
197
+         {start:  0, end:  3, contents: "foo"},
198
+         {start:  3, end:  9, contents: "barbaz"},
199
+         {start:  6, end:  9, contents: "baz"},
200
+         {start:  6, end: 12, contents: "baz"},
201
+         {start:  0, end:  9, contents: "foobarbaz"},
202
+         {start:  0, end: 11, contents: "foobarbaz"},
203
+         {start: 10, end: 15, contents: ""}]
204
+      ],
205
+      // Test string, Blob, string
206
+      [
207
+        ["foo", blob1, "baz"],
208
+        [{start:  0, end:  3, contents: "foo"},
209
+         {start:  3, end: 11, contents: "squiggle"},
210
+         {start:  2, end:  4, contents: "os"},
211
+         {start: 10, end: 12, contents: "eb"}]
212
+      ],
213
+      // Test blob, string, blob
214
+      [
215
+        [blob1, "foo", blob1],
216
+        [{start:  0, end:  8, contents: "squiggle"},
217
+         {start:  7, end:  9, contents: "ef"},
218
+         {start: 10, end: 12, contents: "os"},
219
+         {start:  1, end:  4, contents: "qui"},
220
+         {start: 12, end: 15, contents: "qui"},
221
+         {start: 40, end: 60, contents: ""}]
222
+      ],
223
+      // Test blobs all the way down
224
+      [
225
+        [blob2, blob1, blob2],
226
+        [{start: 0,  end:  5, contents: "steak"},
227
+         {start: 5,  end: 13, contents: "squiggle"},
228
+         {start: 13, end: 18, contents: "steak"},
229
+         {start:  1, end:  3, contents: "te"},
230
+         {start:  6, end: 10, contents: "quig"}]
231
+      ]
232
+    ]
233
+  }
234
+
235
+  function startTest() {
236
+    Promise.all(testData.map(assert)).then(done)
237
+  }
238
+
239
+  function assert(d):Promise {
240
+    let content = d[0]
241
+    let assertions = d[1]
242
+    console.log('create blob content = ', content)
243
+    Blob.build(content).then((b) => {
244
+      for(let i in assertions) {
245
+        let args = assertions[i]
246
+        let target = b.slice(args.start, args.end)
247
+        target.onCreated((b2) => {
248
+          let raw = null
249
+          fs.readFile(b.blobPath, 'utf8').then((data) => {
250
+            raw = data
251
+            fs.readFile(b2.blobPath, 'utf8')
252
+              .then(function(actual){
253
+                console.log('---')
254
+                console.log('raw',data)
255
+                console.log('expect', this.contents)
256
+                console.log('actual', actual)
257
+                report(<Assert key={`assertion ${++count}`} expect={this.contents} actual={actual}/>)
258
+              }.bind(args))
259
+          })
260
+
261
+        })
262
+      }
263
+    })
264
+
265
+  }
266
+
267
+})

+ 5
- 5
test/test-firebase.js View File

16
 } from 'react-native';
16
 } from 'react-native';
17
 
17
 
18
 const fs = RNFetchBlob.fs
18
 const fs = RNFetchBlob.fs
19
-const Blob = RNFetchBlob.polyfill.Blob
20
 
19
 
21
 window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
20
 window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
22
-window.Blob = Blob
21
+window.Blob = RNFetchBlob.polyfill.Blob
23
 
22
 
24
 const { Assert, Comparer, Info, prop } = RNTest
23
 const { Assert, Comparer, Info, prop } = RNTest
25
 const describe = RNTest.config({
24
 const describe = RNTest.config({
26
   group : 'firebase',
25
   group : 'firebase',
27
   run : true,
26
   run : true,
28
-  expand : false,
27
+  expand : true,
29
   timeout : 300000000,
28
   timeout : 300000000,
30
 })
29
 })
31
 const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, DROPBOX_TOKEN, styles } = prop()
30
 const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, DROPBOX_TOKEN, styles } = prop()
41
   databaseURL: "https://rnfb-test-app.firebaseio.com",
40
   databaseURL: "https://rnfb-test-app.firebaseio.com",
42
   storageBucket: "rnfb-test-app.appspot.com",
41
   storageBucket: "rnfb-test-app.appspot.com",
43
 };
42
 };
44
-firebase.initializeApp(config);
43
+
45
 
44
 
46
 describe('firebase login', (report, done) => {
45
 describe('firebase login', (report, done) => {
47
 
46
 
47
+  firebase.initializeApp(config);
48
   firebase.auth().signInWithEmailAndPassword('xeiyan@gmail.com', 'rnfbtest1024')
48
   firebase.auth().signInWithEmailAndPassword('xeiyan@gmail.com', 'rnfbtest1024')
49
     .catch((err) => {
49
     .catch((err) => {
50
       console.log('firebase sigin failed', err)
50
       console.log('firebase sigin failed', err)
103
 describe('upload using file path', (report, done) => {
103
 describe('upload using file path', (report, done) => {
104
   RNFetchBlob
104
   RNFetchBlob
105
     .config({ fileCache : true, appendExt : 'jpg' })
105
     .config({ fileCache : true, appendExt : 'jpg' })
106
-    .fetch('GET', `${TEST_SERVER_URL}/public/github2.jpg`)
106
+    .fetch('GET', `${TEST_SERVER_URL}/public/500k-img-dummy.jpg`)
107
     .then((resp) => {
107
     .then((resp) => {
108
       report(<Info key="test image">
108
       report(<Info key="test image">
109
         <Image style={styles.image} source={{uri : prefix + resp.path()}}/>
109
         <Image style={styles.image} source={{uri : prefix + resp.path()}}/>

+ 67
- 0
test/test-fs.js View File

7
   Text,
7
   Text,
8
   View,
8
   View,
9
   ScrollView,
9
   ScrollView,
10
+  Platform,
10
   Dimensions,
11
   Dimensions,
11
   Image,
12
   Image,
12
 } from 'react-native';
13
 } from 'react-native';
20
 
21
 
21
 let { TEST_SERVER_URL, FILENAME, DROPBOX_TOKEN, styles, image } = prop()
22
 let { TEST_SERVER_URL, FILENAME, DROPBOX_TOKEN, styles, image } = prop()
22
 let dirs = RNFetchBlob.fs.dirs
23
 let dirs = RNFetchBlob.fs.dirs
24
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
23
 
25
 
24
 describe('Get storage folders', (report, done) => {
26
 describe('Get storage folders', (report, done) => {
25
   report(
27
   report(
407
 
409
 
408
 })
410
 })
409
 
411
 
412
+describe('fs.slice test', (report, done) => {
413
+
414
+  let source = null
415
+  let parts = fs.dirs.DocumentDir + '/tmp-source-'
416
+  let dests = []
417
+  let combined = fs.dirs.DocumentDir + '/combined-' + Date.now() + '.jpg'
418
+  let size = 0
419
+
420
+  window.fetch = new RNFetchBlob.polyfill.Fetch({
421
+    auto : true,
422
+    binaryContentTypes : ['image/', 'video/', 'audio/']
423
+  }).build()
424
+
425
+  fetch(`${TEST_SERVER_URL}/public/github2.jpg`)
426
+  .then((res) => res.rawResp())
427
+  .then((res) => {
428
+    source = res.path()
429
+    return fs.stat(source)
430
+  })
431
+  // separate file into 4kb chunks
432
+  .then((stat) => {
433
+    size = stat.size
434
+    let promise = Promise.resolve()
435
+    let cursor = 0
436
+    while(cursor < size) {
437
+      promise = promise.then(function(start) {
438
+        console.log('slicing part ', start , start + 40960)
439
+        let offset = 0
440
+        return fs.slice(source, parts + start, start + offset, start + 40960)
441
+                .then((dest) => {
442
+                  console.log('slicing part ', start + offset, start + 40960, 'done')
443
+                  dests.push(dest)
444
+                  return Promise.resolve()
445
+                })
446
+      }.bind(this, cursor))
447
+      cursor += 40960
448
+    }
449
+    console.log('loop end')
450
+    return promise
451
+  })
452
+  // combine chunks and verify the result
453
+  .then(() => {
454
+    console.log('combinding files')
455
+    let p = Promise.resolve()
456
+    for(let d in dests) {
457
+      p = p.then(function(chunk){
458
+        return fs.appendFile(combined, chunk, 'uri').then((write) => {
459
+          console.log(write, 'bytes write')
460
+        })
461
+      }.bind(this, dests[d]))
462
+    }
463
+    return p.then(() => fs.stat(combined))
464
+  })
465
+  .then((stat) => {
466
+    report(
467
+      <Assert key="verify file size" expect={size} actual={stat.size}/>,
468
+      <Info key="image viewer">
469
+        <Image key="combined image" style={styles.image} source={{ uri : prefix + combined}}/>
470
+      </Info>)
471
+    done()
472
+  })
473
+
474
+})
475
+
476
+
410
 function getASCIIArray(str) {
477
 function getASCIIArray(str) {
411
   let r = []
478
   let r = []
412
   for(let i=0;i<str.length;i++) {
479
   for(let i=0;i<str.length;i++) {

+ 2
- 1
test/test-init.js View File

64
 require('./test-0.5.2')
64
 require('./test-0.5.2')
65
 require('./test-0.6.0')
65
 require('./test-0.6.0')
66
 require('./test-0.6.2')
66
 require('./test-0.6.2')
67
-require('./test-0.6.3')
68
 require('./test-0.7.0')
67
 require('./test-0.7.0')
69
 require('./test-0.8.0')
68
 require('./test-0.8.0')
70
 require('./test-0.9.0')
69
 require('./test-0.9.0')
70
+require('./test-0.9.2')
71
 require('./test-fetch')
71
 require('./test-fetch')
72
 require('./test-fs')
72
 require('./test-fs')
73
 require('./test-xmlhttp')
73
 require('./test-xmlhttp')
74
 require('./test-blob')
74
 require('./test-blob')
75
 require('./test-firebase')
75
 require('./test-firebase')
76
 require('./test-android')
76
 require('./test-android')
77
+// require('./test-stress')
77
 // require('./benchmark')
78
 // require('./benchmark')

test/test-0.6.3.js → test/test-stress.js View File

17
 const fs = RNFetchBlob.fs
17
 const fs = RNFetchBlob.fs
18
 const { Assert, Comparer, Info, prop } = RNTest
18
 const { Assert, Comparer, Info, prop } = RNTest
19
 const describe = RNTest.config({
19
 const describe = RNTest.config({
20
-  group : '0.6.3',
20
+  group : 'stress test',
21
   run : true,
21
   run : true,
22
   expand : true,
22
   expand : true,
23
   timeout : 300000000,
23
   timeout : 300000000,

+ 2
- 1
test/test-xmlhttp.js View File

149
   let xhr = new XMLHttpRequest()
149
   let xhr = new XMLHttpRequest()
150
   xhr.open('GET', `${TEST_SERVER_URL}/xhr-header`)
150
   xhr.open('GET', `${TEST_SERVER_URL}/xhr-header`)
151
   xhr.setRequestHeader('value', '100')
151
   xhr.setRequestHeader('value', '100')
152
+  xhr.setRequestHeader('cache-control', 'no-store')
152
   xhr.open('GET', `${TEST_SERVER_URL}/xhr-header`)
153
   xhr.open('GET', `${TEST_SERVER_URL}/xhr-header`)
153
   xhr.setRequestHeader('value', '200')
154
   xhr.setRequestHeader('value', '200')
154
   xhr.send()
155
   xhr.send()
155
   xhr.onreadystatechange = function() {
156
   xhr.onreadystatechange = function() {
156
     if(this.readyState == 4) {
157
     if(this.readyState == 4) {
157
       report(<Assert key="headers should be cleared by open()"
158
       report(<Assert key="headers should be cleared by open()"
158
-        expect={'200'}
159
+        expect={"200"}
159
         actual={this.response.value}/>)
160
         actual={this.response.value}/>)
160
       done()
161
       done()
161
     }
162
     }