Browse Source

Merge branch '0.6.2'

Ben Hsieh 8 years ago
parent
commit
aea70e6ce4

+ 15
- 4
README.md View File

@@ -256,7 +256,7 @@ Elements have property `filename` will be transformed into binary format, otherw
256 256
   })
257 257
 ```
258 258
 
259
-What if you want to upload a file in some field ? Just like [upload a file from storage](#user-content-upload-a-file-from-storage) example, wrap `data` by `wrap` API (this feature is only available for `version >= v0.5.0`)
259
+What if you want to upload a file in some field ? Just like [upload a file from storage](#user-content-upload-a-file-from-storage) example, wrap `data` by `wrap` API (this feature is only available for `version >= v0.5.0`). On version >= `0.6.2`, it is possible to set custom MIME type when appending file to form data.
260 260
 
261 261
 ```js
262 262
 
@@ -274,6 +274,14 @@ What if you want to upload a file in some field ? Just like [upload a file from
274 274
       // Or simply wrap the file path with RNFetchBlob.wrap().
275 275
       data: RNFetchBlob.wrap(PATH_TO_THE_FILE)
276 276
     },
277
+    {
278
+      name : 'ringtone',
279
+      filename : 'ring.mp3',
280
+      // use custom MIME type
281
+      type : 'application/mp3',
282
+      // upload a file from asset is also possible in version >= 0.6.2
283
+      data : RNFetchBlob.wrap(RNFetchBlob.fs.asset('default-ringtone.mp3'))
284
+    }
277 285
     // elements without property `filename` will be sent as plain text
278 286
     { name : 'name', data : 'user'},
279 287
     { name : 'info', data : JSON.stringify({
@@ -289,7 +297,7 @@ What if you want to upload a file in some field ? Just like [upload a file from
289 297
 
290 298
 #### Upload/Download progress
291 299
 
292
-In `version >= 0.4.2` it is possible to know the upload/download progress.
300
+In `version >= 0.4.2` it is possible to know the upload/download progress. On Anroid, only download progress is supported. See [wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetchprogresseventlistenerpromisernfetchblobresponse) for more information.
293 301
 
294 302
 ```js
295 303
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -391,10 +399,12 @@ RNFetchBlob.config({
391 399
 
392 400
 #### File Access
393 401
 
394
-File access APIs were made when developing `v0.5.0`, which helping us write tests, and was not planned to be a part of this module. However I realized that, it's hard to find a great solution to manage cached files, every one who use this moudle may need those APIs for there cases.
402
+File access APIs were made when developing `v0.5.0`, which helping us write tests, and was not planned to be a part of this module. However we realized that, it's hard to find a great solution to manage cached files, every one who use this moudle may need these APIs for there cases.
395 403
 
396
-File Access APIs
404
+Before get started using file APIs we recommend read [Differences between File Source](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#differences-between-file-source) first.
397 405
 
406
+File Access APIs
407
+- [asset (0.6.2)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#assetfilenamestringstring)
398 408
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
399 409
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
400 410
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
@@ -539,6 +549,7 @@ RNFetchBlob.config({
539 549
 
540 550
 | Version | |
541 551
 |---|---|
552
+| 0.6.2 | Add support of asset file and camera roll files, Support custom MIME type when sending multipart request, thanks @smartt |
542 553
 | 0.6.1 | Fix #37 progress report API issue on IOS |
543 554
 | 0.6.0 | Add readFile and writeFile API for easier file access, also added Android download manager support. |
544 555
 | 0.5.8 | Fix #33 PUT request will always be sent as POST on Android |

+ 1
- 1
package.json View File

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

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

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

+ 164
- 43
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

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

+ 45
- 5
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

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

+ 12
- 1
src/fs.js View File

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

+ 1
- 1
src/index.js View File

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

+ 6
- 0
src/ios/RNFetchBlob.xcodeproj/project.pbxproj View File

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

+ 37
- 121
src/ios/RNFetchBlob/RNFetchBlob.m View File

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

+ 3
- 0
src/ios/RNFetchBlobConst.h View File

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

+ 4
- 0
src/ios/RNFetchBlobConst.m View File

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

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

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

+ 185
- 41
src/ios/RNFetchBlobFS.m View File

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

+ 2
- 1
src/ios/RNFetchBlobNetwork.h View File

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

+ 9
- 3
src/ios/RNFetchBlobNetwork.m View File

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

+ 34
- 0
src/ios/RNFetchBlobReqBuilder.h View File

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

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

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

+ 1
- 1
src/package.json View File

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

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

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

+ 1
- 0
test.sh View File

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

+ 3
- 0
test/assets/test-asset1.json View File

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

BIN
test/assets/test-asset2.png View File


+ 1
- 0
test/index.test.js View File

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

+ 174
- 0
test/react-native-testkit/animate-text.js View File

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

+ 59
- 20
test/react-native-testkit/components/reporter.js View File

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

+ 14
- 1
test/react-native-testkit/lib/test-context.js View File

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

+ 39
- 5
test/test-0.5.1.js View File

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

+ 203
- 0
test/test-0.6.2.js View File

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

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

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

+ 5
- 3
test/test-init.js View File

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