diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
index edadaa9b6..b03e3e8e3 100644
--- a/.github/PULL_REQUEST_TEMPLATE
+++ b/.github/PULL_REQUEST_TEMPLATE
@@ -1,5 +1,5 @@
Thank you for making a pull request ! Just a gentle reminder :)
1. If the PR is offering a feature please make the request to our "Feature Branch" 0.11.0
-2. Bug fix request to "Bug Fix Branch" 0.10.7
+2. Bug fix request to "Bug Fix Branch" 0.10.9
3. Correct README.md can directly to master
diff --git a/README.md b/README.md
index b315848b9..3c7d24a59 100644
--- a/README.md
+++ b/README.md
@@ -26,13 +26,13 @@ A project committed to making file access and data transfer easier and more effi
* [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
* [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
* [Transfer Encoding](#user-content-transfer-encoding)
- * [RNFetchBlob as Fetch](#user-content-rnfetchblob-as-fetch)
+ * [Drop-in Fetch Replacement](#user-content-drop-in-fetch-replacement)
* [File System](#user-content-file-system)
* [File access](#user-content-file-access)
* [File stream](#user-content-file-stream)
* [Manage cached files](#user-content-cache-file-management)
* [Web API Polyfills](#user-content-web-api-polyfills)
-* [Performance Tips](#user-content-performance-tipsd)
+* [Performance Tips](#user-content-performance-tips)
* [API References](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API)
* [Caveats](#user-content-caveats)
* [Development](#user-content-development)
@@ -236,7 +236,7 @@ RNFetchBlob
console.log('The file saved to ', res.path())
// Beware that when using a file path as Image source on Android,
// you must prepend "file://"" before the file path
- imageView =
+ imageView =
})
```
@@ -452,11 +452,11 @@ task.cancel((err) => { ... })
```
-### RNFetchBlob as Fetch
+### Drop-in Fetch Replacement
0.9.0
-If you have existing code that uses `whatwg-fetch`(the official **fetch**), you don't have to change them after 0.9.0, just use fetch replacement. The difference between Official fetch and fetch replacement is, official fetch uses [whatwg-fetch](https://github.com/github/fetch) js library which wraps XMLHttpRequest polyfill under the hood it's a great library for web developers, however that does not play very well with RN. Our implementation is simply a wrapper of RNFetchBlob.fetch and fs APIs, so you can access all the features we provide.
+If you have existing code that uses `whatwg-fetch`(the official **fetch**), it's not necessary to replace them with `RNFetchblob.fetch`, you can simply use our **Fetch Replacement**. The difference between Official them is official fetch uses [whatwg-fetch](https://github.com/github/fetch) which wraps XMLHttpRequest polyfill under the hood. It's a great library for web developers, but does not play very well with RN. Our implementation is simply a wrapper of our `fetch` and `fs` APIs, so you can access all the features we provided.
[See document and examples](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetch-replacement)
@@ -613,6 +613,8 @@ In `v0.5.0` we've added `writeStream` and `readStream`, which allows your app r
When calling `readStream` method, you have to `open` the stream, and start to read data. When the file is large, consider using an appropriate `bufferSize` and `interval` to reduce the native event dispatching overhead (see [Performance Tips](#user-content-performance-tips))
+> The file stream event has a default throttle(10ms) and buffer size which preventing it cause too much overhead to main thread, yo can also [tweak these values](#user-content-performance-tips).
+
```js
let data = ''
RNFetchBlob.fs.readStream(
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 807904417..9f823199f 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,9 +1,3 @@
-
-
-
-
-
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
index 8146a99cf..7eb1717d3 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
@@ -1,5 +1,6 @@
package com.RNFetchBlob;
+import android.net.Uri;
import android.util.Base64;
import com.facebook.react.bridge.Arguments;
@@ -21,8 +22,13 @@
import okhttp3.RequestBody;
import okio.BufferedSink;
+import static com.RNFetchBlob.RNFetchBlobConst.CONTENT_PREFIX;
+
public class RNFetchBlobBody extends RequestBody{
+ private static final int BYTE_SKIP_LENGTH = 1024 * 1024; //1024 * 1024 = 1MB
+
+
InputStream requestStream;
long contentLength = 0;
ReadableArray form;
@@ -160,6 +166,14 @@ private InputStream getReuqestStream() throws Exception {
}
}
}
+ else if (rawBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
+ try {
+ String contentUri = rawBody.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
+ return RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(contentUri));
+ } catch (IOException e) {
+ throw new Exception("error when getting request stream from content uri: " +e.getLocalizedMessage());
+ }
+ }
// base 64 encoded
else {
try {
@@ -226,6 +240,16 @@ private File createMultipartBodyCache() throws IOException {
}
}
}
+ else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
+ try {
+ String contentUri = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
+ InputStream inputStream = ctx.getContentResolver().openInputStream(Uri.parse(contentUri));
+
+ pipeStreamToFileStream(inputStream, os);
+ } catch (IOException e) {
+ RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
+ }
+ }
// base64 embedded file content
else {
byte[] b = Base64.decode(data, 0);
@@ -324,6 +348,14 @@ else if (field.filename != null) {
File file = new File(RNFetchBlobFS.normalizePath(orgPath));
total += file.length();
}
+ } else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
+ try {
+ String contentUri = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
+ long length = ctx.getContentResolver().openInputStream(Uri.parse(contentUri)).available();
+ total += length;
+ } catch (IOException e) {
+ RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
+ }
}
// base64 embedded file content
else {
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java
index 015cc8954..fe2424432 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java
@@ -14,5 +14,6 @@ public class RNFetchBlobConst {
public static final String RNFB_RESPONSE_UTF8 = "utf8";
public static final String RNFB_RESPONSE_PATH = "path";
public static final Integer GET_CONTENT_INTENT = 99900;
+ public static final String CONTENT_PREFIX = "RNFetchBlob-content://";
}
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
index 1537bca71..7a7910546 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
@@ -138,11 +138,13 @@ static public void writeFile(String path, ReadableArray data, final boolean appe
* @param promise
*/
static public void readFile(String path, String encoding, final Promise promise ) {
- path = normalizePath(path);
+ String resolved = normalizePath(path);
+ if(resolved != null)
+ path = resolved;
try {
byte[] bytes;
- if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
+ if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
bytes = new byte[(int) length];
@@ -150,6 +152,14 @@ static public void readFile(String path, String encoding, final Promise promise
in.read(bytes, 0, (int) length);
in.close();
}
+ // issue 287
+ else if(resolved == null) {
+ InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
+ int length = (int) in.available();
+ bytes = new byte[length];
+ in.read(bytes);
+ in.close();
+ }
else {
File f = new File(path);
int length = (int) f.length();
@@ -226,7 +236,9 @@ static public String getTmpPath(ReactApplicationContext ctx, String taskId) {
* @param bufferSize Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
*/
public void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
- path = normalizePath(path);
+ String resolved = normalizePath(path);
+ if(resolved != null)
+ path = resolved;
try {
int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
@@ -234,9 +246,14 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f
chunkSize = bufferSize;
InputStream fs;
- if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
- fs = RNFetchBlob.RCTContext.getAssets()
- .open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
+
+ if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
+ fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));
+
+ }
+ // fix issue 287
+ else if(resolved == null) {
+ fs = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
}
else {
fs = new FileInputStream(new File(path));
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java
index 74e0224a7..48aac7ac3 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java
@@ -20,7 +20,6 @@ public List createNativeModules(ReactApplicationContext reactConte
return modules;
}
- @Override
public List> createJSModules() {
return Collections.emptyList();
}
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
index db213c1e8..8a81a832e 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
@@ -7,10 +7,12 @@
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Build;
import android.util.Base64;
import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
import com.RNFetchBlob.Response.RNFetchBlobFileResp;
+import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
@@ -21,6 +23,7 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.network.OkHttpClientProvider;
+import com.facebook.react.modules.network.TLSSocketFactory;
import java.io.File;
import java.io.FileOutputStream;
@@ -35,11 +38,14 @@
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
+import java.util.List;
import java.util.HashMap;
+
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.ConnectionPool;
+import okhttp3.ConnectionSpec;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
@@ -48,6 +54,8 @@
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
+import okhttp3.TlsVersion;
+
public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
@@ -148,8 +156,15 @@ public void run() {
if(options.addAndroidDownloads.hasKey("path")) {
req.setDestinationUri(Uri.parse("file://" + options.addAndroidDownloads.getString("path")));
}
+ // #391 Add MIME type to the request
+ if(options.addAndroidDownloads.hasKey("mime")) {
+ req.setMimeType(options.addAndroidDownloads.getString("mime"));
+ }
// set headers
ReadableMapKeySetIterator it = headers.keySetIterator();
+ if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable") == true ) {
+ req.allowScanningByMediaScanner();
+ }
while (it.hasNextKey()) {
String key = it.nextKey();
req.addRequestHeader(key, headers.getString(key));
@@ -359,9 +374,10 @@ public Response intercept(Chain chain) throws IOException {
clientBuilder.retryOnConnectionFailure(false);
clientBuilder.followRedirects(options.followRedirect);
clientBuilder.followSslRedirects(options.followRedirect);
+ clientBuilder.retryOnConnectionFailure(true);
+ OkHttpClient client = enableTls12OnPreLollipop(clientBuilder).build();
- OkHttpClient client = clientBuilder.retryOnConnectionFailure(true).build();
Call call = client.newCall(req);
taskTable.put(taskId, call);
call.enqueue(new okhttp3.Callback() {
@@ -636,16 +652,20 @@ public void onReceive(Context context, Intent intent) {
return;
}
String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
- if (contentUri != null) {
+ if ( contentUri != null &&
+ options.addAndroidDownloads.hasKey("mime") &&
+ options.addAndroidDownloads.getString("mime").contains("image")) {
Uri uri = Uri.parse(contentUri);
Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
- // use default destination of DownloadManager
+
+ // use default destination of DownloadManager
if (cursor != null) {
cursor.moveToFirst();
filePath = cursor.getString(0);
}
}
}
+
// When the file is not found in media content database, check if custom path exists
if (options.addAndroidDownloads.hasKey("path")) {
try {
@@ -672,5 +692,28 @@ public void onReceive(Context context, Intent intent) {
}
}
+ public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+ try {
+ client.sslSocketFactory(new TLSSocketFactory());
+
+ ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+ .tlsVersions(TlsVersion.TLS_1_2)
+ .build();
+
+ List< ConnectionSpec > specs = new ArrayList < > ();
+ specs.add(cs);
+ specs.add(ConnectionSpec.COMPATIBLE_TLS);
+ specs.add(ConnectionSpec.CLEARTEXT);
+
+ client.connectionSpecs(specs);
+ } catch (Exception exc) {
+ FLog.e("OkHttpClientProvider", "Error while enabling TLS 1.2", exc);
+ }
+ }
+
+ return client;
+ }
+
}
diff --git a/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java b/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
index 381a3f9f3..fef3ada97 100644
--- a/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
+++ b/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
@@ -64,8 +64,16 @@ else if (isMediaDocument(uri)) {
return getDataColumn(context, contentUri, selection, selectionArgs);
}
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+
+ // Return the remote address
+ if (isGooglePhotosUri(uri))
+ return uri.getLastPathSegment();
+
+ return getDataColumn(context, uri, null, null);
+ }
// Other Providers
- else {
+ else{
try {
InputStream attachment = context.getContentResolver().openInputStream(uri);
if (attachment != null) {
@@ -131,6 +139,7 @@ public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
+ String result = null;
final String column = "_data";
final String[] projection = {
column
@@ -141,13 +150,18 @@ public static String getDataColumn(Context context, Uri uri, String selection,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(index);
+ result = cursor.getString(index);
}
- } finally {
+ }
+ catch (Exception ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ finally {
if (cursor != null)
cursor.close();
}
- return null;
+ return result;
}
diff --git a/index.js b/index.js
index c8ed1a9f6..2daccb2de 100644
--- a/index.js
+++ b/index.js
@@ -79,7 +79,11 @@ if(!RNFetchBlob || !RNFetchBlob.fetchBlobForm || !RNFetchBlob.fetchBlob) {
}
function wrap(path:string):string {
- return 'RNFetchBlob-file://' + path
+ if (path.startsWith('content://')) {
+ return 'RNFetchBlob-content://' + path
+ } else {
+ return 'RNFetchBlob-file://' + path
+ }
}
/**
diff --git a/ios.js b/ios.js
index 566b424e2..340ef04cf 100644
--- a/ios.js
+++ b/ios.js
@@ -43,7 +43,7 @@ function openDocument(path:string, scheme:string) {
* @param {string} url URL of the resource, only file URL is supported
* @return {Promise}
*/
-function excludeFromBackupKey(url:string) {
+function excludeFromBackupKey(path:string) {
return RNFetchBlob.excludeFromBackupKey('file://' + path);
}
diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m
index 9d4e00b0d..5e102d184 100644
--- a/ios/RNFetchBlobFS.m
+++ b/ios/RNFetchBlobFS.m
@@ -568,11 +568,11 @@ - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)enco
// Write file chunk into an opened stream
- (void)writeEncodeChunk:(NSString *) chunk {
- NSMutableData * decodedData = [NSData alloc];
+ NSData * decodedData = nil;
if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
- decodedData = [[NSData alloc] initWithBase64EncodedData:chunk options:0];
- }
- if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
+ decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters];
+ }
+ else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
}
else if([[self.encoding lowercaseString] isEqualToString:@"ascii"]) {
@@ -793,4 +793,4 @@ + (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest
return;
}
-@end
+@end
\ No newline at end of file
diff --git a/package.json b/package.json
index a4524df60..a93dba81d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-fetch-blob",
- "version": "0.10.6",
+ "version": "0.10.8",
"description": "A module provides upload, download, and files access API. Supports file stream read/write for process large files.",
"main": "index.js",
"scripts": {
@@ -8,7 +8,7 @@
},
"dependencies": {
"base-64": "0.1.0",
- "glob": "^7.0.6"
+ "glob": "7.0.6"
},
"keywords": [
"react-native",
@@ -35,4 +35,4 @@
"Ben ",
""
]
-}
\ No newline at end of file
+}
diff --git a/polyfill/Blob.js b/polyfill/Blob.js
index 384ae8fd9..53662a798 100644
--- a/polyfill/Blob.js
+++ b/polyfill/Blob.js
@@ -130,6 +130,8 @@ export default class Blob extends EventTarget {
// Blob data from file path
else if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) {
log.verbose('create Blob cache file from file path', data)
+ // set this flag so that we know this blob is a wrapper of an existing file
+ this._isReference = true
this._ref = String(data).replace('RNFetchBlob-file://', '')
let orgPath = this._ref
if(defer)
@@ -282,6 +284,20 @@ export default class Blob extends EventTarget {
})
}
+ safeClose() {
+ if(this._closed)
+ return Promise.reject('Blob has been released.')
+ this._closed = true
+ if(!this._isReference) {
+ return fs.unlink(this._ref).catch((err) => {
+ console.warn(err)
+ })
+ }
+ else {
+ return Promise.resolve()
+ }
+ }
+
_invokeOnCreateEvent() {
log.verbose('invoke create event', this._onCreated)
this._blobCreated = true
diff --git a/polyfill/XMLHttpRequest.js b/polyfill/XMLHttpRequest.js
index 89171921f..42c987704 100644
--- a/polyfill/XMLHttpRequest.js
+++ b/polyfill/XMLHttpRequest.js
@@ -277,7 +277,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
_headerReceived = (e) => {
log.debug('header received ', this._task.taskId, e)
this.responseURL = this._url
- if(e.state === "2") {
+ if(e.state === "2" && e.taskId === this._task.taskId) {
this._responseHeaders = e.headers
this._statusText = e.status
this._status = Math.floor(e.status)