Преглед на файлове

wip commit, complete Android form upload

Ben Hsieh преди 8 години
родител
ревизия
622c588ff4

+ 0
- 1
src/android/build.gradle Целия файл

@@ -34,6 +34,5 @@ android {
34 34
 
35 35
 dependencies {
36 36
     compile 'com.facebook.react:react-native:+'
37
-    compile 'com.loopj.android:android-async-http:1.4.9'
38 37
     compile 'com.squareup.okhttp3:okhttp:3.4.1'
39 38
 }

+ 196
- 17
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java Целия файл

@@ -1,42 +1,56 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
+import android.util.Base64;
4
+
3 5
 import com.facebook.react.bridge.Arguments;
6
+import com.facebook.react.bridge.ReactApplicationContext;
7
+import com.facebook.react.bridge.ReadableArray;
8
+import com.facebook.react.bridge.ReadableMap;
9
+import com.facebook.react.bridge.WritableArray;
4 10
 import com.facebook.react.bridge.WritableMap;
5 11
 import com.facebook.react.modules.core.DeviceEventManagerModule;
6 12
 
13
+import java.io.File;
14
+import java.io.FileInputStream;
7 15
 import java.io.IOException;
8 16
 import java.io.InputStream;
17
+import java.util.ArrayList;
18
+import java.util.HashMap;
9 19
 
10 20
 import okhttp3.MediaType;
11 21
 import okhttp3.RequestBody;
12 22
 import okio.Buffer;
13 23
 import okio.BufferedSink;
24
+import okio.ByteString;
14 25
 import okio.ForwardingSink;
15 26
 import okio.Okio;
16 27
 import okio.Sink;
17 28
 
18 29
 /**
19
- * Created by wkh237on 2016/7/11.
30
+ * Created by wkh237 on 2016/7/11.
20 31
  */
21 32
 public class RNFetchBlobBody extends RequestBody{
22 33
 
23 34
     InputStream requestStream;
24 35
     long contentLength;
25
-    RequestBody originalBody;
36
+    long bytesWritten = 0;
37
+    ReadableArray form;
26 38
     String mTaskId;
27 39
     RNFetchBlobReq.RequestType requestType;
40
+    MediaType mime;
28 41
 
29
-    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, RequestBody body, InputStream stream, long size) {
42
+    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, InputStream stream, long size, MediaType contentType) {
30 43
         this.mTaskId = taskId;
31
-        originalBody = body;
44
+        this.form = form;
32 45
         requestStream = stream;
33 46
         contentLength = size;
34 47
         requestType = type;
48
+        mime = contentType;
35 49
     }
36 50
 
37 51
     @Override
38 52
     public MediaType contentType() {
39
-        return originalBody.contentType();
53
+        return mime;
40 54
     }
41 55
 
42 56
     @Override
@@ -46,39 +60,204 @@ public class RNFetchBlobBody extends RequestBody{
46 60
         BufferedSink buffer = Okio.buffer(source);
47 61
         switch (requestType) {
48 62
             case Form:
49
-                originalBody.writeTo(buffer);
50
-                break;
51
-            case SingleFile:
52
-                byte [] chunk = new byte[10240];
53
-                int cursor = requestStream.read(chunk, 0, 10240);
54
-                while(cursor > 0) {
55
-                    cursor = requestStream.read(chunk, 0, 10240);
56
-                    buffer.write(chunk);
63
+                String boundary = "RNFetchBlob-" + mTaskId;
64
+                ArrayList<FormField> fields = countFormDataLength();
65
+                ReactApplicationContext ctx = RNFetchBlob.RCTContext;
66
+                for(int i = 0;i < fields.size(); i++) {
67
+                    FormField field = fields.get(i);
68
+                    String data = field.data;
69
+                    String name = field.name;
70
+                    // skip invalid fields
71
+                    if(name == null || data == null)
72
+                        continue;
73
+                    // form begin
74
+                    String header = "--" + boundary + "\r\n";
75
+                    if (field.filename != null) {
76
+                        header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
77
+                        header += "Content-Type: " + field.mime+ "\r\n\r\n";
78
+                        sink.write(header.getBytes());
79
+                        // file field header end
80
+                        // upload from storage
81
+                        if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
82
+                            String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
83
+                            orgPath = RNFetchBlobFS.normalizePath(orgPath);
84
+                            // path starts with content://
85
+                            if (RNFetchBlobFS.isAsset(orgPath)) {
86
+                                try {
87
+                                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
88
+                                    InputStream in = ctx.getAssets().open(assetName);
89
+                                    pipeStreamToSink(in, sink);
90
+                                } catch (IOException e) {
91
+
92
+                                }
93
+                            }
94
+                            // data from normal files
95
+                            else {
96
+                                File file = new File(RNFetchBlobFS.normalizePath(orgPath));
97
+                                if(file.exists()) {
98
+                                    FileInputStream fs = new FileInputStream(file);
99
+                                    pipeStreamToSink(fs, sink);
100
+                                }
101
+                            }
102
+                        }
103
+                        // base64 embedded file content
104
+                        else {
105
+                            byte[] b = Base64.decode(data, 0);
106
+                            sink.write(b);
107
+                            bytesWritten += b.length;
108
+                            emitUploadProgress(bytesWritten, contentLength);
109
+                        }
110
+
111
+                    }
112
+                    // data field
113
+                    else {
114
+                        header += "Content-Disposition: form-data; name=" + name + "\r\n";
115
+                        header += "Content-Type: " + field.mime + "\r\n\r\n";
116
+                        sink.write(header.getBytes());
117
+                        byte[] fieldData = field.data.getBytes();
118
+                        bytesWritten += fieldData.length;
119
+                        sink.write(fieldData);
120
+                    }
121
+                    // form end
122
+                    sink.write("\r\n".getBytes());
57 123
                 }
58
-                requestStream.close();
124
+                // close the form
125
+                byte[] end = ("--" + boundary + "--\r\n").getBytes();
126
+                sink.write(end);
59 127
                 break;
60
-            case Others:
61
-                originalBody.writeTo(buffer);
128
+            case SingleFile:
129
+                pipeStreamToSink(requestStream, sink);
130
+//                byte [] chunk = new byte[10240];
131
+//                int read = requestStream.read(chunk, 0, 10240);
132
+//                sink.write(chunk, 0, read);
133
+//                bytesWritten += read;
134
+//                while(read > 0) {
135
+//                    read = requestStream.read(chunk, 0, 10240);
136
+//                    if(read > 0) {
137
+//                        sink.write(chunk, 0, read);
138
+//                        bytesWritten += read;
139
+//                        emitUploadProgress(bytesWritten, contentLength);
140
+//                    }
141
+//
142
+//                }
143
+//                requestStream.close();
62 144
                 break;
63 145
         }
64 146
         buffer.flush();
65 147
     }
66 148
 
149
+    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
150
+        byte [] chunk = new byte[10240];
151
+        int read = stream.read(chunk, 0, 10240);
152
+        sink.write(chunk, 0, read);
153
+        bytesWritten += read;
154
+        while(read > 0) {
155
+            read = stream.read(chunk, 0, 10240);
156
+            if(read > 0) {
157
+                sink.write(chunk, 0, read);
158
+                bytesWritten += read;
159
+                emitUploadProgress(bytesWritten, contentLength);
160
+            }
161
+
162
+        }
163
+        stream.close();
164
+    }
165
+
166
+    private void emitUploadProgress(long current, long total) {
167
+        WritableMap args = Arguments.createMap();
168
+        args.putString("taskId", mTaskId);
169
+        args.putString("written", String.valueOf(bytesWritten));
170
+        args.putString("total", String.valueOf(contentLength));
171
+
172
+        // emit event to js context
173
+        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
174
+                .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
175
+    }
176
+
177
+    /**
178
+     * Compute a proximate content length for form data
179
+     * @return
180
+     */
181
+    private ArrayList<FormField> countFormDataLength() {
182
+        long total = 0;
183
+        ArrayList<FormField> list = new ArrayList<>();
184
+        ReactApplicationContext ctx = RNFetchBlob.RCTContext;
185
+        for(int i = 0;i < form.size(); i++) {
186
+            ReadableMap field = form.getMap(i);
187
+            list.add(new FormField(field));
188
+            String data = field.getString("data");
189
+            if (field.hasKey("filename")) {
190
+                // upload from storage
191
+                if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
192
+                    String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
193
+                    orgPath = RNFetchBlobFS.normalizePath(orgPath);
194
+                    // path starts with content://
195
+                    if (RNFetchBlobFS.isAsset(orgPath)) {
196
+                        try {
197
+                            String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
198
+                            long length = ctx.getAssets().openFd(assetName).getLength();
199
+                            total += length;
200
+                        } catch (IOException e) {
201
+
202
+                        }
203
+                    } else {
204
+                        File file = new File(RNFetchBlobFS.normalizePath(orgPath));
205
+                        total += file.length();
206
+                    }
207
+                }
208
+                // base64 embedded file content
209
+                else {
210
+                    byte[] bytes = Base64.decode(data, 0);
211
+                    total += bytes.length;
212
+                }
213
+            }
214
+            // data field
215
+            else {
216
+                total += field.getString("data").length();
217
+            }
218
+        }
219
+        contentLength = total;
220
+        return list;
221
+    }
222
+
223
+    private class FormField {
224
+        public String name;
225
+        public String filename;
226
+        public String mime;
227
+        public String data;
228
+
229
+        public FormField(ReadableMap rawData) {
230
+            if(rawData.hasKey("name"))
231
+                name = rawData.getString("name");
232
+            if(rawData.hasKey("filename"))
233
+                filename = rawData.getString("filename");
234
+            if(rawData.hasKey("type"))
235
+                mime = rawData.getString("type");
236
+            else {
237
+                mime = filename == null ? "text/plain" : "application/octet-stream";
238
+            }
239
+            if(rawData.hasKey("data"))
240
+                data = rawData.getString("data");
241
+        }
242
+    }
243
+
67 244
     private final class ProgressReportingSource extends ForwardingSink {
68 245
 
69 246
         private long bytesWritten = 0;
70 247
         private String mTaskId;
71 248
         private long mContentLength ;
249
+        private Sink delegate;
72 250
 
73 251
         public ProgressReportingSource (Sink delegate, String taskId, long contentLength) {
74 252
             super(delegate);
75 253
             this.mTaskId = taskId;
76 254
             this.mContentLength = contentLength;
255
+            this.delegate = delegate;
77 256
         }
78 257
 
79 258
         @Override
80 259
         public void write(Buffer source, long byteCount) throws IOException {
81
-            super.write(source, byteCount);
260
+            delegate.write(source, byteCount);
82 261
             // on progress, emit RNFetchBlobProgress upload progress event with ticketId,
83 262
             // bytesWritten, and totalSize
84 263
             bytesWritten += byteCount;

+ 3
- 3
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java Целия файл

@@ -8,6 +8,7 @@ import android.net.Uri;
8 8
 import android.os.AsyncTask;
9 9
 import android.os.Environment;
10 10
 import android.provider.MediaStore;
11
+import android.util.Base64;
11 12
 
12 13
 import com.facebook.react.bridge.Arguments;
13 14
 import com.facebook.react.bridge.Callback;
@@ -17,7 +18,6 @@ import com.facebook.react.bridge.ReadableArray;
17 18
 import com.facebook.react.bridge.WritableArray;
18 19
 import com.facebook.react.bridge.WritableMap;
19 20
 import com.facebook.react.modules.core.DeviceEventManagerModule;
20
-import com.loopj.android.http.Base64;
21 21
 
22 22
 import java.io.File;
23 23
 import java.io.FileInputStream;
@@ -158,7 +158,7 @@ public class RNFetchBlobFS {
158 158
 
159 159
                     switch (encoding.toLowerCase()) {
160 160
                         case "base64" :
161
-                            promise.resolve(Base64.encodeToString(bytes, 0));
161
+                            promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP));
162 162
                             break;
163 163
                         case "ascii" :
164 164
                             WritableArray asciiResult = Arguments.createArray();
@@ -700,7 +700,7 @@ public class RNFetchBlobFS {
700 700
             return data.getBytes(Charset.forName("US-ASCII"));
701 701
         }
702 702
         else if(encoding.equalsIgnoreCase("base64")) {
703
-            return Base64.decode(data, 0);
703
+            return Base64.decode(data, Base64.NO_WRAP);
704 704
         }
705 705
         else if(encoding.equalsIgnoreCase("utf8")) {
706 706
             return data.getBytes(Charset.forName("UTF-8"));

+ 100
- 101
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java Целия файл

@@ -7,8 +7,8 @@ import android.content.Intent;
7 7
 import android.content.IntentFilter;
8 8
 import android.database.Cursor;
9 9
 import android.net.Uri;
10
+import android.util.Base64;
10 11
 
11
-import com.RNFetchBlob.Request.FormPartBody;
12 12
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
13 13
 import com.RNFetchBlob.Response.RNFetchBlobFileResp;
14 14
 import com.facebook.react.bridge.Callback;
@@ -16,14 +16,16 @@ import com.facebook.react.bridge.ReactApplicationContext;
16 16
 import com.facebook.react.bridge.ReadableArray;
17 17
 import com.facebook.react.bridge.ReadableMap;
18 18
 import com.facebook.react.bridge.ReadableMapKeySetIterator;
19
-import com.loopj.android.http.Base64;
20 19
 
21 20
 import java.io.ByteArrayInputStream;
22 21
 import java.io.File;
23 22
 import java.io.FileInputStream;
24 23
 import java.io.FileNotFoundException;
24
+import java.io.FileOutputStream;
25 25
 import java.io.IOException;
26 26
 import java.io.InputStream;
27
+import java.net.MalformedURLException;
28
+import java.net.URL;
27 29
 
28 30
 import okhttp3.Call;
29 31
 import okhttp3.Interceptor;
@@ -33,6 +35,7 @@ import okhttp3.OkHttpClient;
33 35
 import okhttp3.Request;
34 36
 import okhttp3.RequestBody;
35 37
 import okhttp3.Response;
38
+import okhttp3.ResponseBody;
36 39
 
37 40
 /**
38 41
  * Created by wkh237 on 2016/6/21.
@@ -42,12 +45,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
42 45
     enum RequestType  {
43 46
         Form,
44 47
         SingleFile,
48
+        WithoutBody,
45 49
         Others
46 50
     };
47 51
 
48 52
     enum ResponseType {
49
-        MemoryCache,
50
-        FileCache
53
+        KeepInMemory,
54
+        FileStorage
51 55
     };
52 56
 
53 57
     MediaType contentType = RNFetchBlobConst.MIME_OCTET;
@@ -76,17 +80,17 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
76 80
         this.rawRequestBody = body;
77 81
         this.rawRequestBodyArray = arrayBody;
78 82
 
79
-        if(this.options.fileCache != null || this.options.path != null)
80
-            responseType = ResponseType.FileCache;
83
+        if(this.options.fileCache == true || this.options.path != null)
84
+            responseType = ResponseType.FileStorage;
81 85
         else
82
-            responseType = ResponseType.MemoryCache;
86
+            responseType = ResponseType.KeepInMemory;
83 87
 
84 88
         if (body != null)
85 89
             requestType = RequestType.SingleFile;
86 90
         else if (arrayBody != null)
87 91
             requestType = RequestType.Form;
88 92
         else
89
-            requestType = RequestType.Others;
93
+            requestType = RequestType.WithoutBody;
90 94
     }
91 95
 
92 96
     @Override
@@ -122,109 +126,112 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
122 126
 
123 127
         // find cached result if `key` property exists
124 128
         String cacheKey = this.taskId;
125
-        if (this.options.key != null) {
126
-            cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
127
-            if (cacheKey == null) {
128
-                cacheKey = this.taskId;
129
-            }
130
-
131
-            File file = new File(RNFetchBlobFS.getTmpPath(ctx, cacheKey));
132
-            if (file.exists()) {
133
-                callback.invoke(null, file.getAbsolutePath());
134
-                return;
135
-            }
136
-        }
129
+//        if (this.options.key != null) {
130
+//            cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
131
+//            if (cacheKey == null) {
132
+//                cacheKey = this.taskId;
133
+//            }
134
+//
135
+//            File file = new File(RNFetchBlobFS.getTmpPath(ctx, cacheKey));
136
+//            if (file.exists()) {
137
+//                callback.invoke(null, file.getAbsolutePath());
138
+//                return;
139
+//            }
140
+//        }
137 141
 
138 142
         if(this.options.path != null)
139
-            destPath = this.options.path;
140
-        else if(this.options.fileCache)
141
-            destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey);
143
+            this.destPath = this.options.path;
144
+        else if(this.options.fileCache == true)
145
+            this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey);
142 146
 
143
-        OkHttpClient client;
144
-        try {
147
+        OkHttpClient.Builder client;
145 148
 
149
+//        try {
146 150
             // use trusty SSL socket
147 151
             if (this.options.trusty) {
148 152
                 client = RNFetchBlobUtils.getUnsafeOkHttpClient();
149 153
             } else {
150
-                client = new OkHttpClient();
154
+                client = new OkHttpClient.Builder();
151 155
             }
152 156
 
153 157
             final Request.Builder builder = new Request.Builder();
154
-
158
+            try {
159
+                builder.url(new URL(url));
160
+            } catch (MalformedURLException e) {
161
+                e.printStackTrace();
162
+            }
155 163
             // set headers
156 164
             if (headers != null) {
157 165
                 ReadableMapKeySetIterator it = headers.keySetIterator();
158 166
                 while (it.hasNextKey()) {
159 167
                     String key = it.nextKey();
160
-                    builder.addHeader(key, headers.getString(key));
168
+                    String value = headers.getString(key);
169
+                    builder.header(key, value);
161 170
                 }
162 171
             }
163 172
 
164 173
             // set request body
165 174
             switch (requestType) {
166 175
                 case SingleFile:
176
+                    InputStream dataStream= buildOctetBody(rawRequestBody);
167 177
                     builder.method(method, new RNFetchBlobBody(
168 178
                             taskId,
169 179
                             RequestType.SingleFile,
170 180
                             null,
171
-                            buildOctetBody(rawRequestBody),
172
-                            contentLength
181
+                            dataStream,
182
+                            contentLength,
183
+                            RNFetchBlobConst.MIME_OCTET
173 184
                     ));
174 185
                     break;
175 186
                 case Form:
176 187
                     builder.method(method, new RNFetchBlobBody(
177 188
                             taskId,
178 189
                             RequestType.Form,
179
-                            buildFormBody(rawRequestBodyArray),
190
+                            rawRequestBodyArray,
180 191
                             null,
181
-                            contentLength
182
-                    ));
183
-                case Others:
184
-                    builder.method(method, new RNFetchBlobBody(
185
-                            taskId,
186
-                            RequestType.Others,
187
-                            buildRawBody(rawRequestBody),
188
-                            null,
189
-                            contentLength
192
+                            0,
193
+                            MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
190 194
                     ));
191 195
                     break;
196
+                case WithoutBody:
197
+                    builder.method(method, null);
198
+                    break;
192 199
             }
193 200
 
194 201
             final Request req = builder.build();
195 202
 
196
-            // create response handler
197
-            client.networkInterceptors().add(new Interceptor() {
203
+//             create response handler
204
+            client.addInterceptor(new Interceptor() {
198 205
                 @Override
199 206
                 public Response intercept(Chain chain) throws IOException {
200
-                    Response originalResponse = chain.proceed(req);
201
-                    RNFetchBlobDefaultResp exetneded;
202
-                    switch (responseType) {
203
-                        case MemoryCache:
204
-                            exetneded = new RNFetchBlobDefaultResp(
205
-                                    RNFetchBlob.RCTContext,
206
-                                    taskId,
207
-                                    originalResponse.body());
208
-                            break;
209
-                        case FileCache:
210
-                            exetneded = new RNFetchBlobFileResp(
211
-                                    RNFetchBlob.RCTContext,
212
-                                    taskId,
213
-                                    originalResponse.body(),
214
-                                    destPath);
215
-                            break;
216
-                        default:
217
-                            exetneded = new RNFetchBlobDefaultResp(
218
-                                    RNFetchBlob.RCTContext,
219
-                                    taskId,
220
-                                    originalResponse.body());
221
-                            break;
222
-                    }
223
-                    return originalResponse.newBuilder().body(exetneded).build();
207
+                Response originalResponse = chain.proceed(req);
208
+                    ResponseBody extended;
209
+                switch (responseType) {
210
+                    case KeepInMemory:
211
+                        extended = new RNFetchBlobDefaultResp(
212
+                                RNFetchBlob.RCTContext,
213
+                                taskId,
214
+                                originalResponse.body());
215
+                        break;
216
+                    case FileStorage:
217
+                        extended = new RNFetchBlobFileResp(
218
+                                RNFetchBlob.RCTContext,
219
+                                taskId,
220
+                                originalResponse.body(),
221
+                                destPath);
222
+                        break;
223
+                    default:
224
+                        extended = new RNFetchBlobDefaultResp(
225
+                                RNFetchBlob.RCTContext,
226
+                                taskId,
227
+                                originalResponse.body());
228
+                        break;
229
+                }
230
+                return originalResponse.newBuilder().body(extended).build();
224 231
                 }
225 232
             });
226 233
 
227
-            client.newCall(req).enqueue(new okhttp3.Callback() {
234
+            client.build().newCall(req).enqueue(new okhttp3.Callback() {
228 235
                 @Override
229 236
                 public void onFailure(Call call, IOException e) {
230 237
                     callback.invoke(e.getLocalizedMessage(), null);
@@ -255,9 +262,10 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
255 262
             });
256 263
 
257 264
 
258
-        } catch (Exception error) {
259
-            callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause());
260
-        }
265
+//        } catch (Exception error) {
266
+//            error.printStackTrace();
267
+//            callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause());
268
+//        }
261 269
     }
262 270
 
263 271
     /**
@@ -266,15 +274,22 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
266 274
      */
267 275
     private void done(Response resp) {
268 276
         switch (responseType) {
269
-            case MemoryCache:
277
+            case KeepInMemory:
270 278
                 try {
271
-                    callback.invoke(null, android.util.Base64.encode(resp.body().bytes(), 0));
279
+                    byte [] b = resp.body().bytes();
280
+                    callback.invoke(null, android.util.Base64.encodeToString(b,Base64.NO_WRAP));
272 281
                 } catch (IOException e) {
273 282
                     callback.invoke("RNFetchBlob failed to encode response data to BASE64 string.", null);
274 283
                 }
275 284
                 break;
276
-            case FileCache:
277
-                callback.invoke(null, destPath);
285
+            case FileStorage:
286
+                // write chunk
287
+                try {
288
+                    resp.body().bytes();
289
+                } catch (IOException e) {
290
+                    e.printStackTrace();
291
+                }
292
+                callback.invoke(null, this.destPath);
278 293
                 break;
279 294
             default:
280 295
                 try {
@@ -294,32 +309,10 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
294 309
     RequestBody buildRawBody(String body) {
295 310
         if(body != null) {
296 311
             this.contentType = MediaType.parse(options.mime);
297
-        }
298
-        return RequestBody.create(this.contentType, body);
299
-    }
300
-
301
-    /**
302
-     * When request body is a multipart form data, build a MultipartBody object.
303
-     * @param body Body in array format
304
-     * @return
305
-     */
306
-    MultipartBody buildFormBody(ReadableArray body) {
307
-        if (body != null && (method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put"))) {
308
-            this.contentType = RNFetchBlobConst.MIME_MULTIPART;
309
-            MultipartBody.Builder formBuilder = new MultipartBody.Builder();
310
-            formBuilder.setType(MultipartBody.FORM);
311
-
312
-            for (int i = 0; i < body.size(); i++) {
313
-                ReadableMap map = body.getMap(i);
314
-                FormPartBody fieldData = new FormPartBody(RNFetchBlob.RCTContext, map);
315
-                if(fieldData.filename == null)
316
-                    formBuilder.addFormDataPart(fieldData.fieldName, fieldData.stringBody);
317
-                else
318
-                    formBuilder.addFormDataPart(fieldData.fieldName, fieldData.filename, fieldData.partBody);
319
-            }
320
-            return formBuilder.build();
312
+            return RequestBody.create(this.contentType, body);
321 313
         }
322 314
         return null;
315
+
323 316
     }
324 317
 
325 318
     /**
@@ -337,10 +330,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
337 330
             if (body.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
338 331
                 String orgPath = body.substring(RNFetchBlobConst.FILE_PREFIX.length());
339 332
                 orgPath = RNFetchBlobFS.normalizePath(orgPath);
340
-                // handle
333
+                // upload file from assets
341 334
                 if (RNFetchBlobFS.isAsset(orgPath)) {
342 335
                     try {
343 336
                         String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
337
+                        contentLength = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
344 338
                         return RNFetchBlob.RCTContext.getAssets().open(assetName);
345 339
                     } catch (IOException e) {
346 340
 //                        e.printStackTrace();
@@ -348,13 +342,18 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
348 342
                 } else {
349 343
                     File f = new File(RNFetchBlobFS.normalizePath(orgPath));
350 344
                     try {
345
+                        if(!f.exists())
346
+                            f.createNewFile();
347
+                        contentLength = f.length();
351 348
                         return new FileInputStream(f);
352
-                    } catch (FileNotFoundException e) {
349
+                    } catch (Exception e) {
353 350
                         callback.invoke(e.getLocalizedMessage(), null);
354 351
                     }
355 352
                 }
356 353
             } else {
357
-                return new ByteArrayInputStream(Base64.decode(body, 0));
354
+                byte[] bytes = Base64.decode(body, 0);
355
+                contentLength = bytes.length;
356
+                return new ByteArrayInputStream(bytes);
358 357
             }
359 358
         }
360 359
         return null;

+ 2
- 3
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java Целия файл

@@ -39,7 +39,7 @@ public class RNFetchBlobUtils {
39 39
 
40 40
     }
41 41
 
42
-    public static OkHttpClient getUnsafeOkHttpClient() {
42
+    public static OkHttpClient.Builder getUnsafeOkHttpClient() {
43 43
         try {
44 44
             // Create a trust manager that does not validate certificate chains
45 45
             final TrustManager[] trustAllCerts = new TrustManager[]{
@@ -74,8 +74,7 @@ public class RNFetchBlobUtils {
74 74
                 }
75 75
             });
76 76
 
77
-            OkHttpClient okHttpClient = builder.build();
78
-            return okHttpClient;
77
+            return builder;
79 78
         } catch (Exception e) {
80 79
             throw new RuntimeException(e);
81 80
         }

+ 0
- 71
src/android/src/main/java/com/RNFetchBlob/Request/FormPartBody.java Целия файл

@@ -1,71 +0,0 @@
1
-package com.RNFetchBlob.Request;
2
-
3
-import com.RNFetchBlob.RNFetchBlobConst;
4
-import com.RNFetchBlob.RNFetchBlobFS;
5
-import com.facebook.react.bridge.ReactApplicationContext;
6
-import com.facebook.react.bridge.ReadableMap;
7
-import com.loopj.android.http.Base64;
8
-
9
-import java.io.File;
10
-import java.io.IOException;
11
-import java.io.InputStream;
12
-
13
-import okhttp3.MediaType;
14
-import okhttp3.RequestBody;
15
-
16
-/**
17
- * Created by wkh237 on 2016/7/12.
18
- */
19
-public class FormPartBody {
20
-
21
-    public String fieldName;
22
-    public String filename;
23
-    public RequestBody partBody;
24
-    public String stringBody;
25
-
26
-    public FormPartBody(ReactApplicationContext ctx, ReadableMap field) {
27
-        String data = field.getString("data");
28
-        String name = field.getString("name");
29
-        RequestBody partBody;
30
-        if (field.hasKey("filename")) {
31
-            MediaType mime = field.hasKey("type") ?
32
-                    MediaType.parse(field.getString("type")) : RNFetchBlobConst.MIME_OCTET;
33
-            String filename = field.getString("filename");
34
-            // upload from storage
35
-            if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
36
-                String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
37
-                orgPath = RNFetchBlobFS.normalizePath(orgPath);
38
-                // path starts with content://
39
-                if (RNFetchBlobFS.isAsset(orgPath)) {
40
-                    try {
41
-                        String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
42
-                        InputStream in = ctx.getAssets().open(assetName);
43
-                        long length = ctx.getAssets().openFd(assetName).getLength();
44
-                        byte[] bytes = new byte[(int) length];
45
-                        in.read(bytes, 0, (int) length);
46
-                        in.close();
47
-                        partBody = RequestBody.create(mime, bytes, 0, (int) length);
48
-                    } catch (IOException e) {
49
-                        partBody = null;
50
-                    }
51
-                } else {
52
-                    File file = new File(RNFetchBlobFS.normalizePath(orgPath));
53
-                    partBody = RequestBody.create(mime, file);
54
-                }
55
-            }
56
-            // base64 embedded file content
57
-            else {
58
-                byte[] bytes = Base64.decode(data, 0);
59
-                partBody = RequestBody.create(mime, bytes, 0, bytes.length);
60
-            }
61
-            this.filename = filename;
62
-            this.fieldName = name;
63
-            this.partBody = partBody;
64
-        }
65
-        // data field
66
-        else {
67
-            this.fieldName = name;
68
-            this.stringBody = field.getString("data");
69
-        }
70
-    }
71
-}

+ 38
- 26
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java Целия файл

@@ -10,6 +10,7 @@ import java.io.File;
10 10
 import java.io.FileOutputStream;
11 11
 import java.io.IOException;
12 12
 
13
+import okhttp3.MediaType;
13 14
 import okhttp3.ResponseBody;
14 15
 import okio.Buffer;
15 16
 import okio.BufferedSource;
@@ -20,52 +21,63 @@ import okio.Timeout;
20 21
 /**
21 22
  * Created by wkh237 on 2016/7/11.
22 23
  */
23
-public class RNFetchBlobFileResp extends RNFetchBlobDefaultResp {
24
+public class RNFetchBlobFileResp extends ResponseBody {
24 25
 
26
+    String mTaskId;
27
+    ResponseBody originalBody;
25 28
     String mPath;
29
+    long bytesUploaded = 0;
26 30
     ReactApplicationContext rctContext;
27 31
     FileOutputStream ofStream;
28 32
 
29 33
     public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path) throws IOException {
30
-        super(ctx, taskId, body);
34
+        super();
31 35
         this.rctContext = ctx;
32 36
         this.mTaskId = taskId;
33 37
         this.originalBody = body;
38
+        assert path != null;
34 39
         this.mPath = path;
35
-        File f = new File(path);
36
-        if(f.exists() == false)
37
-            f.createNewFile();
38
-        ofStream = new FileOutputStream(new File(path));
40
+        if (path != null) {
41
+            File f = new File(path);
42
+            if(f.exists() == false)
43
+                f.createNewFile();
44
+            ofStream = new FileOutputStream(new File(path));
45
+        }
39 46
     }
40 47
 
48
+    @Override
49
+    public MediaType contentType() {
50
+        return originalBody.contentType();
51
+    }
52
+
53
+    @Override
54
+    public long contentLength() {
55
+        return originalBody.contentLength();
56
+    }
41 57
 
42 58
     @Override
43 59
     public BufferedSource source() {
44
-        ProgressReportingSource source = new ProgressReportingSource(originalBody.source());
45
-        return Okio.buffer(source);
60
+        ProgressReportingSource countable = new ProgressReportingSource();
61
+        return Okio.buffer(countable);
46 62
     }
47 63
 
48 64
     private class ProgressReportingSource implements Source {
49
-
50
-        BufferedSource mOriginalSource;
51
-        long bytesRead = 0;
52
-
53
-        ProgressReportingSource(BufferedSource originalSource) {
54
-            mOriginalSource = originalSource;
55
-        }
56
-
65
+        int count = 0;
57 66
         @Override
58 67
         public long read(Buffer sink, long byteCount) throws IOException {
59
-            bytesRead += byteCount;
60
-            byte [] bytes = new byte[10240];
61
-            long read = mOriginalSource.read(bytes);
62
-            ofStream.write(bytes);
63
-            WritableMap args = Arguments.createMap();
64
-            args.putString("taskId", mTaskId);
65
-            args.putString("written", String.valueOf(bytesRead));
66
-            args.putString("total", String.valueOf(contentLength()));
67
-            rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
68
-                    .emit("RNFetchBlobProgress", args);
68
+            count++;
69
+            byte [] bytes = new byte[(int) byteCount];
70
+            long read = originalBody.byteStream().read(bytes, 0, (int) byteCount);
71
+            if(read > 0) {
72
+                bytesUploaded += read;
73
+                ofStream.write(bytes, 0, (int) read);
74
+                WritableMap args = Arguments.createMap();
75
+                args.putString("taskId", mTaskId);
76
+                args.putString("written", String.valueOf(bytesUploaded));
77
+                args.putString("total", String.valueOf(contentLength()));
78
+                rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
79
+                        .emit("RNFetchBlobProgress", args);
80
+            }
69 81
             return read;
70 82
         }
71 83
 

+ 3
- 2
src/index.js Целия файл

@@ -121,8 +121,8 @@ function fetch(...args:any):Promise {
121 121
       }
122 122
     })
123 123
 
124
-    let subscription = emitter.addListener('RNFetchBlobProgress-upload', (e) => {
125
-      if(e.taskId === taskId && promise.onProgress) {
124
+    let subscriptionUpload = emitter.addListener('RNFetchBlobProgress-upload', (e) => {
125
+      if(e.taskId === taskId && promise.onUploadProgress) {
126 126
         promise.onUploadProgress(e.written, e.total)
127 127
       }
128 128
     })
@@ -132,6 +132,7 @@ function fetch(...args:any):Promise {
132 132
 
133 133
       // task done, remove event listener
134 134
       subscription.remove()
135
+      subscriptionUpload.remove()
135 136
       if(err)
136 137
         reject(new Error(err, data))
137 138
       else {

+ 1
- 0
test-server/.gitignore Целия файл

@@ -4,6 +4,7 @@ public/*
4 4
 !public/github2.jpg
5 5
 !public/22mb-dummy
6 6
 !public/1mb-dummy
7
+!public/chunk-dummy
7 8
 !public/beethoven.mp3
8 9
 !public/cat-fu.mp4
9 10
 !uploads/readme

+ 0
- 0
test-server/dummy-res Целия файл


BIN
test-server/dummy-res.png Целия файл


+ 1
- 0
test-server/public/chunk-dummy Целия файл

@@ -0,0 +1 @@
1
+12345678910abcdefg

+ 1
- 2
test-server/server.js Целия файл

@@ -40,10 +40,9 @@ app.listen(8123, function(err){
40 40
     console.log('test server running at port ',8123)
41 41
 })
42 42
 
43
-var count = 0
44 43
 
45 44
 app.use(function(req,res,next){
46
-  console.log(chalk.green('request url=') + chalk.magenta(req.url), ++count)
45
+  console.log(chalk.green('request url=') + chalk.magenta(req.url))
47 46
   next()
48 47
 })
49 48
 

+ 2
- 0
test/test-0.1.x-0.4.x.js Целия файл

@@ -66,7 +66,9 @@ describe('Upload multipart/form-data', (report, done) => {
66 66
       { name : 'field2', data : 'hello2 !!'}
67 67
     ])
68 68
   .then((resp) => {
69
+    console.log(resp.json())
69 70
     resp = resp.json()
71
+
70 72
     report(
71 73
       <Assert key="check posted form data #1" expect="hello !!" actual={resp.fields.field1}/>,
72 74
       <Assert key="check posted form data #2" expect="hello2 !!" actual={resp.fields.field2}/>,

+ 0
- 62
test/test-0.5.1.js Целия файл

@@ -185,68 +185,6 @@ describe('Upload and download at the same time', (report, done) => {
185 185
     })
186 186
 })
187 187
 
188
-RNTest.config({
189
-  group : '0.5.1',
190
-  run : true,
191
-  expand : false,
192
-  timeout : 600000,
193
-})('Upload and download large file', (report, done) => {
194
-  let filename = '22mb-dummy-' + Date.now()
195
-  let begin = -1
196
-  let begin2 = -1
197
-  let deb = Date.now()
198
-  RNFetchBlob.config({
199
-    fileCache : true
200
-  })
201
-  .fetch('GET', `${TEST_SERVER_URL}/public/22mb-dummy`)
202
-  .progress((now, total) => {
203
-    if(begin === -1)
204
-      begin = Date.now()
205
-    if(Date.now() - deb < 1000)
206
-      return
207
-    deb = Date.now()
208
-    report(<Info uid="200" key="progress">
209
-      <Text>
210
-        {`download ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s)`}
211
-      </Text>
212
-    </Info>)
213
-  })
214
-  .then((res) => {
215
-    try {
216
-    deb = Date.now()
217
-    let promise =  RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
218
-      Authorization : `Bearer ${DROPBOX_TOKEN}`,
219
-      'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+filename+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
220
-      'Content-Type' : 'application/octet-stream',
221
-    }, RNFetchBlob.wrap(res.path()))
222
-    if(Platform.OS === 'ios') {
223
-      promise.progress((now, total) => {
224
-        if(Date.now() - deb < 1000)
225
-          return
226
-        deb = Date.now()
227
-        if(begin2 === -1)
228
-          begin2 = Date.now()
229
-        let speed = Math.floor(now / (Date.now() - begin2))
230
-        report(<Info uid="100"  key="progress">
231
-          <Text>
232
-            {`upload ${now} / ${total} bytes (${speed} kb/s)`}
233
-            {` ${Math.floor((total-now)/speed/1000)} seconds left`}
234
-          </Text>
235
-        </Info>)
236
-      })
237
-    }
238
-    return promise
239
-  } catch(err) { console.log(err) }
240
-  })
241
-  .then((res) => {
242
-    report(<Assert
243
-      key="upload should success without crashing app"
244
-      expect={filename}
245
-      actual={res.json().name}/>)
246
-    done()
247
-  })
248
-})
249
-
250 188
 describe('Session create mechanism test', (report, done) => {
251 189
   let sessionName = 'foo-' + Date.now()
252 190
   testSessionName = sessionName

+ 1
- 1
test/test-0.5.2.js Целия файл

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

+ 63
- 5
test/test-0.6.3.js Целия файл

@@ -27,8 +27,8 @@ const  dirs = RNFetchBlob.fs.dirs
27 27
 
28 28
 let prefix = ((Platform.OS === 'android') ? 'file://' : '')
29 29
 
30
-describe('massive HTTP request', (report, done) => {
31
-  try {
30
+false && describe('massive HTTP request', (report, done) => {
31
+  return
32 32
   let promises = []
33 33
   let progress = []
34 34
   let begin = Date.now()
@@ -61,8 +61,66 @@ describe('massive HTTP request', (report, done) => {
61 61
     // Timer.clearInterval(it)
62 62
     done()
63 63
   })
64
-} catch(err) {
65
-  console.log(err)
66
-}
67 64
 
68 65
 })
66
+
67
+RNTest.config({
68
+  group : '0.7.0',
69
+  run : true,
70
+  expand : false,
71
+  timeout : 600000,
72
+})('Upload and download large file', (report, done) => {
73
+  let filename = '22mb-dummy-' + Date.now()
74
+  let begin = -1
75
+  let begin2 = -1
76
+  let deb = Date.now()
77
+  RNFetchBlob.config({
78
+    fileCache : true
79
+  })
80
+  .fetch('GET', `${TEST_SERVER_URL}/public/22mb-dummy`)
81
+  .progress((now, total) => {
82
+    if(begin === -1)
83
+      begin = Date.now()
84
+    if(Date.now() - deb < 1000)
85
+      return
86
+    deb = Date.now()
87
+    report(<Info uid="200" key="progress">
88
+      <Text>
89
+        {`download ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s)`}
90
+      </Text>
91
+    </Info>)
92
+  })
93
+  .then((res) => {
94
+    try {
95
+    deb = Date.now()
96
+    // let promise =  RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/raw`, {
97
+    let promise =  RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
98
+      Authorization : `Bearer ${DROPBOX_TOKEN}`,
99
+      'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+filename+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
100
+      'Content-Type' : 'application/octet-stream',
101
+    }, RNFetchBlob.wrap(res.path()))
102
+    promise.uploadProgress((now, total) => {
103
+      if(Date.now() - deb < 1000)
104
+        return
105
+      deb = Date.now()
106
+      if(begin2 === -1)
107
+        begin2 = Date.now()
108
+      let speed = Math.floor(now / (Date.now() - begin2))
109
+      report(<Info uid="100"  key="progress">
110
+        <Text>
111
+          {`upload ${now} / ${total} bytes (${speed} kb/s)`}
112
+          {` ${Math.floor((total-now)/speed/1000)} seconds left`}
113
+        </Text>
114
+      </Info>)
115
+    })
116
+    return promise
117
+  } catch(err) { console.log(err) }
118
+  })
119
+  .then((res) => {
120
+    report(<Assert
121
+      key="upload should success without crashing app"
122
+      expect={filename}
123
+      actual={res.json().name}/>)
124
+    done()
125
+  })
126
+})

+ 15
- 9
test/test-init.js Целия файл

@@ -13,7 +13,7 @@ import {
13 13
   Image,
14 14
 } from 'react-native';
15 15
 
16
-const { Assert, Comparer, Info, describe, prop } = RNTest
16
+const { Assert, Comparer, Info, prop } = RNTest
17 17
 
18 18
 // test environment variables
19 19
 
@@ -25,12 +25,18 @@ prop('styles', {
25 25
   image : {
26 26
     width: Dimensions.get('window').width*0.9,
27 27
     height : Dimensions.get('window').width*0.9,
28
-    margin :16
28
+    margin : 16
29 29
   }
30 30
 })
31 31
 
32 32
 const { TEST_SERVER_URL, FILENAME, DROPBOX_TOKEN, styles, image } = prop()
33 33
 
34
+const describe = RNTest.config({
35
+  run : true,
36
+  expand : false,
37
+  timeout : 5000,
38
+})
39
+
34 40
 // init
35 41
 
36 42
 describe('GET image from server', (report, done) => {
@@ -53,10 +59,10 @@ describe('GET image from server', (report, done) => {
53 59
 
54 60
 
55 61
 require('./test-0.1.x-0.4.x')
56
-require('./test-0.5.1')
57
-require('./test-0.5.2')
58
-require('./test-0.6.0')
59
-require('./test-0.6.2')
60
-require('./test-0.6.3')
61
-require('./test-fs')
62
-require('./test-android')
62
+// require('./test-0.5.1')
63
+// require('./test-0.5.2')
64
+// require('./test-0.6.0')
65
+// require('./test-0.6.2')
66
+// require('./test-0.6.3')
67
+// require('./test-fs')
68
+// require('./test-android')