Browse Source

Merge branch 'master' into 0.10.0

Ben Hsieh 8 years ago
parent
commit
4944e39f70

+ 2
- 2
README.md View File

@@ -38,9 +38,9 @@ A project committed to make file acess and data transfer easier, efficient for R
38 38
 
39 39
 ## About
40 40
 
41
-This project was initially for solving the issue [facebook/react-native#854](https://github.com/facebook/react-native/issues/854), because React Native lack of `Blob` implementation and it will cause some problem when transfering binary data. Now, this project is committed to make file access and transfer more easier, efficient for React Native developers. We've implemented highly customizable filesystem and network module which plays well together. For example, upload and download data directly from/to storage which is much more efficient in some cases(especially for large ones). The file system supports file stream, so you don't have to worry about OOM problem when accessing large files.
41
+This project was initially for solving the issue [facebook/react-native#854](https://github.com/facebook/react-native/issues/854), because React Native lack of `Blob` implementation and it will cause some problem when transferring binary data. Now, this project is committed to make file access and transfer more easier, efficient for React Native developers. We've implemented highly customizable filesystem and network module which plays well together. For example, upload and download data directly from/to storage which is much more efficient in some cases(especially for large ones). The file system supports file stream, so you don't have to worry about OOM problem when accessing large files.
42 42
 
43
-In `0.8.0` we introduced experimential Web API polyfills that make it possible to use browser-based libraries in React Native, for example, [FireBase JS SDK](https://github.com/wkh237/rn-firebase-storage-upload-sample)
43
+In `0.8.0` we introduced experimental Web API polyfills that make it possible to use browser-based libraries in React Native, such as, [FireBase JS SDK](https://github.com/wkh237/rn-firebase-storage-upload-sample)
44 44
 
45 45
 
46 46
 ## Installation

+ 1
- 2
package.json View File

@@ -1,7 +1,6 @@
1 1
 {
2 2
   "name": "fetchblob-dev",
3
-  "author": "wkh237 <xeiyan@gmail.com>",
4
-  "version": "0.9.2",
3
+  "version": "0.9.3",
5 4
   "private": true,
6 5
   "scripts": {
7 6
     "start": "node node_modules/react-native/local-cli/cli.js start",

+ 76
- 98
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

@@ -1,7 +1,6 @@
1 1
 package com.RNFetchBlob;
2 2
 
3 3
 import android.util.Base64;
4
-import android.util.Log;
5 4
 
6 5
 import com.facebook.react.bridge.Arguments;
7 6
 import com.facebook.react.bridge.ReactApplicationContext;
@@ -13,23 +12,14 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
13 12
 import java.io.ByteArrayInputStream;
14 13
 import java.io.File;
15 14
 import java.io.FileInputStream;
16
-import java.io.FileNotFoundException;
17 15
 import java.io.FileOutputStream;
18 16
 import java.io.IOException;
19 17
 import java.io.InputStream;
20
-import java.nio.ByteBuffer;
21
-import java.nio.MappedByteBuffer;
22 18
 import java.util.ArrayList;
23
-import java.util.HashMap;
24 19
 
25 20
 import okhttp3.MediaType;
26 21
 import okhttp3.RequestBody;
27
-import okhttp3.FormBody;
28
-import okio.Buffer;
29 22
 import okio.BufferedSink;
30
-import okio.ForwardingSink;
31
-import okio.Okio;
32
-import okio.Sink;
33 23
 
34 24
 /**
35 25
  * Created by wkh237 on 2016/7/11.
@@ -38,7 +28,6 @@ public class RNFetchBlobBody extends RequestBody{
38 28
 
39 29
     InputStream requestStream;
40 30
     long contentLength = 0;
41
-    long bytesWritten = 0;
42 31
     ReadableArray form;
43 32
     String mTaskId;
44 33
     String rawBody;
@@ -47,6 +36,13 @@ public class RNFetchBlobBody extends RequestBody{
47 36
     File bodyCache;
48 37
 
49 38
 
39
+    /**
40
+     * Single file or raw content request constructor
41
+     * @param taskId
42
+     * @param type
43
+     * @param form
44
+     * @param contentType
45
+     */
50 46
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
51 47
         this.mTaskId = taskId;
52 48
         this.form = form;
@@ -54,27 +50,53 @@ public class RNFetchBlobBody extends RequestBody{
54 50
         mime = contentType;
55 51
         try {
56 52
             bodyCache = createMultipartBodyCache();
53
+            requestStream = new FileInputStream(bodyCache);
57 54
             contentLength = bodyCache.length();
58
-        } catch (IOException e) {
59
-            e.printStackTrace();
55
+        } catch(Exception ex) {
56
+            ex.printStackTrace();
57
+            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create request multipart body :" + ex.getLocalizedMessage());
60 58
         }
61 59
     }
62 60
 
61
+    /**
62
+     * Multipart request constructor
63
+     * @param taskId
64
+     * @param type
65
+     * @param rawBody
66
+     * @param contentType
67
+     */
63 68
     public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
64 69
         this.mTaskId = taskId;
65 70
         requestType = type;
66 71
         this.rawBody = rawBody;
67 72
         mime = contentType;
68
-        if(rawBody != null) {
69
-            if(requestType == RNFetchBlobReq.RequestType.AsIs)
70
-                contentLength = rawBody.length();
71
-            else
72
-                contentLength = caculateOctetContentLength();
73
+        if(rawBody == null) {
74
+            this.rawBody = "";
75
+            requestType = RNFetchBlobReq.RequestType.AsIs;
76
+        }
77
+        try {
78
+            switch (requestType) {
79
+                case SingleFile:
80
+                    requestStream = getReuqestStream();
81
+                    contentLength = requestStream.available();
82
+                    break;
83
+                case AsIs:
84
+                    contentLength = this.rawBody.getBytes().length;
85
+                    break;
86
+                case Others:
87
+                    break;
88
+            }
89
+        } catch(Exception ex) {
90
+            ex.printStackTrace();
91
+            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create single content request body :" + ex.getLocalizedMessage() + "\r\n");
73 92
         }
93
+
74 94
     }
75 95
 
76
-    // ${RN 0.26+ ONLY} @Override
77
-    // ${RN 0.26+ ONLY} public long contentLength() { return contentLength; }
96
+    @Override
97
+    public long contentLength() {
98
+        return contentLength;
99
+    }
78 100
 
79 101
     @Override
80 102
     public MediaType contentType() {
@@ -82,23 +104,16 @@ public class RNFetchBlobBody extends RequestBody{
82 104
     }
83 105
 
84 106
     @Override
85
-    public void writeTo(BufferedSink sink) throws IOException {
86
-
87
-        ProgressReportingSource source = new ProgressReportingSource(sink, mTaskId);
88
-        BufferedSink buffer = Okio.buffer(source);
89
-        switch (requestType) {
90
-            case Form:
91
-                pipeStreamToSink(new FileInputStream(bodyCache), sink);
92
-                break;
93
-            case SingleFile:
94
-                if(requestStream != null)
95
-                    pipeStreamToSink(requestStream, sink);
96
-                break;
97
-            case AsIs:
98
-				writeRawData(sink);
99
-				break;
107
+    public void writeTo(BufferedSink sink) {
108
+        try {
109
+            if (requestType == RNFetchBlobReq.RequestType.AsIs)
110
+                sink.write(rawBody.getBytes());
111
+            else
112
+                pipeStreamToSink(requestStream, sink);
113
+        } catch(Exception ex) {
114
+            RNFetchBlobUtils.emitWarningEvent(ex.getLocalizedMessage());
115
+            ex.printStackTrace();
100 116
         }
101
-        buffer.flush();
102 117
     }
103 118
 
104 119
     boolean clearRequestBody() {
@@ -113,8 +128,8 @@ public class RNFetchBlobBody extends RequestBody{
113 128
         return true;
114 129
     }
115 130
 
116
-    private long caculateOctetContentLength() {
117
-        long total = 0;
131
+    private InputStream getReuqestStream() throws Exception {
132
+
118 133
         // upload from storage
119 134
         if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
120 135
             String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
@@ -123,32 +138,30 @@ public class RNFetchBlobBody extends RequestBody{
123 138
             if (RNFetchBlobFS.isAsset(orgPath)) {
124 139
                 try {
125 140
                     String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
126
-                    total += RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
127
-                    requestStream = RNFetchBlob.RCTContext.getAssets().open(assetName);
128
-                } catch (IOException e) {
129
-                    RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
141
+                    return RNFetchBlob.RCTContext.getAssets().open(assetName);
142
+                } catch (Exception e) {
143
+                    throw new Exception("error when getting request stream from asset : " +e.getLocalizedMessage());
130 144
                 }
131 145
             } else {
132 146
                 File f = new File(RNFetchBlobFS.normalizePath(orgPath));
133 147
                 try {
134 148
                     if(!f.exists())
135 149
                         f.createNewFile();
136
-                    total += f.length();
137
-                    requestStream = new FileInputStream(f);
150
+                    return new FileInputStream(f);
138 151
                 } catch (Exception e) {
139
-                    RNFetchBlobUtils.emitWarningEvent("RNetchBlob error when counting content length: " +e.getLocalizedMessage());
152
+                    throw new Exception("error when getting request stream: " +e.getLocalizedMessage());
140 153
                 }
141 154
             }
142
-        } else {
155
+        }
156
+        // base 64 encoded
157
+        else {
143 158
             try {
144 159
                 byte[] bytes = Base64.decode(rawBody, 0);
145
-                requestStream = new ByteArrayInputStream(bytes);
146
-                total += requestStream.available();
160
+                return  new ByteArrayInputStream(bytes);
147 161
             } catch(Exception ex) {
148
-                RNFetchBlobUtils.emitWarningEvent("RNetchBlob error when counting content length: " +ex.getLocalizedMessage());
162
+                throw new Exception("error when getting request stream: " + ex.getLocalizedMessage());
149 163
             }
150 164
         }
151
-        return total;
152 165
     }
153 166
 
154 167
     /**
@@ -191,7 +204,7 @@ public class RNFetchBlobBody extends RequestBody{
191 204
                             InputStream in = ctx.getAssets().open(assetName);
192 205
                             pipeStreamToFileStream(in, os);
193 206
                         } catch (IOException e) {
194
-                            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
207
+                            RNFetchBlobUtils.emitWarningEvent("Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
195 208
                         }
196 209
                     }
197 210
                     // data from normal files
@@ -202,7 +215,7 @@ public class RNFetchBlobBody extends RequestBody{
202 215
                             pipeStreamToFileStream(fs, os);
203 216
                         }
204 217
                         else {
205
-                            RNFetchBlobUtils.emitWarningEvent("RNFetchBlob Failed to create form data from path :" + orgPath + ", file not exists.");
218
+                            RNFetchBlobUtils.emitWarningEvent("Failed to create form data from path :" + orgPath + ", file not exists.");
206 219
                         }
207 220
                     }
208 221
                 }
@@ -210,8 +223,6 @@ public class RNFetchBlobBody extends RequestBody{
210 223
                 else {
211 224
                     byte[] b = Base64.decode(data, 0);
212 225
                     os.write(b);
213
-                    bytesWritten += b.length;
214
-                    emitUploadProgress();
215 226
                 }
216 227
 
217 228
             }
@@ -221,7 +232,6 @@ public class RNFetchBlobBody extends RequestBody{
221 232
                 header += "Content-Type: " + field.mime + "\r\n\r\n";
222 233
                 os.write(header.getBytes());
223 234
                 byte[] fieldData = field.data.getBytes();
224
-                bytesWritten += fieldData.length;
225 235
                 os.write(fieldData);
226 236
             }
227 237
             // form end
@@ -235,28 +245,22 @@ public class RNFetchBlobBody extends RequestBody{
235 245
         return outputFile;
236 246
     }
237 247
 
238
-	/**
239
-     * Write data to request body as-is
240
-     * @param sink
241
-     */
242
-	private void writeRawData(BufferedSink sink) throws IOException {
243
-        byte[] bytes = rawBody.getBytes();
244
-        contentLength = bytes.length;
245
-		sink.write(bytes);
246
-	}
247
-
248 248
     /**
249 249
      * Pipe input stream to request body output stream
250 250
      * @param stream    The input stream
251 251
      * @param sink      The request body buffer sink
252 252
      * @throws IOException
253 253
      */
254
-    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
254
+    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws Exception {
255
+
255 256
         byte [] chunk = new byte[10240];
257
+        int totalWritten = 0;
256 258
         int read;
257 259
         while((read = stream.read(chunk, 0, 10240)) > 0) {
258 260
             if(read > 0) {
259 261
                 sink.write(chunk, 0, read);
262
+                totalWritten += read;
263
+                emitUploadProgress(totalWritten);
260 264
             }
261 265
         }
262 266
         stream.close();
@@ -355,10 +359,14 @@ public class RNFetchBlobBody extends RequestBody{
355 359
         }
356 360
     }
357 361
 
358
-    private void emitUploadProgress() {
362
+    /**
363
+     * Emit progress event
364
+     * @param written
365
+     */
366
+    private void emitUploadProgress(int written) {
359 367
         WritableMap args = Arguments.createMap();
360 368
         args.putString("taskId", mTaskId);
361
-        args.putString("written", String.valueOf(bytesWritten));
369
+        args.putString("written", String.valueOf(written));
362 370
         args.putString("total", String.valueOf(contentLength));
363 371
 
364 372
         // emit event to js context
@@ -366,34 +374,4 @@ public class RNFetchBlobBody extends RequestBody{
366 374
                 .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
367 375
     }
368 376
 
369
-    private final class ProgressReportingSource extends ForwardingSink {
370
-
371
-        private long bytesWritten = 0;
372
-        private String mTaskId;
373
-        private Sink delegate;
374
-
375
-        public ProgressReportingSource (Sink delegate, String taskId) {
376
-            super(delegate);
377
-            this.mTaskId = taskId;
378
-            this.delegate = delegate;
379
-        }
380
-
381
-        @Override
382
-        public void write(Buffer source, long byteCount) throws IOException {
383
-            delegate.write(source, byteCount);
384
-            // on progress, emit RNFetchBlobProgress upload progress event with ticketId,
385
-            // bytesWritten, and totalSize
386
-            bytesWritten += byteCount;
387
-            WritableMap args = Arguments.createMap();
388
-            args.putString("taskId", mTaskId);
389
-            args.putString("written", String.valueOf(bytesWritten));
390
-            args.putString("total", String.valueOf(contentLength));
391
-
392
-            if(RNFetchBlobReq.isReportUploadProgress(mTaskId)) {
393
-                // emit event to js context
394
-                RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
395
-                        .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
396
-            }
397
-        }
398
-    }
399 377
 }

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

@@ -332,7 +332,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
332 332
             clientBuilder.retryOnConnectionFailure(false);
333 333
             clientBuilder.followRedirects(true);
334 334
 
335
-            OkHttpClient client = clientBuilder.build();
335
+            OkHttpClient client = clientBuilder.retryOnConnectionFailure(true).build();
336 336
             Call call =  client.newCall(req);
337 337
             taskTable.put(taskId, call);
338 338
             call.enqueue(new okhttp3.Callback() {

+ 8
- 0
src/class/StatefulPromise.js View File

@@ -0,0 +1,8 @@
1
+// Copyright 2016 wkh237@github. All rights reserved.
2
+// Use of this source code is governed by a MIT-style license that can be
3
+// found in the LICENSE file.
4
+// @flow
5
+
6
+export default class StatefulPromise extends Promise {
7
+
8
+}

+ 8
- 0
src/index.js View File

@@ -16,6 +16,7 @@ import type {
16 16
   RNFetchBlobStream,
17 17
   RNFetchBlobResponseInfo
18 18
 } from './types'
19
+import StatefulPromise from './class/StatefulPromise.js'
19 20
 import fs from './fs'
20 21
 import getUUID from './utils/uuid'
21 22
 import base64 from 'base-64'
@@ -167,6 +168,13 @@ function fetch(...args:any):Promise {
167 168
       subscription.remove()
168 169
       subscriptionUpload.remove()
169 170
       stateEvent.remove()
171
+      delete promise['progress']
172
+      delete promise['uploadProgress']
173
+      delete promise['stateChange']
174
+      delete promise['cancel']
175
+      promise.cancel = () => {
176
+        console.warn('finished request could not be canceled')
177
+      }
170 178
 
171 179
       if(err)
172 180
         reject(new Error(err, respInfo))

+ 0
- 15
src/ios/RNFetchBlobNetwork.m View File

@@ -408,21 +408,6 @@ NSOperationQueue *taskQueue;
408 408
         [task cancel];
409 409
 }
410 410
 
411
-//- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
412
-//
413
-//}
414
-
415
-//- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
416
-//{
417
-//    if(self.dataTaskCompletionHandler != nil)
418
-//    {
419
-//        dataTaskCompletionHandler(self.respData, nil, error);
420
-//    }
421
-//    else if(self.fileTaskCompletionHandler != nil)
422
-//    {
423
-//        fileTaskCompletionHandler(nil, nil, self.error);
424
-//    }
425
-//}
426 411
 
427 412
 - (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
428 413
 {

+ 1
- 1
src/package.json View File

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

+ 1
- 1
src/types.js View File

@@ -47,7 +47,7 @@ type RNFetchBlobResponseInfo = {
47 47
   state : number,
48 48
   headers : any,
49 49
   status : number,
50
-  respType : 'text' | 'blob' | '' | 'json'
50
+  respType : 'text' | 'blob' | '' | 'json',
51 51
   rnfbEncode : 'path' | 'base64' | 'ascii' | 'utf8'
52 52
 }
53 53
 

BIN
test-server/public/img-large-dummy.JPG View File


+ 27
- 1
test/test-0.7.0.js View File

@@ -33,11 +33,13 @@ describe('Upload and download large file', (report, done) => {
33 33
   let begin = -1
34 34
   let begin2 = -1
35 35
   let deb = Date.now()
36
+  let download = false, upload = false
36 37
   RNFetchBlob.config({
37 38
     fileCache : true
38 39
   })
39
-  .fetch('GET', `${TEST_SERVER_URL}/public/1mb-dummy`)
40
+  .fetch('GET', `${TEST_SERVER_URL}/public/2mb-dummy`)
40 41
   .progress((now, total) => {
42
+    download = true
41 43
     if(begin === -1)
42 44
       begin = Date.now()
43 45
     if(Date.now() - deb < 1000)
@@ -57,6 +59,29 @@ describe('Upload and download large file', (report, done) => {
57 59
     report(<Info key="big file stat">
58 60
       <Text>{JSON.stringify(stat)}</Text>
59 61
     </Info>)
62
+    let task = RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
63
+      Authorization : `Bearer ${DROPBOX_TOKEN}`,
64
+      'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+filename+Date.now()+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
65
+      'Content-Type' : 'application/octet-stream',
66
+    }, RNFetchBlob.wrap(bigfile))
67
+    begin = -1
68
+    task.uploadProgress((now, total) => {
69
+      upload = true
70
+      if(begin === -1)
71
+        begin = Date.now()
72
+      if(Date.now() - deb < 1000)
73
+        return
74
+      deb = Date.now()
75
+      report(<Info uid="300" key="upload progress">
76
+        <Text>
77
+          {`upload ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s) ${(100*now/total).toFixed(2)}%`}
78
+        </Text>
79
+      </Info>)
80
+    })
81
+    return task
82
+  })
83
+  .then(() => {
84
+    report(<Assert key="upload and download event triggered" expect={true} actual={download && upload}/>)
60 85
     done()
61 86
   })
62 87
 })
@@ -87,6 +112,7 @@ describe('cancel task should work properly', (report, done) => {
87 112
       </Text>
88 113
     </Info>)
89 114
   })
115
+
90 116
   let checkpoint1 = 0
91 117
   Timer.setTimeout(() => {
92 118
     task.cancel()