Browse Source

Change native network module logic and improve Android fs performance

Ben Hsieh 7 years ago
parent
commit
7c2782ade5

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

@@ -9,10 +9,15 @@ import com.facebook.react.bridge.ReadableArray;
9 9
 import com.facebook.react.bridge.ReadableMap;
10 10
 
11 11
 import java.util.Map;
12
+import java.util.concurrent.LinkedBlockingQueue;
13
+import java.util.concurrent.ThreadPoolExecutor;
14
+import java.util.concurrent.TimeUnit;
12 15
 
13 16
 public class RNFetchBlob extends ReactContextBaseJavaModule {
14 17
 
15 18
     static ReactApplicationContext RCTContext;
19
+    static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
20
+    static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
16 21
 
17 22
     public RNFetchBlob(ReactApplicationContext reactContext) {
18 23
 
@@ -31,17 +36,29 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
31 36
     }
32 37
 
33 38
     @ReactMethod
34
-    public void createFile(String path, String content, String encode, Callback callback) {
35
-        RNFetchBlobFS.createFile(path, content, encode, callback);
39
+    public void createFile(final String path, final String content, final String encode, final Callback callback) {
40
+        threadPool.execute(new Runnable() {
41
+            @Override
42
+            public void run() {
43
+                RNFetchBlobFS.createFile(path, content, encode, callback);
44
+            }
45
+        });
46
+
36 47
     }
37 48
 
38 49
     @ReactMethod
39
-    public void createFileASCII(String path, ReadableArray dataArray, Callback callback) {
40
-        RNFetchBlobFS.createFileASCII(path, dataArray, callback);
50
+    public void createFileASCII(final String path, final ReadableArray dataArray, final Callback callback) {
51
+        threadPool.execute(new Runnable() {
52
+            @Override
53
+            public void run() {
54
+                RNFetchBlobFS.createFileASCII(path, dataArray, callback);
55
+            }
56
+        });
57
+
41 58
     }
42 59
 
43 60
     @ReactMethod
44
-    public void writeArrayChunk(String streamId, ReadableArray dataArray, Callback callback) {
61
+    public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
45 62
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
46 63
     }
47 64
 
@@ -61,8 +78,14 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
61 78
     }
62 79
 
63 80
     @ReactMethod
64
-    public void cp(String path, String dest, Callback callback) {
65
-        RNFetchBlobFS.cp(path, dest, callback);
81
+    public void cp(final String path, final String dest, final Callback callback) {
82
+        threadPool.execute(new Runnable() {
83
+            @Override
84
+            public void run() {
85
+                RNFetchBlobFS.cp(path, dest, callback);
86
+            }
87
+        });
88
+
66 89
     }
67 90
 
68 91
     @ReactMethod
@@ -96,18 +119,34 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
96 119
     }
97 120
 
98 121
     @ReactMethod
99
-    public void readFile(String path, String encoding, Promise promise) {
100
-        RNFetchBlobFS.readFile(path, encoding, promise);
122
+    public void readFile(final String path, final String encoding, final Promise promise) {
123
+        threadPool.execute(new Runnable() {
124
+            @Override
125
+            public void run() {
126
+                RNFetchBlobFS.readFile(path, encoding, promise);
127
+            }
128
+        });
101 129
     }
102 130
 
103 131
     @ReactMethod
104
-    public void writeFileArray(String path, ReadableArray data, boolean append, Promise promise) {
105
-        RNFetchBlobFS.writeFile(path, data, append, promise);
132
+    public void writeFileArray(final String path, final ReadableArray data, final boolean append, final Promise promise) {
133
+        threadPool.execute(new Runnable() {
134
+            @Override
135
+            public void run() {
136
+                RNFetchBlobFS.writeFile(path, data, append, promise);
137
+            }
138
+        });
106 139
     }
107 140
 
108 141
     @ReactMethod
109
-    public void writeFile(String path, String encoding, String data, boolean append, Promise promise) {
110
-        RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
142
+    public void writeFile(final String path, final String encoding, final String data, final boolean append, final Promise promise) {
143
+        threadPool.execute(new Runnable() {
144
+            @Override
145
+            public void run() {
146
+                RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
147
+            }
148
+        });
149
+
111 150
     }
112 151
 
113 152
     @ReactMethod
@@ -121,21 +160,28 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
121 160
     }
122 161
 
123 162
     @ReactMethod
124
-    public void scanFile(ReadableArray pairs, Callback callback) {
125
-        int size = pairs.size();
126
-        String [] p = new String[size];
127
-        String [] m = new String[size];
128
-        for(int i=0;i<size;i++) {
129
-            ReadableMap pair = pairs.getMap(i);
130
-            if(pair.hasKey("path")) {
131
-                p[i] = pair.getString("path");
132
-                if(pair.hasKey("mime"))
133
-                    m[i] = pair.getString("mime");
134
-                else
135
-                    m[i] = null;
163
+    public void scanFile(final ReadableArray pairs, final Callback callback) {
164
+        final ReactApplicationContext ctx = this.getReactApplicationContext();
165
+        threadPool.execute(new Runnable() {
166
+            @Override
167
+            public void run() {
168
+                int size = pairs.size();
169
+                String [] p = new String[size];
170
+                String [] m = new String[size];
171
+                for(int i=0;i<size;i++) {
172
+                    ReadableMap pair = pairs.getMap(i);
173
+                    if(pair.hasKey("path")) {
174
+                        p[i] = pair.getString("path");
175
+                        if(pair.hasKey("mime"))
176
+                            m[i] = pair.getString("mime");
177
+                        else
178
+                            m[i] = null;
179
+                    }
180
+                }
181
+                new RNFetchBlobFS(ctx).scanFile(p, m, callback);
136 182
             }
137
-        }
138
-        new RNFetchBlobFS(this.getReactApplicationContext()).scanFile(p, m, callback);
183
+        });
184
+
139 185
     }
140 186
 
141 187
     @ReactMethod

+ 4
- 5
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java View File

@@ -10,12 +10,11 @@ public class RNFetchBlobConst {
10 10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
11 11
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
12 12
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
13
-    public static final MediaType MIME_OCTET = MediaType.parse("application/octet-stream");
14
-    public static final MediaType MIME_MULTIPART = MediaType.parse("multipart/form-data");
15
-	public static final MediaType MIME_ENCODED = MediaType.parse("application/x-www-form-urlencoded");
16 13
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
17 14
     public static final String FILE_PREFIX_CONTENT = "content://";
18 15
     public static final String DATA_ENCODE_URI = "uri";
19
-    public static final String DATA_ENCODE_BASE64 = "base64";
20
-    public static final String DATA_ENCODE_UTF8 = "utf8";
16
+    public static final String RNFB_RESPONSE_BASE64 = "base64";
17
+    public static final String RNFB_RESPONSE_UTF8  = "utf8";
18
+    public static final String RNFB_RESPONSE_PATH  = "path";
19
+
21 20
 }

+ 100
- 130
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

@@ -36,6 +36,11 @@ import java.nio.charset.Charset;
36 36
 import java.util.HashMap;
37 37
 import java.util.Map;
38 38
 import java.util.UUID;
39
+import java.util.concurrent.BlockingDeque;
40
+import java.util.concurrent.BlockingQueue;
41
+import java.util.concurrent.LinkedBlockingQueue;
42
+import java.util.concurrent.ThreadPoolExecutor;
43
+import java.util.concurrent.TimeUnit;
39 44
 
40 45
 /**
41 46
  * Created by wkh237 on 2016/5/26.
@@ -71,51 +76,41 @@ public class RNFetchBlobFS {
71 76
      * @param promise
72 77
      */
73 78
     static public void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) {
74
-        AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {
75
-            @Override
76
-            protected Integer doInBackground(String... args) {
77
-                try {
78
-                    String path = args[0];
79
-                    String encoding = args[1];
80
-                    String data = args[2];
81
-                    int written = 0;
82
-                    File f = new File(path);
83
-                    File dir = f.getParentFile();
84
-                    if(!dir.exists())
85
-                        dir.mkdirs();
86
-                    FileOutputStream fout = new FileOutputStream(f, append);
87
-                    // write data from a file
88
-                    if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
89
-                        File src = new File(data);
90
-                        if(!src.exists()) {
91
-                            promise.reject("RNfetchBlob writeFileError", "source file : " + data + "not exists");
92
-                            fout.close();
93
-                            return null;
94
-                        }
95
-                        FileInputStream fin = new FileInputStream(src);
96
-                        byte [] buffer = new byte [10240];
97
-                        int read;
98
-                        written = 0;
99
-                        while((read = fin.read(buffer)) > 0) {
100
-                            fout.write(buffer, 0, read);
101
-                            written += read;
102
-                        }
103
-                        fin.close();
104
-                    }
105
-                    else {
106
-                        byte[] bytes = stringToBytes(data, encoding);
107
-                        fout.write(bytes);
108
-                        written = bytes.length;
109
-                    }
79
+        try {
80
+            int written = 0;
81
+            File f = new File(path);
82
+            File dir = f.getParentFile();
83
+            if(!dir.exists())
84
+                dir.mkdirs();
85
+            FileOutputStream fout = new FileOutputStream(f, append);
86
+            // write data from a file
87
+            if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
88
+                File src = new File(data);
89
+                if(!src.exists()) {
90
+                    promise.reject("RNfetchBlob writeFileError", "source file : " + data + "not exists");
110 91
                     fout.close();
111
-                    promise.resolve(written);
112
-                } catch (Exception e) {
113
-                    promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage());
92
+                    return ;
114 93
                 }
115
-                return null;
94
+                FileInputStream fin = new FileInputStream(src);
95
+                byte [] buffer = new byte [10240];
96
+                int read;
97
+                written = 0;
98
+                while((read = fin.read(buffer)) > 0) {
99
+                    fout.write(buffer, 0, read);
100
+                    written += read;
101
+                }
102
+                fin.close();
116 103
             }
117
-        };
118
-        task.execute(path, encoding, data);
104
+            else {
105
+                byte[] bytes = stringToBytes(data, encoding);
106
+                fout.write(bytes);
107
+                written = bytes.length;
108
+            }
109
+            fout.close();
110
+            promise.resolve(written);
111
+        } catch (Exception e) {
112
+            promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage());
113
+        }
119 114
     }
120 115
 
121 116
     /**
@@ -125,31 +120,24 @@ public class RNFetchBlobFS {
125 120
      * @param promise
126 121
      */
127 122
     static public void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
128
-        AsyncTask<Object, Void, Void> task = new AsyncTask<Object, Void, Void>() {
129
-            @Override
130
-            protected Void doInBackground(Object... args) {
131
-                try {
132
-                    String path = (String)args[0];
133
-                    ReadableArray data = (ReadableArray) args[1];
134
-                    File f = new File(path);
135
-                    File dir = f.getParentFile();
136
-                    if(!dir.exists())
137
-                        dir.mkdirs();
138
-                    FileOutputStream os = new FileOutputStream(f, append);
139
-                    byte [] bytes = new byte[data.size()];
140
-                    for(int i=0;i<data.size();i++) {
141
-                        bytes[i] = (byte) data.getInt(i);
142
-                    }
143
-                    os.write(bytes);
144
-                    os.close();
145
-                    promise.resolve(data.size());
146
-                } catch (Exception e) {
147
-                    promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage());
148
-                }
149
-                return null;
123
+
124
+        try {
125
+
126
+            File f = new File(path);
127
+            File dir = f.getParentFile();
128
+            if(!dir.exists())
129
+                dir.mkdirs();
130
+            FileOutputStream os = new FileOutputStream(f, append);
131
+            byte [] bytes = new byte[data.size()];
132
+            for(int i=0;i<data.size();i++) {
133
+                bytes[i] = (byte) data.getInt(i);
150 134
             }
151
-        };
152
-        task.execute(path, data);
135
+            os.write(bytes);
136
+            os.close();
137
+            promise.resolve(data.size());
138
+        } catch (Exception e) {
139
+            promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage());
140
+        }
153 141
     }
154 142
 
155 143
     /**
@@ -160,58 +148,49 @@ public class RNFetchBlobFS {
160 148
      */
161 149
     static public void readFile(String path, String encoding, final Promise promise ) {
162 150
         path = normalizePath(path);
163
-        AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {
164
-            @Override
165
-            protected Integer doInBackground(String... strings) {
166
-
167
-                try {
168
-                    String path = strings[0];
169
-                    String encoding = strings[1];
170
-                    byte[] bytes;
171
-
172
-                    if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
173
-                        String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
174
-                        long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
175
-                        bytes = new byte[(int) length];
176
-                        InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
177
-                        in.read(bytes, 0, (int) length);
178
-                        in.close();
179
-                    }
180
-                    else {
181
-                        File f = new File(path);
182
-                        int length = (int) f.length();
183
-                        bytes = new byte[length];
184
-                        FileInputStream in = new FileInputStream(f);
185
-                        in.read(bytes);
186
-                        in.close();
187
-                    }
151
+        try {
152
+            byte[] bytes;
153
+
154
+            if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
155
+                String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
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
+            }
188 170
 
189
-                    switch (encoding.toLowerCase()) {
190
-                        case "base64" :
191
-                            promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
192
-                            break;
193
-                        case "ascii" :
194
-                            WritableArray asciiResult = Arguments.createArray();
195
-                            for(byte b : bytes) {
196
-                                asciiResult.pushInt((int)b);
197
-                            }
198
-                            promise.resolve(asciiResult);
199
-                            break;
200
-                        case "utf8" :
201
-                            promise.resolve(new String(bytes));
202
-                            break;
203
-                        default:
204
-                            promise.resolve(new String(bytes));
205
-                            break;
171
+            switch (encoding.toLowerCase()) {
172
+                case "base64" :
173
+                    promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
174
+                    break;
175
+                case "ascii" :
176
+                    WritableArray asciiResult = Arguments.createArray();
177
+                    for(byte b : bytes) {
178
+                        asciiResult.pushInt((int)b);
206 179
                     }
207
-                }
208
-                catch(Exception err) {
209
-                    promise.reject("ReadFile Error", err.getLocalizedMessage());
210
-                }
211
-                return null;
180
+                    promise.resolve(asciiResult);
181
+                    break;
182
+                case "utf8" :
183
+                    promise.resolve(new String(bytes));
184
+                    break;
185
+                default:
186
+                    promise.resolve(new String(bytes));
187
+                    break;
212 188
             }
213
-        };
214
-        task.execute(path, encoding);
189
+        }
190
+        catch(Exception err) {
191
+            promise.reject("ReadFile Error", err.getLocalizedMessage());
192
+        }
193
+
215 194
     }
216 195
 
217 196
     /**
@@ -375,16 +354,15 @@ public class RNFetchBlobFS {
375 354
      */
376 355
     static void writeArrayChunk(String streamId, ReadableArray data, Callback callback) {
377 356
 
378
-        RNFetchBlobFS fs = fileStreams.get(streamId);
379
-        OutputStream stream = fs.writeStreamInstance;
380
-        byte [] chunk = new byte[data.size()];
381
-        for(int i =0; i< data.size();i++) {
382
-            chunk[i] = (byte) data.getInt(i);
383
-        }
384 357
         try {
358
+            RNFetchBlobFS fs = fileStreams.get(streamId);
359
+            OutputStream stream = fs.writeStreamInstance;
360
+            byte [] chunk = new byte[data.size()];
361
+            for(int i =0; i< data.size();i++) {
362
+                chunk[i] = (byte) data.getInt(i);
363
+            }
385 364
             stream.write(chunk);
386 365
             callback.invoke();
387
-            chunk = null;
388 366
         } catch (Exception e) {
389 367
             callback.invoke(e.getLocalizedMessage());
390 368
         }
@@ -795,14 +773,6 @@ public class RNFetchBlobFS {
795 773
         }
796 774
         else if(encoding.toLowerCase().contains("base64")) {
797 775
             byte [] b = Base64.decode(data, Base64.NO_WRAP);
798
-            if(encoding.toLowerCase().contains("urlencode")) {
799
-                try {
800
-                    String encoded = URLDecoder.decode(new String(b), "UTF-8");
801
-                    b = encoded.getBytes();
802
-                } catch (Exception e) {
803
-                    e.printStackTrace();
804
-                }
805
-            }
806 776
             return b;
807 777
         }
808 778
         else if(encoding.equalsIgnoreCase("utf8")) {

+ 23
- 17
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -8,7 +8,6 @@ import android.content.IntentFilter;
8 8
 import android.database.Cursor;
9 9
 import android.net.Uri;
10 10
 import android.util.Base64;
11
-import android.util.Log;
12 11
 
13 12
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
14 13
 import com.RNFetchBlob.Response.RNFetchBlobFileResp;
@@ -21,9 +20,7 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator;
21 20
 import com.facebook.react.bridge.WritableMap;
22 21
 import com.facebook.react.modules.core.DeviceEventManagerModule;
23 22
 
24
-import java.io.ByteArrayInputStream;
25 23
 import java.io.File;
26
-import java.io.FileInputStream;
27 24
 import java.io.FileOutputStream;
28 25
 import java.io.IOException;
29 26
 import java.io.InputStream;
@@ -35,7 +32,6 @@ import java.nio.ByteBuffer;
35 32
 import java.nio.CharBuffer;
36 33
 import java.nio.charset.CharacterCodingException;
37 34
 import java.nio.charset.Charset;
38
-import java.nio.charset.CharsetDecoder;
39 35
 import java.nio.charset.CharsetEncoder;
40 36
 import java.util.HashMap;
41 37
 import java.util.concurrent.TimeUnit;
@@ -50,9 +46,6 @@ import okhttp3.Request;
50 46
 import okhttp3.RequestBody;
51 47
 import okhttp3.Response;
52 48
 import okhttp3.ResponseBody;
53
-import okhttp3.FormBody;
54
-import okhttp3.internal.framed.Header;
55
-import okhttp3.internal.http.OkHeaders;
56 49
 
57 50
 /**
58 51
  * Created by wkh237 on 2016/6/21.
@@ -333,10 +326,10 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
333 326
                     // check if this error caused by socket timeout
334 327
                     if(e.getClass().equals(SocketTimeoutException.class)) {
335 328
                         respInfo.putBoolean("timeout", true);
336
-                        callback.invoke("request timed out.", respInfo, null);
329
+                        callback.invoke("request timed out.", null, null);
337 330
                     }
338 331
                     else
339
-                        callback.invoke(e.getLocalizedMessage(), respInfo, null);
332
+                        callback.invoke(e.getLocalizedMessage(), null, null);
340 333
                     removeTaskInfo();
341 334
                 }
342 335
 
@@ -408,8 +401,9 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
408 401
                         ins.close();
409 402
                         os.flush();
410 403
                         os.close();
411
-                        callback.invoke(null, null, dest);
404
+                        callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, dest);
412 405
                     }
406
+                    // response data directly pass to JS context as string.
413 407
                     else {
414 408
                         // #73 Check if the response data contains valid UTF8 string, since BASE64
415 409
                         // encoding will somehow break the UTF8 string format, to encode UTF8
@@ -418,14 +412,15 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
418 412
                         CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
419 413
                         try {
420 414
                             encoder.encode(ByteBuffer.wrap(b).asCharBuffer());
421
-                            // if the data can be encoded to UTF8 append URL encode
422
-                            b = URLEncoder.encode(new String(b), "UTF-8").replace("+", "%20").getBytes();
415
+                            // if the data contains invalid characters the following lines will be
416
+                            // skipped.
417
+                            String utf8 = new String(b);
418
+                            callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, utf8);
423 419
                         }
424 420
                         // This usually mean the data is contains invalid unicode characters, it's
425 421
                         // binary data
426
-                        catch(CharacterCodingException ignored) {}
427
-                        finally {
428
-                            callback.invoke(null, null, android.util.Base64.encodeToString(b, Base64.NO_WRAP));
422
+                        catch(CharacterCodingException ignored) {
423
+                            callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP));
429 424
                         }
430 425
                     }
431 426
                 } catch (IOException e) {
@@ -439,11 +434,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
439 434
                     // and write response data to destination path.
440 435
                     resp.body().bytes();
441 436
                 } catch (Exception ignored) {}
442
-                callback.invoke(null, null, this.destPath);
437
+                callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
443 438
                 break;
444 439
             default:
445 440
                 try {
446
-                    callback.invoke(null, null, new String(resp.body().bytes(), "UTF-8"));
441
+                    callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, new String(resp.body().bytes(), "UTF-8"));
447 442
                 } catch (IOException e) {
448 443
                     callback.invoke("RNFetchBlob failed to encode response data to UTF8 string.", null);
449 444
                 }
@@ -472,6 +467,12 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
472 467
         return uploadProgressReport.get(taskId);
473 468
     }
474 469
 
470
+    /**
471
+     * Create response information object, conatins status code, headers, etc.
472
+     * @param resp
473
+     * @param isBlobResp
474
+     * @return
475
+     */
475 476
     private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
476 477
         WritableMap info = Arguments.createMap();
477 478
         info.putInt("status", resp.code());
@@ -499,6 +500,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
499 500
         return info;
500 501
     }
501 502
 
503
+    /**
504
+     * Check if response data is binary data.
505
+     * @param resp OkHttp response.
506
+     * @return
507
+     */
502 508
     private boolean isBlobResponse(Response resp) {
503 509
         Headers h = resp.headers();
504 510
         String ctype = getHeaderIgnoreCases(h, "Content-Type");

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

@@ -1,6 +1,7 @@
1 1
 package com.RNFetchBlob.Response;
2 2
 
3 3
 import com.RNFetchBlob.RNFetchBlob;
4
+import com.RNFetchBlob.RNFetchBlobConst;
4 5
 import com.RNFetchBlob.RNFetchBlobReq;
5 6
 import com.facebook.react.bridge.Arguments;
6 7
 import com.facebook.react.bridge.ReactApplicationContext;
@@ -71,7 +72,7 @@ public class RNFetchBlobDefaultResp extends ResponseBody {
71 72
                 args.putString("written", String.valueOf(bytesRead));
72 73
                 args.putString("total", String.valueOf(contentLength()));
73 74
                 rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
74
-                        .emit("RNFetchBlobProgress", args);
75
+                        .emit(RNFetchBlobConst.EVENT_PROGRESS, args);
75 76
             }
76 77
             return read;
77 78
         }

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

@@ -2,6 +2,7 @@ package com.RNFetchBlob.Response;
2 2
 
3 3
 import android.util.Log;
4 4
 
5
+import com.RNFetchBlob.RNFetchBlobConst;
5 6
 import com.RNFetchBlob.RNFetchBlobReq;
6 7
 import com.facebook.react.bridge.Arguments;
7 8
 import com.facebook.react.bridge.ReactApplicationContext;
@@ -79,7 +80,7 @@ public class RNFetchBlobFileResp extends ResponseBody {
79 80
                 args.putString("written", String.valueOf(bytesDownloaded));
80 81
                 args.putString("total", String.valueOf(contentLength()));
81 82
                 rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
82
-                        .emit("RNFetchBlobProgress", args);
83
+                        .emit(RNFetchBlobConst.EVENT_PROGRESS, args);
83 84
             }
84 85
             return read;
85 86
         }

+ 64
- 40
src/index.js View File

@@ -87,6 +87,8 @@ function wrap(path:string):string {
87 87
  *                   If it exists, the absolute path is returned (no network
88 88
  *                   activity takes place )
89 89
  *                   If it doesn't exist, the file is downloaded as usual
90
+ *         @property {number} timeout
91
+ *                   Request timeout in millionseconds, by default it's 30000ms.
90 92
  *
91 93
  * @return {function} This method returns a `fetch` method instance.
92 94
  */
@@ -146,9 +148,20 @@ function fetch(...args:any):Promise {
146 148
 
147 149
     let req = RNFetchBlob[nativeMethodName]
148 150
 
149
-    req(options, taskId, method, url, headers || {}, body, (err, unsused, data) => {
151
+    /**
152
+     * Send request via native module, the response callback accepts three arguments
153
+     * @callback
154
+     * @param err {any} Error message or object, when the request success, this
155
+     *                  parameter should be `null`.
156
+     * @param rawType { 'utf8' | 'base64' | 'path'} RNFB request will be stored
157
+     *                  as UTF8 string, BASE64 string, or a file path reference
158
+     *                  in JS context, and this parameter indicates which one
159
+     *                  dose the response data presents.
160
+     * @param data {string} Response data or its reference.
161
+     */
162
+    req(options, taskId, method, url, headers || {}, body, (err, rawType, data) => {
150 163
 
151
-      // task done, remove event listener
164
+      // task done, remove event listeners
152 165
       subscription.remove()
153 166
       subscriptionUpload.remove()
154 167
       stateEvent.remove()
@@ -156,15 +169,13 @@ function fetch(...args:any):Promise {
156 169
       if(err)
157 170
         reject(new Error(err, respInfo))
158 171
       else {
159
-        let rnfbEncode = 'base64'
160
-        // response data is saved to storage
172
+        // response data is saved to storage, create a session for it
161 173
         if(options.path || options.fileCache || options.addAndroidDownloads
162 174
           || options.key || options.auto && respInfo.respType === 'blob') {
163
-          rnfbEncode = 'path'
164 175
           if(options.session)
165 176
             session(options.session).add(data)
166 177
         }
167
-        respInfo.rnfbEncode = rnfbEncode
178
+        respInfo.rnfbEncode = rawType
168 179
         resolve(new FetchBlobResponse(taskId, respInfo, data))
169 180
       }
170 181
 
@@ -208,7 +219,7 @@ class FetchBlobResponse {
208 219
 
209 220
   taskId : string;
210 221
   path : () => string | null;
211
-  type : 'base64' | 'path';
222
+  type : 'base64' | 'path' | 'utf8';
212 223
   data : any;
213 224
   blob : (contentType:string, sliceSize:number) => null;
214 225
   text : () => string;
@@ -236,22 +247,19 @@ class FetchBlobResponse {
236 247
      * @return {Promise<Blob>} Return a promise resolves Blob object.
237 248
      */
238 249
     this.blob = ():Promise<Blob> => {
250
+      let Blob = polyfill.Blob
251
+      let cType = info.headers['Content-Type'] || info.headers['content-type']
239 252
       return new Promise((resolve, reject) => {
240
-        if(this.type === 'base64') {
241
-          try {
242
-            let b = new polyfill.Blob(this.data, 'application/octet-stream;BASE64')
243
-            b.onCreated(() => {
244
-              resolve(b)
245
-            })
246
-          } catch(err) {
247
-            reject(err)
248
-          }
249
-        }
250
-        else {
251
-          polyfill.Blob.build(wrap(this.data))
252
-          .then((b) => {
253
-            resolve(b)
254
-          })
253
+        switch(this.type) {
254
+          case 'base64':
255
+            Blob.build(this.data, { type : cType + ';BASE64' }).then(resolve)
256
+          break
257
+          case 'path':
258
+            polyfill.Blob.build(wrap(this.data), { type : cType }).then(resolve)
259
+          break
260
+          default:
261
+            polyfill.Blob.build(this.data, { type : 'text/plain' }).then(resolve)
262
+          break
255 263
         }
256 264
       })
257 265
     }
@@ -261,37 +269,52 @@ class FetchBlobResponse {
261 269
      */
262 270
     this.text = ():string => {
263 271
       let res = this.data
264
-      try {
265
-        res = base64.decode(this.data)
266
-        res = decodeURIComponent(res)
267
-      } catch(err) {
268
-        console.warn(err)
269
-        res = ''
272
+      switch(this.type) {
273
+        case 'base64':
274
+          return base64.decode(this.data)
275
+        break
276
+        case 'path':
277
+          return fs.readFile(this.data, 'utf8')
278
+        break
279
+        default:
280
+          return this.data
281
+        break
270 282
       }
271
-      return res
272 283
     }
273 284
     /**
274 285
      * Convert result to JSON object.
275 286
      * @return {object} Parsed javascript object.
276 287
      */
277 288
     this.json = ():any => {
278
-      let res = this.data
279
-      try {
280
-        res = base64.decode(this.data)
281
-        res = decodeURIComponent(res)
282
-        res = JSON.parse(res)
283
-      } catch(err) {
284
-        console.warn(err)
285
-        res = {}
289
+      switch(this.type) {
290
+        case 'base64':
291
+          return JSON.parse(base64.decode(this.data))
292
+        break
293
+        case 'path':
294
+          return fs.readFile(this.data, 'utf8')
295
+                   .then((text) => Promise.resolve(JSON.parse(text)))
296
+        break
297
+        default:
298
+          return JSON.parse(this.data)
299
+        break
286 300
       }
287
-      return res
288 301
     }
289 302
     /**
290 303
      * Return BASE64 string directly.
291 304
      * @return {string} BASE64 string of response body.
292 305
      */
293 306
     this.base64 = ():string => {
294
-      return this.data
307
+      switch(this.type) {
308
+        case 'base64':
309
+          return this.data
310
+        break
311
+        case 'path':
312
+          return fs.readFile(this.data, 'base64')
313
+        break
314
+        default:
315
+          return base64.encode(this.data)
316
+        break
317
+      }
295 318
     }
296 319
     /**
297 320
      * Remove cahced file
@@ -299,7 +322,7 @@ class FetchBlobResponse {
299 322
      */
300 323
     this.flush = () => {
301 324
       let path = this.path()
302
-      if(!path)
325
+      if(!path || this.type !== 'path')
303 326
         return
304 327
       return unlink(path)
305 328
     }
@@ -312,6 +335,7 @@ class FetchBlobResponse {
312 335
         return this.data
313 336
       return null
314 337
     }
338
+
315 339
     this.session = (name:string):RNFetchBlobSession | null => {
316 340
       if(this.type === 'path')
317 341
         return session(name).add(this.data)

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

@@ -40,6 +40,11 @@ extern NSString *const FS_EVENT_ERROR;
40 40
 extern NSString *const KEY_REPORT_PROGRESS;
41 41
 extern NSString *const KEY_REPORT_UPLOAD_PROGRESS;
42 42
 
43
+// response type
44
+extern NSString *const RESP_TYPE_BASE64;
45
+extern NSString *const RESP_TYPE_UTF8;
46
+extern NSString *const RESP_TYPE_PATH;
47
+
43 48
 
44 49
 
45 50
 #endif /* RNFetchBlobConst_h */

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

@@ -34,3 +34,8 @@ extern NSString *const FS_EVENT_ERROR = @"error";
34 34
 
35 35
 extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
36 36
 extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
37
+
38
+// response type
39
+extern NSString *const RESP_TYPE_BASE64 = @"base64";
40
+extern NSString *const RESP_TYPE_UTF8 = @"utf8";
41
+extern NSString *const RESP_TYPE_PATH = @"path";

+ 14
- 8
src/ios/RNFetchBlobNetwork.m View File

@@ -323,6 +323,7 @@ NSOperationQueue *taskQueue;
323 323
     self.error = error;
324 324
     NSString * errMsg = [NSNull null];
325 325
     NSString * respStr = [NSNull null];
326
+    NSString * rnfbRespType = @"";
326 327
     
327 328
     [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
328 329
     
@@ -345,6 +346,7 @@ NSOperationQueue *taskQueue;
345 346
         if(respFile == YES)
346 347
         {
347 348
             [writeStream close];
349
+            rnfbRespType = RESP_TYPE_PATH;
348 350
             respStr = destPath;
349 351
         }
350 352
         // base64 response
@@ -353,19 +355,23 @@ NSOperationQueue *taskQueue;
353 355
             // when response type is BASE64, we should first try to encode the response data to UTF8 format
354 356
             // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
355 357
             // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
356
-            NSString * urlEncoded = [[[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding]
357
-                                     stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
358
-            NSString * base64 = @"";
359
-            if(urlEncoded != nil)
360
-                base64 = [[urlEncoded dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
358
+            NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
359
+            
360
+            if(utf8 != nil)
361
+            {
362
+                rnfbRespType = RESP_TYPE_UTF8;
363
+                respStr = utf8;
364
+            }
361 365
             else
362
-                base64 = [respData base64EncodedStringWithOptions:0];
363
-            respStr = base64;
366
+            {
367
+                rnfbRespType = RESP_TYPE_BASE64;
368
+                respStr = [respData base64EncodedStringWithOptions:0];
369
+            }
364 370
             
365 371
         }
366 372
     }
367 373
     
368
-    callback(@[ errMsg, respInfo, respStr ]);
374
+    callback(@[ errMsg, rnfbRespType, respStr]);
369 375
     
370 376
     @synchronized(taskTable, uploadProgressTable, progressTable)
371 377
     {

+ 1
- 1
src/polyfill/Blob.js View File

@@ -128,7 +128,7 @@ export default class Blob extends EventTarget {
128 128
       // when content type contains application/octet* or *;base64, RNFetchBlob
129 129
       // fs will treat it as BASE64 encoded string binary data
130 130
       if(/(application\/octet|\;base64)/i.test(mime))
131
-        encoding = 'base64+urlencode'
131
+        encoding = 'base64'
132 132
       else
133 133
         data = data.toString()
134 134
       // create cache file

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

@@ -133,9 +133,7 @@ function readText(resp, info):Promise<string> {
133 133
       return Promise.resolve(resp.text())
134 134
       break
135 135
     case 'path':
136
-      return resp.readFile('utf8').then((data) => {
137
-        return Promise.resolve(data)
138
-      })
136
+      return resp.text()
139 137
       break
140 138
     default:
141 139
       return Promise.resolve(resp.text())
@@ -152,15 +150,7 @@ function readText(resp, info):Promise<string> {
152 150
  */
153 151
 function readBlob(resp, info):Promise<Blob> {
154 152
   log.verbose('readBlob', resp, info)
155
-  let cType = info.headers['Content-Type']
156
-  switch (info.rnfbEncode) {
157
-    case 'base64':
158
-      return Blob.build(resp.data, { type : `${cType};BASE64` })
159
-    case 'path':
160
-      return Blob.build(RNFetchBlob.wrap(resp.data), { type : `${cType}`})
161
-    default:
162
-      return Blob.build(resp.data, { type : `${cType}`})
163
-  }
153
+  return resp.blob()
164 154
 }
165 155
 
166 156
 /**
@@ -175,10 +165,8 @@ function readJSON(resp, info):Promise<object> {
175 165
     case 'base64':
176 166
       return Promise.resolve(resp.json())
177 167
     case 'path':
178
-      return resp.readFile('utf8').then((data) => {
179
-        return Promise.resolve(JSON.parse(data))
180
-      })
168
+      return resp.json()
181 169
     default:
182
-      return Promise.resolve(JSON.parse(resp.data))
170
+      return Promise.resolve(resp.json())
183 171
   }
184 172
 }

+ 22
- 20
src/polyfill/XMLHttpRequest.js View File

@@ -10,7 +10,7 @@ import ProgressEvent from './ProgressEvent.js'
10 10
 
11 11
 const log = new Log('XMLHttpRequest')
12 12
 
13
-log.level(0)
13
+log.level(2)
14 14
 
15 15
 const UNSENT = 0
16 16
 const OPENED = 1
@@ -232,7 +232,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
232 232
   }
233 233
 
234 234
   _headerReceived(e) {
235
-    log.verbose('header received ', this._task.taskId, e)
235
+    log.debug('header received ', this._task.taskId, e)
236 236
     this.responseURL = this._url
237 237
     if(e.state === "2") {
238 238
       this._responseHeaders = e.headers
@@ -269,7 +269,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
269 269
     if(statusCode >= 100 && statusCode !== 408) {
270 270
       return
271 271
     }
272
-    log.verbose('XMLHttpRequest error', err)
272
+    log.debug('XMLHttpRequest error', err)
273 273
     this._statusText = err
274 274
     this._status = String(err).match(/\d+/)
275 275
     this._status = this._status ? Math.floor(this.status) : 404
@@ -286,33 +286,35 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
286 286
   }
287 287
 
288 288
   _onDone(resp) {
289
-    log.verbose('XMLHttpRequest done', this._url, resp)
289
+    log.debug('XMLHttpRequest done', this._url, resp, this)
290 290
     this._statusText = this._status
291
+    let responseDataReady = () => {
292
+      this.dispatchEvent('load')
293
+      this.dispatchEvent('loadend')
294
+      this._dispatchReadStateChange(XMLHttpRequest.DONE)
295
+      this.clearEventListeners()
296
+    }
291 297
     if(resp) {
292
-      switch(resp.type) {
293
-        case 'base64' :
294
-          if(this._responseType === 'json') {
295
-              this._responseText = resp.text()
296
-              this._response = resp.json()
297
-          }
298
-          else {
299
-            this._responseText = resp.text()
300
-            this._response = this.responseText
301
-          }
298
+      let info = resp.respInfo || {}
299
+      switch(info.respType) {
300
+        case 'json' :
301
+          this._response = resp.json()
302
+          responseDataReady()
302 303
         break;
303
-        case 'path' :
304
-          this.response = resp.blob()
304
+        case 'blob' :
305
+          resp.blob().then((b) => {
306
+            this.response = b
307
+            responseDataReady()
308
+          })
305 309
         break;
306 310
         default :
307 311
           this._responseText = resp.text()
308 312
           this._response = this.responseText
313
+          responseDataReady()
309 314
         break;
310 315
       }
311
-      this.dispatchEvent('load')
312
-      this.dispatchEvent('loadend')
313
-      this._dispatchReadStateChange(XMLHttpRequest.DONE)
314 316
     }
315
-    this.clearEventListeners()
317
+
316 318
   }
317 319
 
318 320
   _dispatchReadStateChange(state) {