ソースを参照

Change native network module logic and improve Android fs performance

Ben Hsieh 7 年 前
コミット
7c2782ade5

+ 73
- 27
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java ファイルの表示

9
 import com.facebook.react.bridge.ReadableMap;
9
 import com.facebook.react.bridge.ReadableMap;
10
 
10
 
11
 import java.util.Map;
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
 public class RNFetchBlob extends ReactContextBaseJavaModule {
16
 public class RNFetchBlob extends ReactContextBaseJavaModule {
14
 
17
 
15
     static ReactApplicationContext RCTContext;
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
     public RNFetchBlob(ReactApplicationContext reactContext) {
22
     public RNFetchBlob(ReactApplicationContext reactContext) {
18
 
23
 
31
     }
36
     }
32
 
37
 
33
     @ReactMethod
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
     @ReactMethod
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
     @ReactMethod
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
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
62
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
46
     }
63
     }
47
 
64
 
61
     }
78
     }
62
 
79
 
63
     @ReactMethod
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
     @ReactMethod
91
     @ReactMethod
96
     }
119
     }
97
 
120
 
98
     @ReactMethod
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
     @ReactMethod
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
     @ReactMethod
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
     @ReactMethod
152
     @ReactMethod
121
     }
160
     }
122
 
161
 
123
     @ReactMethod
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
     @ReactMethod
187
     @ReactMethod

+ 4
- 5
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java ファイルの表示

10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
10
     public static final String EVENT_PROGRESS = "RNFetchBlobProgress";
11
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
11
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
12
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
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
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
13
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
17
     public static final String FILE_PREFIX_CONTENT = "content://";
14
     public static final String FILE_PREFIX_CONTENT = "content://";
18
     public static final String DATA_ENCODE_URI = "uri";
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 ファイルの表示

36
 import java.util.HashMap;
36
 import java.util.HashMap;
37
 import java.util.Map;
37
 import java.util.Map;
38
 import java.util.UUID;
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
  * Created by wkh237 on 2016/5/26.
46
  * Created by wkh237 on 2016/5/26.
71
      * @param promise
76
      * @param promise
72
      */
77
      */
73
     static public void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) {
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
                     fout.close();
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
      * @param promise
120
      * @param promise
126
      */
121
      */
127
     static public void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) {
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
      */
148
      */
161
     static public void readFile(String path, String encoding, final Promise promise ) {
149
     static public void readFile(String path, String encoding, final Promise promise ) {
162
         path = normalizePath(path);
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
      */
354
      */
376
     static void writeArrayChunk(String streamId, ReadableArray data, Callback callback) {
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
         try {
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
             stream.write(chunk);
364
             stream.write(chunk);
386
             callback.invoke();
365
             callback.invoke();
387
-            chunk = null;
388
         } catch (Exception e) {
366
         } catch (Exception e) {
389
             callback.invoke(e.getLocalizedMessage());
367
             callback.invoke(e.getLocalizedMessage());
390
         }
368
         }
795
         }
773
         }
796
         else if(encoding.toLowerCase().contains("base64")) {
774
         else if(encoding.toLowerCase().contains("base64")) {
797
             byte [] b = Base64.decode(data, Base64.NO_WRAP);
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
             return b;
776
             return b;
807
         }
777
         }
808
         else if(encoding.equalsIgnoreCase("utf8")) {
778
         else if(encoding.equalsIgnoreCase("utf8")) {

+ 23
- 17
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java ファイルの表示

8
 import android.database.Cursor;
8
 import android.database.Cursor;
9
 import android.net.Uri;
9
 import android.net.Uri;
10
 import android.util.Base64;
10
 import android.util.Base64;
11
-import android.util.Log;
12
 
11
 
13
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
12
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
14
 import com.RNFetchBlob.Response.RNFetchBlobFileResp;
13
 import com.RNFetchBlob.Response.RNFetchBlobFileResp;
21
 import com.facebook.react.bridge.WritableMap;
20
 import com.facebook.react.bridge.WritableMap;
22
 import com.facebook.react.modules.core.DeviceEventManagerModule;
21
 import com.facebook.react.modules.core.DeviceEventManagerModule;
23
 
22
 
24
-import java.io.ByteArrayInputStream;
25
 import java.io.File;
23
 import java.io.File;
26
-import java.io.FileInputStream;
27
 import java.io.FileOutputStream;
24
 import java.io.FileOutputStream;
28
 import java.io.IOException;
25
 import java.io.IOException;
29
 import java.io.InputStream;
26
 import java.io.InputStream;
35
 import java.nio.CharBuffer;
32
 import java.nio.CharBuffer;
36
 import java.nio.charset.CharacterCodingException;
33
 import java.nio.charset.CharacterCodingException;
37
 import java.nio.charset.Charset;
34
 import java.nio.charset.Charset;
38
-import java.nio.charset.CharsetDecoder;
39
 import java.nio.charset.CharsetEncoder;
35
 import java.nio.charset.CharsetEncoder;
40
 import java.util.HashMap;
36
 import java.util.HashMap;
41
 import java.util.concurrent.TimeUnit;
37
 import java.util.concurrent.TimeUnit;
50
 import okhttp3.RequestBody;
46
 import okhttp3.RequestBody;
51
 import okhttp3.Response;
47
 import okhttp3.Response;
52
 import okhttp3.ResponseBody;
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
  * Created by wkh237 on 2016/6/21.
51
  * Created by wkh237 on 2016/6/21.
333
                     // check if this error caused by socket timeout
326
                     // check if this error caused by socket timeout
334
                     if(e.getClass().equals(SocketTimeoutException.class)) {
327
                     if(e.getClass().equals(SocketTimeoutException.class)) {
335
                         respInfo.putBoolean("timeout", true);
328
                         respInfo.putBoolean("timeout", true);
336
-                        callback.invoke("request timed out.", respInfo, null);
329
+                        callback.invoke("request timed out.", null, null);
337
                     }
330
                     }
338
                     else
331
                     else
339
-                        callback.invoke(e.getLocalizedMessage(), respInfo, null);
332
+                        callback.invoke(e.getLocalizedMessage(), null, null);
340
                     removeTaskInfo();
333
                     removeTaskInfo();
341
                 }
334
                 }
342
 
335
 
408
                         ins.close();
401
                         ins.close();
409
                         os.flush();
402
                         os.flush();
410
                         os.close();
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
                     else {
407
                     else {
414
                         // #73 Check if the response data contains valid UTF8 string, since BASE64
408
                         // #73 Check if the response data contains valid UTF8 string, since BASE64
415
                         // encoding will somehow break the UTF8 string format, to encode UTF8
409
                         // encoding will somehow break the UTF8 string format, to encode UTF8
418
                         CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
412
                         CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
419
                         try {
413
                         try {
420
                             encoder.encode(ByteBuffer.wrap(b).asCharBuffer());
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
                         // This usually mean the data is contains invalid unicode characters, it's
420
                         // This usually mean the data is contains invalid unicode characters, it's
425
                         // binary data
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
                 } catch (IOException e) {
426
                 } catch (IOException e) {
439
                     // and write response data to destination path.
434
                     // and write response data to destination path.
440
                     resp.body().bytes();
435
                     resp.body().bytes();
441
                 } catch (Exception ignored) {}
436
                 } catch (Exception ignored) {}
442
-                callback.invoke(null, null, this.destPath);
437
+                callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
443
                 break;
438
                 break;
444
             default:
439
             default:
445
                 try {
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
                 } catch (IOException e) {
442
                 } catch (IOException e) {
448
                     callback.invoke("RNFetchBlob failed to encode response data to UTF8 string.", null);
443
                     callback.invoke("RNFetchBlob failed to encode response data to UTF8 string.", null);
449
                 }
444
                 }
472
         return uploadProgressReport.get(taskId);
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
     private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
476
     private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
476
         WritableMap info = Arguments.createMap();
477
         WritableMap info = Arguments.createMap();
477
         info.putInt("status", resp.code());
478
         info.putInt("status", resp.code());
499
         return info;
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
     private boolean isBlobResponse(Response resp) {
508
     private boolean isBlobResponse(Response resp) {
503
         Headers h = resp.headers();
509
         Headers h = resp.headers();
504
         String ctype = getHeaderIgnoreCases(h, "Content-Type");
510
         String ctype = getHeaderIgnoreCases(h, "Content-Type");

+ 2
- 1
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobDefaultResp.java ファイルの表示

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

+ 2
- 1
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java ファイルの表示

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

+ 64
- 40
src/index.js ファイルの表示

87
  *                   If it exists, the absolute path is returned (no network
87
  *                   If it exists, the absolute path is returned (no network
88
  *                   activity takes place )
88
  *                   activity takes place )
89
  *                   If it doesn't exist, the file is downloaded as usual
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
  * @return {function} This method returns a `fetch` method instance.
93
  * @return {function} This method returns a `fetch` method instance.
92
  */
94
  */
146
 
148
 
147
     let req = RNFetchBlob[nativeMethodName]
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
       subscription.remove()
165
       subscription.remove()
153
       subscriptionUpload.remove()
166
       subscriptionUpload.remove()
154
       stateEvent.remove()
167
       stateEvent.remove()
156
       if(err)
169
       if(err)
157
         reject(new Error(err, respInfo))
170
         reject(new Error(err, respInfo))
158
       else {
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
         if(options.path || options.fileCache || options.addAndroidDownloads
173
         if(options.path || options.fileCache || options.addAndroidDownloads
162
           || options.key || options.auto && respInfo.respType === 'blob') {
174
           || options.key || options.auto && respInfo.respType === 'blob') {
163
-          rnfbEncode = 'path'
164
           if(options.session)
175
           if(options.session)
165
             session(options.session).add(data)
176
             session(options.session).add(data)
166
         }
177
         }
167
-        respInfo.rnfbEncode = rnfbEncode
178
+        respInfo.rnfbEncode = rawType
168
         resolve(new FetchBlobResponse(taskId, respInfo, data))
179
         resolve(new FetchBlobResponse(taskId, respInfo, data))
169
       }
180
       }
170
 
181
 
208
 
219
 
209
   taskId : string;
220
   taskId : string;
210
   path : () => string | null;
221
   path : () => string | null;
211
-  type : 'base64' | 'path';
222
+  type : 'base64' | 'path' | 'utf8';
212
   data : any;
223
   data : any;
213
   blob : (contentType:string, sliceSize:number) => null;
224
   blob : (contentType:string, sliceSize:number) => null;
214
   text : () => string;
225
   text : () => string;
236
      * @return {Promise<Blob>} Return a promise resolves Blob object.
247
      * @return {Promise<Blob>} Return a promise resolves Blob object.
237
      */
248
      */
238
     this.blob = ():Promise<Blob> => {
249
     this.blob = ():Promise<Blob> => {
250
+      let Blob = polyfill.Blob
251
+      let cType = info.headers['Content-Type'] || info.headers['content-type']
239
       return new Promise((resolve, reject) => {
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
      */
269
      */
262
     this.text = ():string => {
270
     this.text = ():string => {
263
       let res = this.data
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
      * Convert result to JSON object.
285
      * Convert result to JSON object.
275
      * @return {object} Parsed javascript object.
286
      * @return {object} Parsed javascript object.
276
      */
287
      */
277
     this.json = ():any => {
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
      * Return BASE64 string directly.
303
      * Return BASE64 string directly.
291
      * @return {string} BASE64 string of response body.
304
      * @return {string} BASE64 string of response body.
292
      */
305
      */
293
     this.base64 = ():string => {
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
      * Remove cahced file
320
      * Remove cahced file
299
      */
322
      */
300
     this.flush = () => {
323
     this.flush = () => {
301
       let path = this.path()
324
       let path = this.path()
302
-      if(!path)
325
+      if(!path || this.type !== 'path')
303
         return
326
         return
304
       return unlink(path)
327
       return unlink(path)
305
     }
328
     }
312
         return this.data
335
         return this.data
313
       return null
336
       return null
314
     }
337
     }
338
+
315
     this.session = (name:string):RNFetchBlobSession | null => {
339
     this.session = (name:string):RNFetchBlobSession | null => {
316
       if(this.type === 'path')
340
       if(this.type === 'path')
317
         return session(name).add(this.data)
341
         return session(name).add(this.data)

+ 5
- 0
src/ios/RNFetchBlobConst.h ファイルの表示

40
 extern NSString *const KEY_REPORT_PROGRESS;
40
 extern NSString *const KEY_REPORT_PROGRESS;
41
 extern NSString *const KEY_REPORT_UPLOAD_PROGRESS;
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
 #endif /* RNFetchBlobConst_h */
50
 #endif /* RNFetchBlobConst_h */

+ 5
- 0
src/ios/RNFetchBlobConst.m ファイルの表示

34
 
34
 
35
 extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
35
 extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
36
 extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
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 ファイルの表示

323
     self.error = error;
323
     self.error = error;
324
     NSString * errMsg = [NSNull null];
324
     NSString * errMsg = [NSNull null];
325
     NSString * respStr = [NSNull null];
325
     NSString * respStr = [NSNull null];
326
+    NSString * rnfbRespType = @"";
326
     
327
     
327
     [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
328
     [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
328
     
329
     
345
         if(respFile == YES)
346
         if(respFile == YES)
346
         {
347
         {
347
             [writeStream close];
348
             [writeStream close];
349
+            rnfbRespType = RESP_TYPE_PATH;
348
             respStr = destPath;
350
             respStr = destPath;
349
         }
351
         }
350
         // base64 response
352
         // base64 response
353
             // when response type is BASE64, we should first try to encode the response data to UTF8 format
355
             // when response type is BASE64, we should first try to encode the response data to UTF8 format
354
             // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
356
             // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
355
             // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
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
             else
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
     @synchronized(taskTable, uploadProgressTable, progressTable)
376
     @synchronized(taskTable, uploadProgressTable, progressTable)
371
     {
377
     {

+ 1
- 1
src/polyfill/Blob.js ファイルの表示

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

+ 4
- 16
src/polyfill/Fetch.js ファイルの表示

133
       return Promise.resolve(resp.text())
133
       return Promise.resolve(resp.text())
134
       break
134
       break
135
     case 'path':
135
     case 'path':
136
-      return resp.readFile('utf8').then((data) => {
137
-        return Promise.resolve(data)
138
-      })
136
+      return resp.text()
139
       break
137
       break
140
     default:
138
     default:
141
       return Promise.resolve(resp.text())
139
       return Promise.resolve(resp.text())
152
  */
150
  */
153
 function readBlob(resp, info):Promise<Blob> {
151
 function readBlob(resp, info):Promise<Blob> {
154
   log.verbose('readBlob', resp, info)
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
     case 'base64':
165
     case 'base64':
176
       return Promise.resolve(resp.json())
166
       return Promise.resolve(resp.json())
177
     case 'path':
167
     case 'path':
178
-      return resp.readFile('utf8').then((data) => {
179
-        return Promise.resolve(JSON.parse(data))
180
-      })
168
+      return resp.json()
181
     default:
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 ファイルの表示

10
 
10
 
11
 const log = new Log('XMLHttpRequest')
11
 const log = new Log('XMLHttpRequest')
12
 
12
 
13
-log.level(0)
13
+log.level(2)
14
 
14
 
15
 const UNSENT = 0
15
 const UNSENT = 0
16
 const OPENED = 1
16
 const OPENED = 1
232
   }
232
   }
233
 
233
 
234
   _headerReceived(e) {
234
   _headerReceived(e) {
235
-    log.verbose('header received ', this._task.taskId, e)
235
+    log.debug('header received ', this._task.taskId, e)
236
     this.responseURL = this._url
236
     this.responseURL = this._url
237
     if(e.state === "2") {
237
     if(e.state === "2") {
238
       this._responseHeaders = e.headers
238
       this._responseHeaders = e.headers
269
     if(statusCode >= 100 && statusCode !== 408) {
269
     if(statusCode >= 100 && statusCode !== 408) {
270
       return
270
       return
271
     }
271
     }
272
-    log.verbose('XMLHttpRequest error', err)
272
+    log.debug('XMLHttpRequest error', err)
273
     this._statusText = err
273
     this._statusText = err
274
     this._status = String(err).match(/\d+/)
274
     this._status = String(err).match(/\d+/)
275
     this._status = this._status ? Math.floor(this.status) : 404
275
     this._status = this._status ? Math.floor(this.status) : 404
286
   }
286
   }
287
 
287
 
288
   _onDone(resp) {
288
   _onDone(resp) {
289
-    log.verbose('XMLHttpRequest done', this._url, resp)
289
+    log.debug('XMLHttpRequest done', this._url, resp, this)
290
     this._statusText = this._status
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
     if(resp) {
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
         break;
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
         break;
309
         break;
306
         default :
310
         default :
307
           this._responseText = resp.text()
311
           this._responseText = resp.text()
308
           this._response = this.responseText
312
           this._response = this.responseText
313
+          responseDataReady()
309
         break;
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
   _dispatchReadStateChange(state) {
320
   _dispatchReadStateChange(state) {