Selaa lähdekoodia

Merge branch '0.9.2' into 0.10.0

Ben Hsieh 8 vuotta sitten
vanhempi
commit
9b7a81bd0e

+ 2
- 2
.github/PULL_REQUEST_TEMPLATE Näytä tiedosto

@@ -1,5 +1,5 @@
1 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 5
 3. Correct README.md can directly to master

+ 5
- 3
README.md Näytä tiedosto

@@ -1,7 +1,8 @@
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 7
 ## Features
7 8
 - Transfer data directly from/to storage without BASE64 bridging
@@ -37,7 +38,7 @@ A project committed to make file acess and data transfer easier, effiecient for
37 38
 
38 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 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,6 +723,7 @@ If you're going to concatenate files, you don't have to read the data to JS cont
722 723
 
723 724
 | Version | |
724 725
 |---|---|
726
+| 0.9.2 | Add Blob.slice() method implementation #89 |
725 727
 | 0.9.1 | Fix Android Blob constructor asynchronous issue caused by 0.9.0 fs change |
726 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 729
 | 0.8.1 | Remove Web API log and fix ios progress report function. |

+ 1
- 1
package.json Näytä tiedosto

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

+ 2
- 2
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java Näytä tiedosto

@@ -223,8 +223,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
223 223
     }
224 224
 
225 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 230
     @ReactMethod

+ 131
- 81
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java Näytä tiedosto

@@ -13,8 +13,12 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
13 13
 import java.io.ByteArrayInputStream;
14 14
 import java.io.File;
15 15
 import java.io.FileInputStream;
16
+import java.io.FileNotFoundException;
17
+import java.io.FileOutputStream;
16 18
 import java.io.IOException;
17 19
 import java.io.InputStream;
20
+import java.nio.ByteBuffer;
21
+import java.nio.MappedByteBuffer;
18 22
 import java.util.ArrayList;
19 23
 import java.util.HashMap;
20 24
 
@@ -33,19 +37,27 @@ import okio.Sink;
33 37
 public class RNFetchBlobBody extends RequestBody{
34 38
 
35 39
     InputStream requestStream;
36
-    long contentLength;
40
+    long contentLength = 0;
37 41
     long bytesWritten = 0;
38 42
     ReadableArray form;
39 43
     String mTaskId;
40 44
     String rawBody;
41 45
     RNFetchBlobReq.RequestType requestType;
42 46
     MediaType mime;
47
+    File bodyCache;
48
+
43 49
 
44 50
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
45 51
         this.mTaskId = taskId;
46 52
         this.form = form;
47 53
         requestType = type;
48 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 63
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
@@ -53,8 +65,18 @@ public class RNFetchBlobBody extends RequestBody{
53 65
         requestType = type;
54 66
         this.rawBody = rawBody;
55 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 80
     @Override
59 81
     public MediaType contentType() {
60 82
         return mime;
@@ -67,10 +89,11 @@ public class RNFetchBlobBody extends RequestBody{
67 89
         BufferedSink buffer = Okio.buffer(source);
68 90
         switch (requestType) {
69 91
             case Form:
70
-                writeFormData(sink);
92
+                pipeStreamToSink(new FileInputStream(bodyCache), sink);
71 93
                 break;
72 94
             case SingleFile:
73
-                writeOctetData(sink);
95
+                if(requestStream != null)
96
+                    pipeStreamToSink(requestStream, sink);
74 97
                 break;
75 98
             case AsIs:
76 99
 				writeRawData(sink);
@@ -79,10 +102,71 @@ public class RNFetchBlobBody extends RequestBody{
79 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 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 167
         ArrayList<FormField> fields = countFormDataLength();
85 168
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
169
+
86 170
         for(int i = 0;i < fields.size(); i++) {
87 171
             FormField field = fields.get(i);
88 172
             String data = field.data;
@@ -95,7 +179,7 @@ public class RNFetchBlobBody extends RequestBody{
95 179
             if (field.filename != null) {
96 180
                 header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
97 181
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
98
-                sink.write(header.getBytes());
182
+                os.write(header.getBytes());
99 183
                 // file field header end
100 184
                 // upload from storage
101 185
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
@@ -106,9 +190,9 @@ public class RNFetchBlobBody extends RequestBody{
106 190
                         try {
107 191
                             String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
108 192
                             InputStream in = ctx.getAssets().open(assetName);
109
-                            pipeStreamToSink(in, sink);
193
+                            pipeStreamToFileStream(in, os);
110 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 198
                     // data from normal files
@@ -116,17 +200,17 @@ public class RNFetchBlobBody extends RequestBody{
116 200
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
117 201
                         if(file.exists()) {
118 202
                             FileInputStream fs = new FileInputStream(file);
119
-                            pipeStreamToSink(fs, sink);
203
+                            pipeStreamToFileStream(fs, os);
120 204
                         }
121 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 210
                 // base64 embedded file content
127 211
                 else {
128 212
                     byte[] b = Base64.decode(data, 0);
129
-                    sink.write(b);
213
+                    os.write(b);
130 214
                     bytesWritten += b.length;
131 215
                     emitUploadProgress();
132 216
                 }
@@ -136,70 +220,30 @@ public class RNFetchBlobBody extends RequestBody{
136 220
             else {
137 221
                 header += "Content-Disposition: form-data; name=" + name + "\r\n";
138 222
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
139
-                sink.write(header.getBytes());
223
+                os.write(header.getBytes());
140 224
                 byte[] fieldData = field.data.getBytes();
141 225
                 bytesWritten += fieldData.length;
142
-                sink.write(fieldData);
226
+                os.write(fieldData);
143 227
             }
144 228
             // form end
145
-            sink.write("\r\n".getBytes());
229
+            os.write("\r\n".getBytes());
146 230
         }
147 231
         // close the form
148 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 240
      * Write data to request body as-is
199 241
      * @param sink
200 242
      */
201 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,27 +254,29 @@ public class RNFetchBlobBody extends RequestBody{
210 254
      */
211 255
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
212 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 259
             if(read > 0) {
221 260
                 sink.write(chunk, 0, read);
222
-                bytesWritten += read;
223
-                emitUploadProgress();
224 261
             }
225
-
226 262
         }
227 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,10 +288,13 @@ public class RNFetchBlobBody extends RequestBody{
242 288
         ArrayList<FormField> list = new ArrayList<>();
243 289
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
244 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 298
                 // upload from storage
250 299
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
251 300
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
@@ -257,7 +306,7 @@ public class RNFetchBlobBody extends RequestBody{
257 306
                             long length = ctx.getAssets().open(assetName).available();
258 307
                             total += length;
259 308
                         } catch (IOException e) {
260
-
309
+                            RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
261 310
                         }
262 311
                     }
263 312
                     // general files
@@ -274,7 +323,7 @@ public class RNFetchBlobBody extends RequestBody{
274 323
             }
275 324
             // data field
276 325
             else {
277
-                total += field.getString("data").length();
326
+                total += field.data != null ? field.data.getBytes().length : 0;
278 327
             }
279 328
         }
280 329
         contentLength = total;
@@ -301,8 +350,9 @@ public class RNFetchBlobBody extends RequestBody{
301 350
             else {
302 351
                 mime = filename == null ? "text/plain" : "application/octet-stream";
303 352
             }
304
-            if(rawData.hasKey("data"))
353
+            if(rawData.hasKey("data")) {
305 354
                 data = rawData.getString("data");
355
+            }
306 356
         }
307 357
     }
308 358
 

+ 1
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java Näytä tiedosto

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

+ 26
- 22
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java Näytä tiedosto

@@ -13,6 +13,7 @@ import android.os.Looper;
13 13
 import android.provider.MediaStore;
14 14
 import android.util.Base64;
15 15
 
16
+import com.RNFetchBlob.Utils.PathResolver;
16 17
 import com.facebook.react.bridge.Arguments;
17 18
 import com.facebook.react.bridge.Callback;
18 19
 import com.facebook.react.bridge.Promise;
@@ -448,7 +449,7 @@ public class RNFetchBlobFS {
448 449
             in = inputStreamFromPath(path);
449 450
             out = new FileOutputStream(dest);
450 451
 
451
-            byte[] buf = new byte[1024];
452
+            byte[] buf = new byte[10240];
452 453
             int len;
453 454
             while ((len = in.read(buf)) > 0) {
454 455
                 out.write(buf, 0, len);
@@ -535,11 +536,16 @@ public class RNFetchBlobFS {
535 536
      * @param start Start byte offset in source file
536 537
      * @param end   End byte offset
537 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 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 549
             long now = 0;
544 550
             FileInputStream in = new FileInputStream(new File(src));
545 551
             FileOutputStream out = new FileOutputStream(new File(dest));
@@ -547,17 +553,20 @@ public class RNFetchBlobFS {
547 553
             byte [] buffer = new byte[10240];
548 554
             while(now < expected) {
549 555
                 long read = in.read(buffer, 0, 10240);
556
+                long remain = expected - now;
550 557
                 if(read <= 0) {
551 558
                     break;
552 559
                 }
560
+                out.write(buffer, 0, (int) Math.min(remain, read));
553 561
                 now += read;
554
-                out.write(buffer, 0, (int) read);
555 562
             }
556 563
             in.close();
564
+            out.flush();
557 565
             out.close();
558
-            callback.invoke(null, dest);
566
+            promise.resolve(dest);
559 567
         } catch (Exception e) {
560 568
             e.printStackTrace();
569
+            promise.reject(e.getLocalizedMessage());
561 570
         }
562 571
     }
563 572
 
@@ -844,28 +853,23 @@ public class RNFetchBlobFS {
844 853
     }
845 854
 
846 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 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 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 Näytä tiedosto

@@ -25,6 +25,7 @@ import java.io.FileOutputStream;
25 25
 import java.io.IOException;
26 26
 import java.io.InputStream;
27 27
 import java.net.MalformedURLException;
28
+import java.net.SocketException;
28 29
 import java.net.SocketTimeoutException;
29 30
 import java.net.URL;
30 31
 import java.nio.ByteBuffer;
@@ -81,6 +82,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
81 82
     Callback callback;
82 83
     long contentLength;
83 84
     long downloadManagerId;
85
+    RNFetchBlobBody requestBody;
84 86
     RequestType requestType;
85 87
     ResponseType responseType;
86 88
     WritableMap respInfo;
@@ -154,7 +156,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
154 156
 
155 157
         // find cached result if `key` property exists
156 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 161
        	if (this.options.key != null) {
160 162
            cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
@@ -172,7 +174,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
172 174
 
173 175
         if(this.options.path != null)
174 176
             this.destPath = this.options.path;
175
-        else if(this.options.fileCache == true)
177
+        else if(this.options.fileCache)
176 178
             this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
177 179
 
178 180
         OkHttpClient.Builder clientBuilder;
@@ -207,7 +209,10 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
207 209
             if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
208 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 216
                     builder.header("Content-Type", "application/octet-stream");
212 217
                     requestType = RequestType.SingleFile;
213 218
                 }
@@ -235,28 +240,32 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
235 240
             // set request body
236 241
             switch (requestType) {
237 242
                 case SingleFile:
238
-                    builder.method(method, new RNFetchBlobBody(
243
+                    requestBody = new RNFetchBlobBody(
239 244
                             taskId,
240 245
                             requestType,
241 246
                             rawRequestBody,
242 247
                             MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
243
-                    ));
248
+                    );
249
+                    builder.method(method, requestBody);
244 250
                     break;
245 251
                 case AsIs:
246
-                    builder.method(method, new RNFetchBlobBody(
252
+                    requestBody = new RNFetchBlobBody(
247 253
                             taskId,
248 254
                             requestType,
249 255
                             rawRequestBody,
250 256
                             MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))
251
-                    ));
257
+                    );
258
+                    builder.method(method, requestBody);
252 259
                     break;
253 260
                 case Form:
254
-                    builder.method(method, new RNFetchBlobBody(
261
+                    String boundary = "RNFetchBlob-" + taskId;
262
+                    requestBody = new RNFetchBlobBody(
255 263
                             taskId,
256 264
                             requestType,
257 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 269
                     break;
261 270
 
262 271
                 case WithoutBody:
@@ -271,7 +280,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
271 280
 
272 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 284
             clientBuilder.addInterceptor(new Interceptor() {
276 285
                 @Override
277 286
                 public Response intercept(Chain chain) throws IOException {
@@ -300,8 +309,14 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
300 309
                                 break;
301 310
                         }
302 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 317
                         timeout = true;
318
+                    } catch(Exception ex) {
319
+                        RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + ex.getLocalizedMessage());
305 320
                     }
306 321
                     return chain.proceed(chain.request());
307 322
                 }
@@ -336,7 +351,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
336 351
                     }
337 352
                     else
338 353
                         callback.invoke(e.getLocalizedMessage(), null, null);
339
-                    removeTaskInfo();
354
+                    releaseTaskResource();
340 355
                 }
341 356
 
342 357
                 @Override
@@ -366,7 +381,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
366 381
 
367 382
         } catch (Exception error) {
368 383
             error.printStackTrace();
369
-            taskTable.remove(taskId);
384
+            releaseTaskResource();
370 385
             callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause());
371 386
         }
372 387
     }
@@ -374,13 +389,15 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
374 389
     /**
375 390
      * Remove cached information of the HTTP task
376 391
      */
377
-    private void removeTaskInfo() {
392
+    private void releaseTaskResource() {
378 393
         if(taskTable.containsKey(taskId))
379 394
             taskTable.remove(taskId);
380 395
         if(uploadProgressReport.containsKey(taskId))
381 396
             uploadProgressReport.remove(taskId);
382 397
         if(progressReport.containsKey(taskId))
383 398
             progressReport.remove(taskId);
399
+        if(requestBody != null)
400
+            requestBody.clearRequestBody();
384 401
     }
385 402
 
386 403
     /**
@@ -439,7 +456,9 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
439 456
                     // It uses customized response body which is able to report download progress
440 457
                     // and write response data to destination path.
441 458
                     resp.body().bytes();
442
-                } catch (Exception ignored) {}
459
+                } catch (Exception ignored) {
460
+                    ignored.printStackTrace();
461
+                }
443 462
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
444 463
                 break;
445 464
             default:
@@ -450,8 +469,9 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
450 469
                 }
451 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,8 +603,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
583 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 Näytä tiedosto

@@ -1,5 +1,9 @@
1 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 7
 import java.security.MessageDigest;
4 8
 import java.security.cert.CertificateException;
5 9
 
@@ -40,6 +44,16 @@ public class RNFetchBlobUtils {
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 57
     public static OkHttpClient.Builder getUnsafeOkHttpClient() {
44 58
         try {
45 59
             // Create a trust manager that does not validate certificate chains

+ 146
- 0
src/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java Näytä tiedosto

@@ -0,0 +1,146 @@
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 Näytä tiedosto

@@ -299,6 +299,28 @@ function exists(path:string):Promise<bool, bool> {
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 324
 function isDir(path:string):Promise<bool, bool> {
303 325
 
304 326
   return new Promise((resolve, reject) => {
@@ -333,5 +355,6 @@ export default {
333 355
   lstat,
334 356
   scanFile,
335 357
   dirs,
358
+  slice,
336 359
   asset
337 360
 }

+ 1
- 1
src/ios/RNFetchBlob/RNFetchBlob.m Näytä tiedosto

@@ -362,7 +362,7 @@ RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId {
362 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 367
     [RNFetchBlobFS slice:src dest:dest start:start end:end encode:@"" resolver:resolve rejecter:reject];
368 368
 })

+ 7
- 1
src/ios/RNFetchBlobFS.h Näytä tiedosto

@@ -55,7 +55,13 @@
55 55
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
56 56
 + (void) readFile:(NSString *)path encoding:(NSString *)encoding resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject onComplete:(void (^)(NSData * content))onComplete;
57 57
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
58
-+ (void) slice:(NSString *)path 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 65
 //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append;
60 66
 
61 67
 // constructor

+ 28
- 14
src/ios/RNFetchBlobFS.m Näytä tiedosto

@@ -525,11 +525,11 @@ NSMutableDictionary *fileStreams = nil;
525 525
 }
526 526
 
527 527
 // Slice a file into another file, generally for support Blob implementation.
528
-- (void)slice:(NSString *)path
528
++ (void)slice:(NSString *)path
529 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 533
      resolver:(RCTPromiseResolveBlock)resolve
534 534
      rejecter:(RCTPromiseRejectBlock)reject
535 535
 {
@@ -542,25 +542,39 @@ NSMutableDictionary *fileStreams = nil;
542 542
     // abort for the source file not exists
543 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 546
         return;
547 547
     }
548 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 551
     if(![fm fileExistsAtPath:dest]) {
556 552
         [fm createFileAtPath:dest contents:@"" attributes:nil];
557 553
     }
558
-    [handle seekToFileOffset:start];
554
+    [handle seekToFileOffset:[start longValue]];
559 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 577
         read += [chunk length];
563
-        [os write:[chunk bytes] maxLength:10240];
564 578
     }
565 579
     [handle closeFile];
566 580
     [os close];

+ 10
- 0
src/ios/RNFetchBlobReqBuilder.m Näytä tiedosto

@@ -11,6 +11,7 @@
11 11
 #import "RNFetchBlobNetwork.h"
12 12
 #import "RNFetchBlobConst.h"
13 13
 #import "RNFetchBlobFS.h"
14
+#import "RCTLog.h"
14 15
 
15 16
 @interface RNFetchBlobReqBuilder()
16 17
 {
@@ -154,11 +155,20 @@
154 155
     {
155 156
         __block int i = 0;
156 157
         __block int count = [form count];
158
+        // a recursive block that builds multipart body asynchornously
157 159
         void __block (^getFieldData)(id field) = ^(id field)
158 160
         {
159 161
             NSString * name = [field valueForKey:@"name"];
160 162
             NSString * content = [field valueForKey:@"data"];
161 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 172
             contentType = contentType == nil ? @"application/octet-stream" : contentType;
163 173
             // field is a text field
164 174
             if([field valueForKey:@"filename"] == nil || content == [NSNull null]) {

+ 1
- 1
src/package.json Näytä tiedosto

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

+ 55
- 41
src/polyfill/Blob.js Näytä tiedosto

@@ -94,21 +94,26 @@ export default class Blob extends EventTarget {
94 94
       this.multipartBoundary = boundary
95 95
       let parts = data.getParts()
96 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 118
     // if the data is a string starts with `RNFetchBlob-file://`, append the
114 119
     // Blob data from file path
@@ -181,6 +186,14 @@ export default class Blob extends EventTarget {
181 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 198
    * Get file reference of the Blob object.
186 199
    * @nonstandard
@@ -197,13 +210,25 @@ export default class Blob extends EventTarget {
197 210
    * @param  {string} contentType Optional, content type of new Blob object
198 211
    * @return {Blob}
199 212
    */
200
-  slice(start:?number, end:?number, encoding:?string):Blob {
213
+  slice(start:?number, end:?number, contentType='':?string):Blob {
201 214
     if(this._closed)
202 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,6 +291,8 @@ function createMixedBlobData(ref, dataArray) {
266 291
   let size = 0
267 292
   for(let i in dataArray) {
268 293
     let part = dataArray[i]
294
+    if(!part)
295
+      continue
269 296
     if(part.isRNFetchBlobPolyfill) {
270 297
       args.push([ref, part._ref, 'uri'])
271 298
     }
@@ -279,27 +306,14 @@ function createMixedBlobData(ref, dataArray) {
279 306
       args.push([ref, part, 'ascii'])
280 307
   }
281 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 Näytä tiedosto

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

+ 24
- 11
src/polyfill/XMLHttpRequest.js Näytä tiedosto

@@ -11,7 +11,7 @@ import ProgressEvent from './ProgressEvent.js'
11 11
 const log = new Log('XMLHttpRequest')
12 12
 
13 13
 log.disable()
14
-// log.level(3)
14
+// log.level(2)
15 15
 
16 16
 const UNSENT = 0
17 17
 const OPENED = 1
@@ -103,8 +103,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
103 103
   }
104 104
 
105 105
   constructor() {
106
-    super()
107 106
     log.verbose('XMLHttpRequest constructor called')
107
+    super()
108 108
   }
109 109
 
110 110
 
@@ -130,7 +130,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
130 130
    * @param  {any} body Body in RNfetchblob flavor
131 131
    */
132 132
   send(body) {
133
-
133
+    this._body = body
134 134
     if(this._readyState !== XMLHttpRequest.OPENED)
135 135
       throw 'InvalidStateError : XMLHttpRequest is not opened yet.'
136 136
     let promise = Promise.resolve()
@@ -141,10 +141,12 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
141 141
     log.verbose(typeof body, body instanceof FormData)
142 142
 
143 143
     if(body instanceof Blob) {
144
+      log.debug('sending blob body', body._blobCreated)
144 145
       promise = new Promise((resolve, reject) => {
145 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,6 +160,10 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
158 160
     }
159 161
 
160 162
     promise.then(() => {
163
+      log.debug('send request invoke', body)
164
+      for(let h in _headers) {
165
+        _headers[h] = _headers[h].toString()
166
+      }
161 167
       this._task = RNFetchBlob
162 168
                     .config({
163 169
                       auto: true,
@@ -224,10 +230,10 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
224 230
   }
225 231
 
226 232
   getResponseHeader(field:string):string | null {
227
-    log.verbose('XMLHttpRequest get header', field)
233
+    log.verbose('XMLHttpRequest get header', field, this._responseHeaders)
228 234
     if(!this._responseHeaders)
229 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,9 +244,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
238 244
     let result = ''
239 245
     let respHeaders = this.responseHeaders
240 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 252
   _headerReceived(e) {
@@ -308,11 +314,15 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
308 314
     }
309 315
     if(resp) {
310 316
       let info = resp.respInfo || {}
317
+      log.debug(this._url, info, info.respType)
311 318
       switch(info.respType) {
312 319
         case 'json' :
320
+        try{
313 321
           this._responseText = resp.text()
314 322
           this._response = resp.json()
315 323
           responseDataReady()
324
+        } catch(err) {
325
+        }
316 326
         break;
317 327
         case 'blob' :
318 328
           resp.blob().then((b) => {
@@ -322,7 +332,6 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
322 332
           })
323 333
         break;
324 334
         default :
325
-        console.log(resp, resp.text())
326 335
           this._responseText = resp.text()
327 336
           this._response = this.responseText
328 337
           responseDataReady()
@@ -339,7 +348,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
339 348
   }
340 349
 
341 350
   set onreadystatechange(fn:() => void) {
342
-    log.verbose('XMLHttpRequest set onreadystatechange', fn.toString())
351
+    log.verbose('XMLHttpRequest set onreadystatechange', fn)
343 352
     this._onreadystatechange = fn
344 353
   }
345 354
 
@@ -402,4 +411,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
402 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 Näytä tiedosto


+ 7
- 0
test-server/server.js Näytä tiedosto

@@ -34,6 +34,8 @@ var server = https.createServer({
34 34
     console.log('SSL test server running at port ',8124)
35 35
 })
36 36
 
37
+app.disable('etag')
38
+
37 39
 // http
38 40
 app.listen(8123, function(err){
39 41
   if(!err)
@@ -136,6 +138,11 @@ app.all('/xhr-code/:code', (req, res) => {
136 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 146
 app.all('/xhr-header', (req, res) => {
140 147
   console.log(req.headers)
141 148
   // res.header('Content-Type', 'application/json')

+ 4
- 1
test/test-0.5.2.js Näytä tiedosto

@@ -58,7 +58,10 @@ describe('GET request with params', (report, done) => {
58 58
 describe('POST request with params', (report, done) => {
59 59
   let time = Date.now()
60 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 65
     .then((resp) => {
63 66
       let file = resp.path()
64 67
       return RNFetchBlob.fs.readStream(resp.path(), 'utf8')

+ 3
- 1
test/test-0.6.2.js Näytä tiedosto

@@ -183,7 +183,9 @@ describe('Check custom MIME type correctness',(report, done) => {
183 183
   .config({fileCache : true})
184 184
   .fetch('GET', `${TEST_SERVER_URL}/public/beethoven.mp3`)
185 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 189
       { name : 'image', filename : 'image', type : 'image/jpeg', data : RNFetchBlob.base64.encode('123456') },
188 190
       { name : 'mp3', filename : 'mp3', type : 'application/mp3', data : RNFetchBlob.base64.encode('123456') },
189 191
       { name : 'mp3', filename : 'mp3', data : RNFetchBlob.base64.encode('123456') },

+ 2
- 1
test/test-0.7.0.js Näytä tiedosto

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

+ 1
- 1
test/test-0.9.0.js Näytä tiedosto

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

+ 128
- 0
test/test-0.9.2.js Näytä tiedosto

@@ -0,0 +1,128 @@
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 Näytä tiedosto

@@ -154,3 +154,114 @@ describe('create blob using FormData', (report, done) => {
154 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 Näytä tiedosto

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

+ 67
- 0
test/test-fs.js Näytä tiedosto

@@ -7,6 +7,7 @@ import {
7 7
   Text,
8 8
   View,
9 9
   ScrollView,
10
+  Platform,
10 11
   Dimensions,
11 12
   Image,
12 13
 } from 'react-native';
@@ -20,6 +21,7 @@ const describe = RNTest.config({
20 21
 
21 22
 let { TEST_SERVER_URL, FILENAME, DROPBOX_TOKEN, styles, image } = prop()
22 23
 let dirs = RNFetchBlob.fs.dirs
24
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
23 25
 
24 26
 describe('Get storage folders', (report, done) => {
25 27
   report(
@@ -407,6 +409,71 @@ describe('stat and lstat test', (report, done) => {
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 477
 function getASCIIArray(str) {
411 478
   let r = []
412 479
   for(let i=0;i<str.length;i++) {

+ 2
- 1
test/test-init.js Näytä tiedosto

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

test/test-0.6.3.js → test/test-stress.js Näytä tiedosto

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

+ 2
- 1
test/test-xmlhttp.js Näytä tiedosto

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