From 82b55a918370cbe688028c98594e0ba850585769 Mon Sep 17 00:00:00 2001 From: wwm Date: Wed, 26 Apr 2017 14:58:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0ios=E4=B8=8B=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E9=98=BF=E9=87=8C=E4=BA=91=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/RNFetchBlobReqBuilder.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ios/RNFetchBlobReqBuilder.m b/ios/RNFetchBlobReqBuilder.m index 0123ad6a8..38504389f 100644 --- a/ios/RNFetchBlobReqBuilder.m +++ b/ios/RNFetchBlobReqBuilder.m @@ -69,7 +69,7 @@ +(void) buildMultipartRequest:(NSDictionary *)options [mheaders setValue:[NSString stringWithFormat:@"%lu",[postData length]] forKey:@"Content-Length"]; [mheaders setValue:@"100-continue" forKey:@"Expect"]; // appaned boundary to content-type - [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"]; + [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forKey:@"content-type"]; [request setHTTPMethod: method]; [request setAllHTTPHeaderFields:mheaders]; onComplete(request, [formData length]); @@ -205,8 +205,8 @@ void __block (^getFieldData)(id field) = ^(id field) // field is a text field if([field valueForKey:@"filename"] == nil || content == nil) { [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]]; - [formData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; + [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]]; + // [formData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]]; } // field contains a file @@ -227,9 +227,13 @@ void __block (^getFieldData)(id field) = ^(id field) return; } NSString * filename = [field valueForKey:@"filename"]; + // NSString *fileType=[field valueForKey:@"type"]; [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]]; - [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; + [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; + [formData appendData:[[NSString stringWithFormat:@"Content-Length: %ld\r\n\r\n", (long)[content length]] dataUsingEncoding:NSUTF8StringEncoding]]; + + [formData appendData:content]; [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; i++; From 5bf78becc70d5920280cdaf06f3a3cc5c4645949 Mon Sep 17 00:00:00 2001 From: windwang Date: Sat, 22 Jul 2017 15:27:33 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 7 ++ CONTRIBUTORS.md | 18 +++ README.md | 4 +- android.js | 20 ++- android/build.gradle | 4 +- .../java/com/RNFetchBlob/RNFetchBlob.java | 116 +++++++++++++----- .../com/RNFetchBlob/RNFetchBlobConst.java | 1 + .../java/com/RNFetchBlob/RNFetchBlobFS.java | 23 ++-- .../java/com/RNFetchBlob/RNFetchBlobReq.java | 43 ++++--- .../com/RNFetchBlob/RNFetchBlobUtils.java | 4 +- .../Response/RNFetchBlobFileResp.java | 1 - .../com/RNFetchBlob/Utils/PathResolver.java | 40 ++++++ fs.js | 12 +- index.js | 3 - ios/RNFetchBlob/RNFetchBlob.m | 35 +++--- ios/RNFetchBlobFS.m | 9 +- ios/RNFetchBlobNetwork.h | 1 - ios/RNFetchBlobNetwork.m | 72 ++--------- ios/RNFetchBlobReqBuilder.m | 13 +- package.json | 3 +- polyfill/Fetch.js | 42 ++++--- polyfill/FileReader.js | 10 +- polyfill/XMLHttpRequest.js | 20 +-- react-native-fetch-blob.podspec | 4 +- utils/uuid.js | 6 +- 25 files changed, 304 insertions(+), 207 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fb71468eb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +For developers who interested in making contribution to this project, please see [https://github.com/wkh237/react-native-fetch-blob-dev](https://github.com/wkh237/react-native-fetch-blob-dev) for more information. + +Please read the following rules before opening a PR : + +1. If the PR is offering a feature please make the PR to our "Feature Branch" 0.11.0 +2. Bug fix request to "Bug Fix Branch" 0.10.6 +3. Correct README.md can directly to master diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 63c11724f..5fa3a88c4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,7 +1,12 @@ 960px +Amerrnath Andreas Amsenius +Andrew Jack Arthur Ouaki +Ben +Ben Hsieh Binur Konarbai +Bronco Chris Sloey Corentin Smith Dmitry Petukhov @@ -9,16 +14,29 @@ Dombi Soma Kristóf Erik Smartt Evgeniy Baraniuk Frank van der Hoek +Guy Blank +Jacob Lauritzen +Jeremi Stadler +Jon San Miguel Juan B. Rodriguez Kaishley Martin Giachetti +Max Gurela Mike Monteith Naoki AINOYA Nguyen Cao Nhat Linh +Nick Pomfret +Oliver Petter Hesselberg +Reza Ghorbani +Simón Gómez +Steve Liles Tim Suchanek +Yonsh Lin +atlanteh follower francisco-sanchez-molina +gferreyra91 hhravn kejinliang pedramsaleh diff --git a/README.md b/README.md index bb920cd63..b315848b9 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ RNFetchBlob **Use Specific File Path** -If you prefer a particular file path rather than randomly generated one, you can use `path` option. We've added [several constants](#user-content-dirs) in v0.5.0 which represents commonly used directories. +If you prefer a particular file path rather than randomly generated one, you can use `path` option. We've added [several constants](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs) in v0.5.0 which represents commonly used directories. ```js let dirs = RNFetchBlob.fs.dirs @@ -771,7 +771,7 @@ If you're going to concatenate files, you don't have to read the data to JS cont ## Caveats * This library does not urlencode unicode characters in URL automatically, see [#146](https://github.com/wkh237/react-native-fetch-blob/issues/146). -* When a `Blob` , from existing file, the file **WILL BE REMOVE** if you `close` the blob. +* When you create a `Blob` , from an existing file, the file **WILL BE REMOVED** if you `close` the blob. * If you replaced `window.XMLHttpRequest` for some reason (e.g. make Firebase SDK work), it will also affect how official `fetch` works (basically it should work just fine). * When file stream and upload/download progress event slow down your app, consider an upgrade to `0.9.6+`, use [additional arguments](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetchprogressconfig-eventlistenerpromisernfetchblobresponse) to limit its frequency. * When passing a file path to the library, remove `file://` prefix. diff --git a/android.js b/android.js index 95f72f985..0b5dbd84b 100644 --- a/android.js +++ b/android.js @@ -21,10 +21,26 @@ function actionViewIntent(path:string, mime:string = 'text/plain') { if(Platform.OS === 'android') return RNFetchBlob.actionViewIntent(path, mime) else - return Promise.reject('RNFetchBlob.actionViewIntent only supports Android.') + return Promise.reject('RNFetchBlob.android.actionViewIntent only supports Android.') +} + +function getContentIntent(mime:string) { + if(Platform.OS === 'android') + return RNFetchBlob.getContentIntent(mime) + else + return Promise.reject('RNFetchBlob.android.getContentIntent only supports Android.') +} + +function addCompleteDownload(config) { + if(Platform.OS === 'android') + return RNFetchBlob.addCompleteDownload(config) + else + return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.') } export default { - actionViewIntent + actionViewIntent, + getContentIntent, + addCompleteDownload } diff --git a/android/build.gradle b/android/build.gradle index 6a477b8b2..919e64ac8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,10 +6,10 @@ repositories { buildscript { repositories { - mavenCentral() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 00631c619..19e1be435 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -1,9 +1,11 @@ package com.RNFetchBlob; +import android.app.Activity; +import android.app.DownloadManager; import android.content.Intent; import android.net.Uri; -import com.RNFetchBlob.Utils.RNFBCookieJar; +import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; @@ -12,28 +14,64 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableArray; + +// Cookies import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.network.ForwardingCookieHandler; +import com.facebook.react.modules.network.CookieJarContainer; +import com.facebook.react.modules.network.OkHttpClientProvider; +import okhttp3.OkHttpClient; +import okhttp3.JavaNetCookieJar; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import static android.app.Activity.RESULT_OK; +import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT; + public class RNFetchBlob extends ReactContextBaseJavaModule { + // Cookies + private final ForwardingCookieHandler mCookieHandler; + private final CookieJarContainer mCookieJarContainer; + private final OkHttpClient mClient; + static ReactApplicationContext RCTContext; static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); static LinkedBlockingQueue fsTaskQueue = new LinkedBlockingQueue<>(); static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); static public boolean ActionViewVisible = false; + static HashMap promiseTable = new HashMap<>(); public RNFetchBlob(ReactApplicationContext reactContext) { super(reactContext); + mClient = OkHttpClientProvider.getOkHttpClient(); + mCookieHandler = new ForwardingCookieHandler(reactContext); + mCookieJarContainer = (CookieJarContainer) mClient.cookieJar(); + mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler)); + RCTContext = reactContext; + reactContext.addActivityEventListener(new ActivityEventListener() { + @Override + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + if(requestCode == GET_CONTENT_INTENT && resultCode == RESULT_OK) { + Uri d = data.getData(); + promiseTable.get(GET_CONTENT_INTENT).resolve(d.toString()); + promiseTable.remove(GET_CONTENT_INTENT); + } + } + + @Override + public void onNewIntent(Intent intent) { + + } + }); } @Override @@ -228,35 +266,6 @@ public void run() { } - @ReactMethod - /** - * Get cookies belongs specific host. - * @param host String domain name. - */ - public void getCookies(String domain, Promise promise) { - try { - WritableMap cookies = RNFBCookieJar.getCookies(domain); - promise.resolve(cookies); - } catch(Exception err) { - promise.reject("RNFetchBlob.getCookies", err.getMessage()); - } - } - - @ReactMethod - /** - * Remove cookies for specific domain - * @param domain String of the domain - * @param promise JSC promise injected by RN - */ - public void removeCookies(String domain, Promise promise) { - try { - RNFBCookieJar.removeCookies(domain); - promise.resolve(null); - } catch(Exception err) { - promise.reject("RNFetchBlob.removeCookies", err.getMessage()); - } - } - @ReactMethod /** * @param path Stream file path @@ -314,12 +323,51 @@ public void enableUploadProgressReport(String taskId, int interval, int count) { @ReactMethod public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) { - new RNFetchBlobReq(options, taskId, method, url, headers, body, null, callback).run(); - } + new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run(); +} @ReactMethod public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) { - new RNFetchBlobReq(options, taskId, method, url, headers, null, body, callback).run(); + new RNFetchBlobReq(options, taskId, method, url, headers, null, body, mClient, callback).run(); + } + + @ReactMethod + public void getContentIntent(String mime, Promise promise) { + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + if(mime != null) + i.setType(mime); + else + i.setType("*/*"); + promiseTable.put(GET_CONTENT_INTENT, promise); + this.getReactApplicationContext().startActivityForResult(i, GET_CONTENT_INTENT, null); + + } + + @ReactMethod + public void addCompleteDownload (ReadableMap config, Promise promise) { + DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE); + String path = RNFetchBlobFS.normalizePath(config.getString("path")); + if(path == null) { + promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path); + return; + } + try { + WritableMap stat = RNFetchBlobFS.statFile(path); + dm.addCompletedDownload( + config.hasKey("title") ? config.getString("title") : "", + config.hasKey("description") ? config.getString("description") : "", + true, + config.hasKey("mime") ? config.getString("mime") : null, + path, + Long.valueOf(stat.getString("size")), + config.hasKey("showNotification") && config.getBoolean("showNotification") + ); + promise.resolve(null); + } + catch(Exception ex) { + promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString()); + } + } } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java index e3464f972..015cc8954 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java @@ -13,5 +13,6 @@ public class RNFetchBlobConst { public static final String RNFB_RESPONSE_BASE64 = "base64"; public static final String RNFB_RESPONSE_UTF8 = "utf8"; public static final String RNFB_RESPONSE_PATH = "path"; + public static final Integer GET_CONTENT_INTENT = 99900; } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index b7237a461..1537bca71 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -203,6 +203,7 @@ static public Map getSystemfolders(ReactApplicationContext ctx) state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath()); + res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath()); } res.put("MainBundleDir", ctx.getApplicationInfo().dataDir); return res; @@ -234,7 +235,8 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f InputStream fs; if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { - fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); + fs = RNFetchBlob.RCTContext.getAssets() + .open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); } else { fs = new FileInputStream(new File(path)); @@ -248,9 +250,7 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); while ((cursor = fs.read(buffer)) != -1) { encoder.encode(ByteBuffer.wrap(buffer).asCharBuffer()); - String chunk = new String(buffer); - if(cursor != bufferSize) - chunk = chunk.substring(0, cursor); + String chunk = new String(buffer, 0, cursor); emitStreamEvent(streamId, "data", chunk); if(tick > 0) SystemClock.sleep(tick); @@ -292,7 +292,8 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f buffer = null; } catch (Exception err) { - emitStreamEvent(streamId, "error", "Failed to convert data to "+encoding+" encoded string, this might due to the source data is not able to convert using this encoding."); + emitStreamEvent(streamId, "warn", "Failed to convert data to "+encoding+" encoded string, this might due to the source data is not able to convert using this encoding."); + err.printStackTrace(); } } @@ -878,13 +879,21 @@ static boolean isAsset(String path) { return false; } + /** + * Normalize the path, remove URI scheme (xxx://) so that we can handle it. + * @param path URI string. + * @return Normalized string + */ static String normalizePath(String path) { if(path == null) return null; - Uri uri = Uri.parse(path); - if(uri.getScheme() == null) { + if(!path.matches("\\w+\\:.*")) return path; + if(path.startsWith("file://")) { + return path.replace("file://", ""); } + + Uri uri = Uri.parse(path); if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { return path; } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index f40d9bbdb..db213c1e8 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -11,7 +11,6 @@ import com.RNFetchBlob.Response.RNFetchBlobDefaultResp; import com.RNFetchBlob.Response.RNFetchBlobFileResp; -import com.RNFetchBlob.Utils.RNFBCookieJar; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; @@ -21,14 +20,12 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.modules.network.OkHttpClientProvider; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.CookiePolicy; import java.net.MalformedURLException; import java.net.SocketException; import java.net.SocketTimeoutException; @@ -43,7 +40,6 @@ import okhttp3.Call; import okhttp3.ConnectionPool; -import okhttp3.CookieJar; import okhttp3.Headers; import okhttp3.Interceptor; import okhttp3.MediaType; @@ -98,8 +94,9 @@ enum ResponseFormat { WritableMap respInfo; boolean timeout = false; ArrayList redirects = new ArrayList<>(); + OkHttpClient client; - public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) { + public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, OkHttpClient client, final Callback callback) { this.method = method.toUpperCase(); this.options = new RNFetchBlobConfig(options); this.taskId = taskId; @@ -108,6 +105,7 @@ public RNFetchBlobReq(ReadableMap options, String taskId, String method, String this.callback = callback; this.rawRequestBody = body; this.rawRequestBodyArray = arrayBody; + this.client = client; if(this.options.fileCache || this.options.path != null) responseType = ResponseType.FileStorage; @@ -194,9 +192,9 @@ else if(this.options.fileCache) try { // use trusty SSL socket if (this.options.trusty) { - clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient(); + clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient(client); } else { - clientBuilder = new OkHttpClient.Builder(); + clientBuilder = client.newBuilder(); } final Request.Builder builder = new Request.Builder(); @@ -297,10 +295,7 @@ else if(cType.isEmpty()) { } // #156 fix cookie issue - - final Request req = builder.build(); - clientBuilder.cookieJar(new RNFBCookieJar()); clientBuilder.addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { @@ -629,17 +624,26 @@ public void onReceive(Context context, Intent intent) { DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE); dm.query(query); Cursor c = dm.query(query); - String error = null; + + String filePath = null; // the file exists in media content database if (c.moveToFirst()) { + // #297 handle failed request + int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); + if(statusCode == DownloadManager.STATUS_FAILED) { + this.callback.invoke("Download manager failed to download from " + this.url + ". Statu Code = " + statusCode, null, null); + return; + } String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); - 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 - if (cursor != null) { - cursor.moveToFirst(); - filePath = cursor.getString(0); + if (contentUri != null) { + 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 + if (cursor != null) { + cursor.moveToFirst(); + filePath = cursor.getString(0); + } } } // When the file is not found in media content database, check if custom path exists @@ -653,7 +657,8 @@ public void onReceive(Context context, Intent intent) { this.callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, customDest); } catch(Exception ex) { - error = ex.getLocalizedMessage(); + ex.printStackTrace(); + this.callback.invoke(ex.getLocalizedMessage(), null); } } else { diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java index 479bd4a50..6e976d775 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java @@ -52,7 +52,7 @@ public static void emitWarningEvent(String data) { .emit(RNFetchBlobConst.EVENT_MESSAGE, args); } - public static OkHttpClient.Builder getUnsafeOkHttpClient() { + public static OkHttpClient.Builder getUnsafeOkHttpClient(OkHttpClient client) { try { // Create a trust manager that does not validate certificate chains final TrustManager[] trustAllCerts = new TrustManager[]{ @@ -78,7 +78,7 @@ public java.security.cert.X509Certificate[] getAcceptedIssuers() { // Create an ssl socket factory with our all-trusting manager final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - OkHttpClient.Builder builder = new OkHttpClient.Builder(); + OkHttpClient.Builder builder = client.newBuilder(); builder.sslSocketFactory(sslSocketFactory); builder.hostnameVerifier(new HostnameVerifier() { @Override diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java index bc9b3779c..88fc7c69a 100644 --- a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java +++ b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java @@ -81,7 +81,6 @@ public long read(Buffer sink, long byteCount) throws IOException { byte[] bytes = new byte[(int) byteCount]; long read = originalBody.byteStream().read(bytes, 0, (int) byteCount); bytesDownloaded += read > 0 ? read : 0; - Log.i("bytes downloaded", String.valueOf(byteCount) + "/" + String.valueOf(read) + "=" + String.valueOf(bytesDownloaded)); if (read > 0) { ofStream.write(bytes, 0, (int) read); } diff --git a/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java b/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java index e5742b81e..381a3f9f3 100644 --- a/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java +++ b/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java @@ -8,6 +8,11 @@ import android.provider.MediaStore; import android.content.ContentUris; import android.os.Environment; +import android.content.ContentResolver; +import com.RNFetchBlob.RNFetchBlobUtils; +import java.io.File; +import java.io.InputStream; +import java.io.FileOutputStream; public class PathResolver { public static String getRealPathFromURI(final Context context, final Uri uri) { @@ -59,6 +64,29 @@ else if (isMediaDocument(uri)) { return getDataColumn(context, contentUri, selection, selectionArgs); } + // Other Providers + else { + try { + InputStream attachment = context.getContentResolver().openInputStream(uri); + if (attachment != null) { + String filename = getContentName(context.getContentResolver(), uri); + if (filename != null) { + File file = new File(context.getCacheDir(), filename); + FileOutputStream tmp = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + while (attachment.read(buffer) > 0) { + tmp.write(buffer); + } + tmp.close(); + attachment.close(); + return file.getAbsolutePath(); + } + } + } catch (Exception e) { + RNFetchBlobUtils.emitWarningEvent(e.toString()); + return null; + } + } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { @@ -77,6 +105,18 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) { return null; } + private static String getContentName(ContentResolver resolver, Uri uri) { + Cursor cursor = resolver.query(uri, null, null, null, null); + cursor.moveToFirst(); + int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); + if (nameIndex >= 0) { + String name = cursor.getString(nameIndex); + cursor.close(); + return name; + } + return null; + } + /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. diff --git a/fs.js b/fs.js index ee211dd41..83e6bdceb 100644 --- a/fs.js +++ b/fs.js @@ -29,7 +29,9 @@ const dirs = { DownloadDir : RNFetchBlob.DownloadDir, DCIMDir : RNFetchBlob.DCIMDir, SDCardDir : RNFetchBlob.SDCardDir, - MainBundleDir : RNFetchBlob.MainBundleDir + SDCardApplicationDir : RNFetchBlob.SDCardApplicationDir, + MainBundleDir : RNFetchBlob.MainBundleDir, + LibraryDir : RNFetchBlob.LibraryDir } /** @@ -171,12 +173,12 @@ function writeFile(path:string, data:string | Array, encoding:?string):P return Promise.reject('Invalid argument "path" ') if(encoding.toLocaleLowerCase() === 'ascii') { if(!Array.isArray(data)) - Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) else return RNFetchBlob.writeFileArray(path, data, false); } else { if(typeof data !== 'string') - Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) else return RNFetchBlob.writeFile(path, encoding, data, false); } @@ -188,12 +190,12 @@ function appendFile(path:string, data:string | Array, encoding:?string): return Promise.reject('Invalid argument "path" ') if(encoding.toLocaleLowerCase() === 'ascii') { if(!Array.isArray(data)) - Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) else return RNFetchBlob.writeFileArray(path, data, true); } else { if(typeof data !== 'string') - Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) else return RNFetchBlob.writeFile(path, encoding, data, true); } diff --git a/index.js b/index.js index 0f68fa2e1..c8ed1a9f6 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,6 @@ import polyfill from './polyfill' import _ from 'lodash' import android from './android' import ios from './ios' -import net from './net' import JSONStream from './json-stream' const { RNFetchBlobSession, @@ -51,7 +50,6 @@ const RNFetchBlob = NativeModules.RNFetchBlob // their .expire event if(Platform.OS === 'ios') { AppState.addEventListener('change', (e) => { - console.log('app state changed', e) if(e === 'active') RNFetchBlob.emitExpiredEvent(()=>{}) }) @@ -564,7 +562,6 @@ export default { session, fs, wrap, - net, polyfill, JSONStream } diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 9dd4439f6..246d6707c 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -314,7 +314,7 @@ - (NSDictionary *)constantsToExport #pragma mark - fs.stat RCT_EXPORT_METHOD(stat:(NSString *)target callback:(RCTResponseSenderBlock) callback) { - + [RNFetchBlobFS getPathFromUri:target completionHandler:^(NSString *path, ALAssetRepresentation *asset) { __block NSMutableArray * result; if(path != nil) @@ -323,14 +323,14 @@ - (NSDictionary *)constantsToExport BOOL exist = nil; BOOL isDir = nil; NSError * error = nil; - + exist = [fm fileExistsAtPath:path isDirectory:&isDir]; if(exist == NO) { callback(@[[NSString stringWithFormat:@"failed to stat path `%@` for it is not exist or it is not exist", path]]); return ; } result = [RNFetchBlobFS stat:path error:&error]; - + if(error == nil) callback(@[[NSNull null], result]); else @@ -389,7 +389,7 @@ - (NSDictionary *)constantsToExport #pragma mark - fs.cp RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback) { - + // path = [RNFetchBlobFS getPathOfAsset:path]; [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) { NSError * error = nil; @@ -401,14 +401,14 @@ - (NSDictionary *)constantsToExport else { BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error]; - + if(error == nil) callback(@[[NSNull null], @YES]); else callback(@[[error localizedDescription], @NO]); } }]; - + } @@ -470,7 +470,7 @@ - (NSDictionary *)constantsToExport else bufferSize = 4096; } - + dispatch_async(fsQueue, ^{ [RNFetchBlobFS readStream:path encoding:encoding bufferSize:bufferSize tick:tick streamId:streamId bridgeRef:_bridge]; }); @@ -496,7 +496,7 @@ - (NSDictionary *)constantsToExport #pragma mark - net.enableProgressReport RCT_EXPORT_METHOD(enableProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count) { - + RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count]; [RNFetchBlobNetwork enableProgressReport:taskId config:cfg]; } @@ -523,9 +523,10 @@ - (NSDictionary *)constantsToExport UIViewController *rootCtrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; documentController.delegate = self; if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - [documentController presentOptionsMenuFromRect:rootCtrl.view.bounds inView:rootCtrl.view animated:YES]; - }); + CGRect rect = CGRectMake(0.0, 0.0, 0.0, 0.0); + dispatch_sync(dispatch_get_main_queue(), ^{ + [documentController presentOptionsMenuFromRect:rect inView:rootCtrl.view animated:YES]; + }); resolve(@[[NSNull null]]); } else { reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil); @@ -541,7 +542,7 @@ - (NSDictionary *)constantsToExport // NSURL * url = [[NSURL alloc] initWithString:uri]; documentController = [UIDocumentInteractionController interactionControllerWithURL:url]; documentController.delegate = self; - + if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) { dispatch_sync(dispatch_get_main_queue(), ^{ [documentController presentPreviewAnimated:YES]; @@ -564,7 +565,7 @@ - (NSDictionary *)constantsToExport } else { reject(@"RNFetchBlob could not open document", [error description], nil); } - + } @@ -579,12 +580,6 @@ - (UIViewController *) documentInteractionControllerViewControllerForPreview: (U return window.rootViewController; } -# pragma mark - getCookies -RCT_EXPORT_METHOD(getCookies:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ - resolve([RNFetchBlobNetwork getCookies:url]); -} - # pragma mark - check expired network events RCT_EXPORT_METHOD(emitExpiredEvent:(RCTResponseSenderBlock)callback) @@ -593,4 +588,6 @@ - (UIViewController *) documentInteractionControllerViewControllerForPreview: (U } + + @end diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index d92abe6ce..9d4e00b0d 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -758,8 +758,6 @@ + (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * pa +(void) df:(RCTResponseSenderBlock)callback { - uint64_t totalSpace = 0; - uint64_t totalFreeSpace = 0; NSError *error = nil; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error]; @@ -767,11 +765,10 @@ +(void) df:(RCTResponseSenderBlock)callback if (dictionary) { NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize]; NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize]; - totalSpace = [fileSystemSizeInBytes unsignedLongLongValue]; - totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue]; + callback(@[[NSNull null], @{ - @"free" : [NSString stringWithFormat:@"%ld", totalFreeSpace], - @"total" : [NSString stringWithFormat:@"%ld", totalSpace] + @"free" : freeFileSystemSizeInBytes, + @"total" : fileSystemSizeInBytes, }]); } else { callback(@[@"failed to get storage usage."]); diff --git a/ios/RNFetchBlobNetwork.h b/ios/RNFetchBlobNetwork.h index 3f38322c3..d3b4654a5 100644 --- a/ios/RNFetchBlobNetwork.h +++ b/ios/RNFetchBlobNetwork.h @@ -51,7 +51,6 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse - (void) sendRequest:(NSDictionary * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback; + (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config; + (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config; -+ (NSArray *) getCookies:(NSString *) url; diff --git a/ios/RNFetchBlobNetwork.m b/ios/RNFetchBlobNetwork.m index 0ce7a0dbf..7be57fc59 100644 --- a/ios/RNFetchBlobNetwork.m +++ b/ios/RNFetchBlobNetwork.m @@ -37,7 +37,6 @@ NSMapTable * taskTable; NSMapTable * expirationTable; -NSMapTable * cookiesTable; NSMutableDictionary * progressTable; NSMutableDictionary * uploadProgressTable; @@ -59,10 +58,6 @@ static void initialize_tables() { { uploadProgressTable = [[NSMutableDictionary alloc] init]; } - if(cookiesTable == nil) - { - cookiesTable = [[NSMapTable alloc] init]; - } } @@ -87,6 +82,7 @@ @interface RNFetchBlobNetwork () NSMutableArray * redirects; ResponseFormat responseFormat; BOOL * followRedirect; + BOOL backgroundTask; } @end @@ -116,48 +112,6 @@ - (id)init { return self; } -+ (NSArray *) getCookies:(NSString *) url -{ - NSString * hostname = [[NSURL URLWithString:url] host]; - NSMutableArray * cookies = [NSMutableArray new]; - NSArray * list = [cookiesTable objectForKey:hostname]; - for(NSHTTPCookie * cookie in list) - { - NSMutableString * cookieStr = [[NSMutableString alloc] init]; - [cookieStr appendString:cookie.name]; - [cookieStr appendString:@"="]; - [cookieStr appendString:cookie.value]; - - if(cookie.expiresDate == nil) { - [cookieStr appendString:@"; max-age=0"]; - } - else { - [cookieStr appendString:@"; expires="]; - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"EEE, dd MM yyyy HH:mm:ss ZZZ"]; - NSString *strDate = [dateFormatter stringFromDate:cookie.expiresDate]; - [cookieStr appendString:strDate]; - } - - - [cookieStr appendString:@"; domain="]; - [cookieStr appendString:hostname]; - [cookieStr appendString:@"; path="]; - [cookieStr appendString:cookie.path]; - - - if (cookie.isSecure) { - [cookieStr appendString:@"; secure"]; - } - - if (cookie.isHTTPOnly) { - [cookieStr appendString:@"; httponly"]; - } - [cookies addObject:cookieStr]; - } - return cookies; -} - + (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config { if(progressTable == nil) @@ -215,6 +169,8 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options self.expectedBytes = 0; self.receivedBytes = 0; self.options = options; + + backgroundTask = [options valueForKey:@"IOSBackgroundTask"] == nil ? NO : [[options valueForKey:@"IOSBackgroundTask"] boolValue]; followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue]; isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue]; redirects = [[NSMutableArray alloc] init]; @@ -239,13 +195,12 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options // the session trust any SSL certification NSURLSessionConfiguration *defaultConfigObject; - if(!followRedirect) - { - defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration]; - } - else + + defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration]; + + if(backgroundTask) { - NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId]; + defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId]; } // set request timeout @@ -294,14 +249,6 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; __block UIApplication * app = [UIApplication sharedApplication]; - // #115 handling task expired when application entering backgound for a long time - UIBackgroundTaskIdentifier tid = [app beginBackgroundTaskWithName:taskId expirationHandler:^{ - NSLog([NSString stringWithFormat:@"session %@ expired", taskId ]); - [expirationTable setObject:task forKey:taskId]; - // comment out this one as it might cause app crash #271 -// [app endBackgroundTask:tid]; - }]; - } // #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled @@ -418,9 +365,10 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat // # 153 get cookies if(response.URL != nil) { + NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSArray * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL]; if(cookies != nil && [cookies count] > 0) { - [cookiesTable setObject:cookies forKey:response.URL.host]; + [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil]; } } diff --git a/ios/RNFetchBlobReqBuilder.m b/ios/RNFetchBlobReqBuilder.m index 38504389f..600496295 100644 --- a/ios/RNFetchBlobReqBuilder.m +++ b/ios/RNFetchBlobReqBuilder.m @@ -99,8 +99,8 @@ +(void) buildOctetRequest:(NSDictionary *)options dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableData * blobData; long size = -1; - // if method is POST or PUT, convert data string format - if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) { + // if method is POST, PUT or PATCH, convert data string format + if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"] || [[method lowercaseString] isEqualToString:@"patch"]) { // generate octet-stream body if(body != nil) { __block NSString * cType = [[self class] getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders]; @@ -201,16 +201,18 @@ void __block (^getFieldData)(id field) = ^(id field) RCTLogWarn(@"RNFetchBlob multipart request builder has found a field without `data` or `name` property, the field will be removed implicitly."); return; } - contentType = contentType == nil ? @"application/octet-stream" : contentType; + // field is a text field if([field valueForKey:@"filename"] == nil || content == nil) { + contentType = contentType == nil ? @"text/plain" : contentType; [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]]; - // [formData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; + // [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]]; } // field contains a file else { + contentType = contentType == nil ? @"application/octet-stream" : contentType; NSMutableData * blobData; if(content != nil) { @@ -227,13 +229,12 @@ void __block (^getFieldData)(id field) = ^(id field) return; } NSString * filename = [field valueForKey:@"filename"]; - // NSString *fileType=[field valueForKey:@"type"]; [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; [formData appendData:[[NSString stringWithFormat:@"Content-Length: %ld\r\n\r\n", (long)[content length]] dataUsingEncoding:NSUTF8StringEncoding]]; - + [formData appendData:content]; [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; i++; diff --git a/package.json b/package.json index 44e94dfe3..a4524df60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-fetch-blob", - "version": "0.10.4", + "version": "0.10.6", "description": "A module provides upload, download, and files access API. Supports file stream read/write for process large files.", "main": "index.js", "scripts": { @@ -32,6 +32,7 @@ "author": "wkh237 ", "license": "MIT", "contributors": [ + "Ben ", "" ] } \ No newline at end of file diff --git a/polyfill/Fetch.js b/polyfill/Fetch.js index 7907f8f17..7be52e084 100644 --- a/polyfill/Fetch.js +++ b/polyfill/Fetch.js @@ -57,24 +57,38 @@ class RNFetchBlobFetchPolyfill { // task is a progress reportable and cancellable Promise, however, // task.then is not, so we have to extend task.then with progress and // cancel function - let task = promise + let progressHandler, uploadHandler, cancelHandler + let statefulPromise = promise .then((body) => { - return RNFetchBlob.config(config) - .fetch(options.method, url, options.headers, body) + let task = RNFetchBlob.config(config) + .fetch(options.method, url, options.headers, body) + if(progressHandler) + task.progress(progressHandler) + if(uploadHandler) + task.uploadProgress(uploadHandler) + if(cancelHandler) + task.cancel() + return task.then((resp) => { + log.verbose('response', resp) + // release blob cache created when sending request + if(blobCache !== null && blobCache instanceof Blob) + blobCache.close() + return Promise.resolve(new RNFetchBlobFetchRepsonse(resp)) + }) }) - let statefulPromise = task.then((resp) => { - log.verbose('response', resp) - // release blob cache created when sending request - if(blobCache !== null && blobCache instanceof Blob) - blobCache.close() - return Promise.resolve(new RNFetchBlobFetchRepsonse(resp)) - }) - // extend task.then progress with report and cancelling functions - statefulPromise.cancel = task.cancel - statefulPromise.progress = task.progress - statefulPromise.uploadProgress = task.uploadProgress + statefulPromise.progress = (fn) => { + progressHandler = fn + } + statefulPromise.uploadProgress = (fn) => { + uploadHandler = fn + } + statefulPromise.cancel = () => { + cancelHandler = true + if(task.cancel) + task.cancel() + } return statefulPromise diff --git a/polyfill/FileReader.js b/polyfill/FileReader.js index 3a4e29618..b72df17f7 100644 --- a/polyfill/FileReader.js +++ b/polyfill/FileReader.js @@ -47,15 +47,15 @@ export default class FileReader extends EventTarget { } abort() { - log.verbose('abort', b, label) + log.verbose('abort') } readAsArrayBuffer(b:Blob) { - log.verbose('readAsArrayBuffer', b, label) + log.verbose('readAsArrayBuffer', b) } readAsBinaryString(b:Blob) { - log.verbose('readAsBinaryString', b, label) + log.verbose('readAsBinaryString', b) } readAsText(b:Blob, label:?string) { @@ -63,7 +63,7 @@ export default class FileReader extends EventTarget { } readAsDataURL(b:Blob) { - log.verbose('readAsDataURL', b, label) + log.verbose('readAsDataURL', b) } dispatchEvent(event, e) { @@ -78,7 +78,7 @@ export default class FileReader extends EventTarget { // getters and setters - get readState() { + get readyState() { return this._readyState } diff --git a/polyfill/XMLHttpRequest.js b/polyfill/XMLHttpRequest.js index 661545906..89171921f 100644 --- a/polyfill/XMLHttpRequest.js +++ b/polyfill/XMLHttpRequest.js @@ -196,11 +196,11 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ }) .fetch(_method, _url, _headers, body) this._task - .stateChange(this._headerReceived.bind(this)) - .uploadProgress(this._uploadProgressEvent.bind(this)) - .progress(this._progressEvent.bind(this)) - .catch(this._onError.bind(this)) - .then(this._onDone.bind(this)) + .stateChange(this._headerReceived) + .uploadProgress(this._uploadProgressEvent) + .progress(this._progressEvent) + .catch(this._onError) + .then(this._onDone) }) } @@ -274,7 +274,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ return result.substr(0, result.length-2) } - _headerReceived(e) { + _headerReceived = (e) => { log.debug('header received ', this._task.taskId, e) this.responseURL = this._url if(e.state === "2") { @@ -285,7 +285,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ } } - _uploadProgressEvent(send:number, total:number) { + _uploadProgressEvent = (send:number, total:number) => { if(!this._uploadStarted) { this.upload.dispatchEvent('loadstart') this._uploadStarted = true @@ -295,7 +295,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total)) } - _progressEvent(send:number, total:number, chunk:string) { + _progressEvent = (send:number, total:number, chunk:string) => { log.verbose(this.readyState) if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED) this._dispatchReadStateChange(XMLHttpRequest.LOADING) @@ -310,7 +310,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this.dispatchEvent('progress', e) } - _onError(err) { + _onError = (err) => { let statusCode = Math.floor(this.status) if(statusCode >= 100 && statusCode !== 408) { return @@ -331,7 +331,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this.clearEventListeners() } - _onDone(resp) { + _onDone = (resp) => { log.debug('XMLHttpRequest done', this._url, resp, this) this._statusText = this._status let responseDataReady = () => { diff --git a/react-native-fetch-blob.podspec b/react-native-fetch-blob.podspec index 2518b32d5..f702d287b 100644 --- a/react-native-fetch-blob.podspec +++ b/react-native-fetch-blob.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = "react-native-fetch-blob" - s.version = "0.10.3-beta.1" + s.version = "0.10.6" s.summary = "A project committed to make file acess and data transfer easier, effiecient for React Native developers." s.requires_arc = true s.license = 'MIT' s.homepage = 'n/a' s.authors = { "wkh237" => "xeiyan@gmail.com" } - s.source = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.10.3-beta.1'} + s.source = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.10.6'} s.source_files = 'ios/**/*.{h,m}' s.platform = :ios, "7.0" s.dependency 'React/Core' diff --git a/utils/uuid.js b/utils/uuid.js index e7d0e300f..f147e1f3e 100644 --- a/utils/uuid.js +++ b/utils/uuid.js @@ -1,6 +1,4 @@ export default function getUUID() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }); + return Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); } From 90fa5717819678057f65c1b9f9345907a7ddcda5 Mon Sep 17 00:00:00 2001 From: windwang Date: Sat, 26 Aug 2017 10:53:58 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE | 1 + .github/PULL_REQUEST_TEMPLATE | 2 +- README.md | 10 ++- android.js | 2 +- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 80 ++++++++++++------- .../com/RNFetchBlob/RNFetchBlobPackage.java | 1 - .../java/com/RNFetchBlob/RNFetchBlobReq.java | 49 +++++++++++- .../com/RNFetchBlob/Utils/PathResolver.java | 22 ++++- class/RNFetchBlobSession.js | 9 +-- class/RNFetchBlobWriteStream.js | 17 ++-- fs.js | 24 +++--- index.js | 17 ++-- ios.js | 4 +- ios/RNFetchBlob/RNFetchBlob.m | 16 ++-- ios/RNFetchBlobFS.m | 32 ++++---- json-stream.js | 7 +- package.json | 6 +- polyfill/Blob.js | 16 ++++ polyfill/XMLHttpRequest.js | 2 +- 19 files changed, 204 insertions(+), 113 deletions(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 65d12bb4c..25e78c4cb 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -4,3 +4,4 @@ You may want to take a look on that page or find issues tagged "trouble shooting * please provide the version of installed library and RN project. * a sample code snippet/repository is very helpful to spotting the problem. * issues which have been tagged as 'needs feedback', will be closed after 2 weeks if receive no feedbacks. +* issues lack of detailed information will be closed without any feedback diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 8cbcd298b..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.5 +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..7680fab86 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) @@ -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.js b/android.js index 0b5dbd84b..80c4e4a96 100644 --- a/android.js +++ b/android.js @@ -13,7 +13,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob /** * Send an intent to open the file. - * @param {string]} path Path of the file to be open. + * @param {string} path Path of the file to be open. * @param {string} mime MIME type string * @return {Promise} */ diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 1537bca71..f4531bb5b 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -78,7 +78,7 @@ static public void writeFile(String path, String encoding, String data, final bo data = normalizePath(data); File src = new File(data); if(!src.exists()) { - promise.reject("RNfetchBlob writeFileError", "source file : " + data + "not exists"); + promise.reject("RNfetchBlob writeFile error", "source file : " + data + " does not exist"); fout.close(); return ; } @@ -100,7 +100,7 @@ static public void writeFile(String path, String encoding, String data, final bo fout.close(); promise.resolve(written); } catch (Exception e) { - promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage()); + promise.reject("RNFetchBlob writeFile error", e.getLocalizedMessage()); } } @@ -127,7 +127,7 @@ static public void writeFile(String path, ReadableArray data, final boolean appe os.close(); promise.resolve(data.size()); } catch (Exception e) { - promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage()); + promise.reject("RNFetchBlob writeFile error", e.getLocalizedMessage()); } } @@ -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)); @@ -292,7 +309,8 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f buffer = null; } catch (Exception err) { - emitStreamEvent(streamId, "warn", "Failed to convert data to "+encoding+" encoded string, this might due to the source data is not able to convert using this encoding."); + emitStreamEvent(streamId, "warn", "Failed to convert data to " + encoding + + " encoded string, this might due to the source data is not able to convert using this encoding."); err.printStackTrace(); } } @@ -307,7 +325,7 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f public void writeStream(String path, String encoding, boolean append, Callback callback) { File dest = new File(path); if(!dest.exists() || dest.isDirectory()) { - callback.invoke("write stream error: target path `" + path + "` may not exists or it's a folder"); + callback.invoke("target path `" + path + "` may not exist or it is a folder"); return; } try { @@ -319,7 +337,7 @@ public void writeStream(String path, String encoding, boolean append, Callback c this.writeStreamInstance = fs; callback.invoke(null, streamId); } catch(Exception err) { - callback.invoke("write stream error: failed to create write stream at path `"+path+"` "+ err.getLocalizedMessage()); + callback.invoke("failed to create write stream at path `" + path + "` " + err.getLocalizedMessage()); } } @@ -416,12 +434,13 @@ static void deleteRecursive(File fileOrDirectory) { static void mkdir(String path, Callback callback) { File dest = new File(path); if(dest.exists()) { - callback.invoke("mkdir error: failed to create folder at `" + path + "` folder already exists"); + callback.invoke("mkdir failed, folder already exists at " + path); return; } dest.mkdirs(); callback.invoke(); } + /** * Copy file to destination path * @param path Source path @@ -437,7 +456,7 @@ static void cp(String path, String dest, Callback callback) { try { if(!isPathExists(path)) { - callback.invoke("cp error: source file at path`" + path + "` not exists"); + callback.invoke("source file at path`" + path + "` does not exist"); return; } if(!new File(dest).exists()) @@ -478,7 +497,7 @@ static void cp(String path, String dest, Callback callback) { static void mv(String path, String dest, Callback callback) { File src = new File(path); if(!src.exists()) { - callback.invoke("mv error: source file at path `" + path + "` does not exists"); + callback.invoke("source file at path `" + path + "` does not exist"); return; } src.renameTo(new File(dest)); @@ -518,7 +537,7 @@ static void ls(String path, Callback callback) { path = normalizePath(path); File src = new File(path); if (!src.exists() || !src.isDirectory()) { - callback.invoke("ls error: failed to list path `" + path + "` for it is not exist or it is not a folder"); + callback.invoke("failed to list path `" + path + "` for it is not exist or it is not a folder"); return; } String[] files = new File(path).list(); @@ -542,7 +561,7 @@ public static void slice(String src, String dest, int start, int end, String enc src = normalizePath(src); File source = new File(src); if(!source.exists()) { - promise.reject("RNFetchBlob.slice error", "source file : " + src + " not exists"); + promise.reject("RNFetchBlob slice error", "source file : " + src + " does not exist"); return; } long size = source.length(); @@ -568,7 +587,7 @@ public static void slice(String src, String dest, int start, int end, String enc promise.resolve(dest); } catch (Exception e) { e.printStackTrace(); - promise.reject(e.getLocalizedMessage()); + promise.reject("RNFetchBlob slice error", e.getLocalizedMessage()); } } @@ -580,18 +599,18 @@ static void lstat(String path, final Callback callback) { protected Integer doInBackground(String ...args) { WritableArray res = Arguments.createArray(); if(args[0] == null) { - callback.invoke("lstat error: the path specified for lstat is either `null` or `undefined`."); + callback.invoke("the path specified for lstat is either `null` or `undefined`."); return 0; } File src = new File(args[0]); if(!src.exists()) { - callback.invoke("lstat error: failed to list path `" + args[0] + "` for it is not exist or it is not a folder"); + callback.invoke("failed to lstat path `" + args[0] + "` because it does not exist or it is not a folder"); return 0; } if(src.isDirectory()) { String [] files = src.list(); for(String p : files) { - res.pushMap(statFile ( src.getPath() + "/" + p)); + res.pushMap(statFile(src.getPath() + "/" + p)); } } else { @@ -613,7 +632,7 @@ static void stat(String path, Callback callback) { path = normalizePath(path); WritableMap result = statFile(path); if(result == null) - callback.invoke("stat error: failed to list path `" + path + "` for it is not exist or it is not a folder", null); + callback.invoke("failed to stat path `" + path + "` because it does not exist or it is not a folder", null); else callback.invoke(null, result); } catch(Exception err) { @@ -692,23 +711,24 @@ static void createFile(String path, String data, String encoding, Callback callb String orgPath = data.replace(RNFetchBlobConst.FILE_PREFIX, ""); File src = new File(orgPath); if(!src.exists()) { - callback.invoke("RNfetchBlob writeFileError", "source file : " + data + "not exists"); + callback.invoke("source file : " + data + " does not exist"); return ; } FileInputStream fin = new FileInputStream(src); OutputStream ostream = new FileOutputStream(dest); - byte [] buffer = new byte [10240]; + byte[] buffer = new byte[10240]; int read = fin.read(buffer); - while(read > 0) { + while (read > 0) { ostream.write(buffer, 0, read); read = fin.read(buffer); } fin.close(); ostream.close(); - } - else { + } else { if (!created) { - callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists, or the file already exists. If you intended to overwrite the existing file use fs.writeFile instead."); + callback.invoke("failed to create new file at path `" + path + "` because its parent path " + + "may not exist, or the file already exists. If you intended to overwrite the " + + "existing file use fs.writeFile instead."); return; } OutputStream ostream = new FileOutputStream(dest); @@ -730,12 +750,12 @@ static void createFileASCII(String path, ReadableArray data, Callback callback) try { File dest = new File(path); if(dest.exists()) { - callback.invoke("create file error: failed to create file at path `" + path + "`, file already exists."); + callback.invoke("failed to create new file at path `" + path + "`, file already exists."); return; } boolean created = dest.createNewFile(); if(!created) { - callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists"); + callback.invoke("failed to create new file at path `" + path + "` because its parent path may not exist"); return; } OutputStream ostream = new FileOutputStream(dest); 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/class/RNFetchBlobSession.js b/class/RNFetchBlobSession.js index 02fd54655..f40875cb2 100644 --- a/class/RNFetchBlobSession.js +++ b/class/RNFetchBlobSession.js @@ -9,16 +9,11 @@ import { } from 'react-native' const RNFetchBlob = NativeModules.RNFetchBlob -const emitter = DeviceEventEmitter let sessions = {} export default class RNFetchBlobSession { - add : (path:string) => RNFetchBlobSession; - remove : (path:string) => RNFetchBlobSession; - dispose : () => Promise; - list : () => Array; name : string; static getSession(name:string):any { @@ -50,7 +45,7 @@ export default class RNFetchBlobSession { remove(path:string):RNFetchBlobSession { let list = sessions[this.name] - for(let i in list) { + for(let i of list) { if(list[i] === path) { sessions[this.name].splice(i, 1) break; @@ -67,7 +62,7 @@ export default class RNFetchBlobSession { return new Promise((resolve, reject) => { RNFetchBlob.removeSession(sessions[this.name], (err) => { if(err) - reject(err) + reject(new Error(err)) else { delete sessions[this.name] resolve() diff --git a/class/RNFetchBlobWriteStream.js b/class/RNFetchBlobWriteStream.js index d89b9fe2c..d88c48e22 100644 --- a/class/RNFetchBlobWriteStream.js +++ b/class/RNFetchBlobWriteStream.js @@ -9,36 +9,35 @@ import { } from 'react-native' const RNFetchBlob = NativeModules.RNFetchBlob -const emitter = DeviceEventEmitter export default class RNFetchBlobWriteStream { id : string; encoding : string; - append : bool; + append : boolean; - constructor(streamId:string, encoding:string, append:string) { + constructor(streamId:string, encoding:string, append:boolean) { this.id = streamId this.encoding = encoding this.append = append } - write(data:string) { + write(data:string): Promise { return new Promise((resolve, reject) => { try { let method = this.encoding === 'ascii' ? 'writeArrayChunk' : 'writeChunk' if(this.encoding.toLocaleLowerCase() === 'ascii' && !Array.isArray(data)) { - reject('ascii input data must be an Array') + reject(new Error('ascii input data must be an Array')) return } RNFetchBlob[method](this.id, data, (error) => { if(error) - reject(error) + reject(new Error(error)) else - resolve() + resolve(this) }) } catch(err) { - reject(err) + reject(new Error(err)) } }) } @@ -50,7 +49,7 @@ export default class RNFetchBlobWriteStream { resolve() }) } catch (err) { - reject(err) + reject(new Error(err)) } }) } diff --git a/fs.js b/fs.js index 83e6bdceb..70ed39d96 100644 --- a/fs.js +++ b/fs.js @@ -19,7 +19,7 @@ import type { } from './types' const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob -const emitter = DeviceEventEmitter + const dirs = { DocumentDir : RNFetchBlob.DocumentDir, CacheDir : RNFetchBlob.CacheDir, @@ -83,13 +83,13 @@ function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'ut * Create write stream to a file. * @param {string} path Target path of file stream. * @param {string} encoding Encoding of input data. - * @param {bool} append A flag represent if data append to existing ones. - * @return {Promise} A promise resolves a `WriteStream` object. + * @param {boolean} [append] A flag represent if data append to existing ones. + * @return {Promise} A promise resolves a `WriteStream` object. */ function writeStream( path : string, encoding : 'utf8' | 'ascii' | 'base64', - append? : ?bool, + append? : ?boolean, ):Promise { if(!path) throw Error('RNFetchBlob could not open file stream with empty `path`') @@ -110,6 +110,7 @@ function writeStream( * @param {string} path The file path. * @param {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii` * @param {boolean} bufferSize Size of stream buffer. + * @param {number} [tick=10] Interval in milliseconds between reading chunks of data * @return {RNFetchBlobStream} RNFetchBlobStream stream instance. */ function readStream( @@ -154,7 +155,7 @@ function pathForAppGroup(groupName:string):Promise { * @param {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream. * @return {Promise | string>} */ -function readFile(path:string, encoding:string, bufferSize:?number):Promise { +function readFile(path:string, encoding:string):Promise { if(typeof path !== 'string') return Promise.reject(new Error('Invalid argument "path" ')) return RNFetchBlob.readFile(path, encoding) @@ -170,7 +171,7 @@ function readFile(path:string, encoding:string, bufferSize:?number):Promise function writeFile(path:string, data:string | Array, encoding:?string):Promise { encoding = encoding || 'utf8' if(typeof path !== 'string') - return Promise.reject('Invalid argument "path" ') + return Promise.reject(new Error('Invalid argument "path" ')) if(encoding.toLocaleLowerCase() === 'ascii') { if(!Array.isArray(data)) return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) @@ -187,7 +188,7 @@ function writeFile(path:string, data:string | Array, encoding:?string):P function appendFile(path:string, data:string | Array, encoding:?string):Promise { encoding = encoding || 'utf8' if(typeof path !== 'string') - return Promise.reject('Invalid argument "path" ') + return Promise.reject(new Error('Invalid argument "path" ')) if(encoding.toLocaleLowerCase() === 'ascii') { if(!Array.isArray(data)) return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) @@ -224,7 +225,7 @@ function stat(path:string):Promise { /** * Android only method, request media scanner to scan the file. - * @param {Array>} Array contains Key value pairs with key `path` and `mime`. + * @param {Array>} pairs Array contains Key value pairs with key `path` and `mime`. * @return {Promise} */ function scanFile(pairs:any):Promise { @@ -302,10 +303,9 @@ function unlink(path:string):Promise { /** * Check if file exists and if it is a folder. * @param {string} path Path to check - * @return {Promise} + * @return {Promise} */ -function exists(path:string):Promise { - +function exists(path:string):Promise { return new Promise((resolve, reject) => { try { RNFetchBlob.exists(path, (exist) => { @@ -358,7 +358,7 @@ function df():Promise<{ free : number, total : number }> { return new Promise((resolve, reject) => { RNFetchBlob.df((err, stat) => { if(err) - reject(err) + reject(new Error(err)) else resolve(stat) }) diff --git a/index.js b/index.js index c8ed1a9f6..a4812cb7d 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ import type { RNFetchBlobResponseInfo } from './types' import URIUtil from './utils/uri' -import StatefulPromise from './class/StatefulPromise.js' +//import StatefulPromise from './class/StatefulPromise.js' import fs from './fs' import getUUID from './utils/uuid' import base64 from 'base-64' @@ -118,7 +118,7 @@ function config (options:RNFetchBlobConfig) { * @param {string} method Should be one of `get`, `post`, `put` * @param {string} url A file URI string * @param {string} headers Arguments of file system API - * @param {any} body Data to put or post to file systen. + * @param {any} body Data to put or post to file systen. * @return {Promise} */ function fetchFile(options = {}, method, url, headers = {}, body):Promise { @@ -520,13 +520,12 @@ class FetchBlobResponse { } /** * Start read stream from cached file - * @param {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`. - * @param {Function} fn On data event handler + * @param {String} encoding Encode type, should be one of `base64`, `ascii`, `utf8`. * @return {void} */ - this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => { + this.readStream = (encoding: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => { if(this.type === 'path') { - return readStream(this.data, encode) + return readStream(this.data, encoding) } else { console.warn('RNFetchblob', 'this response data does not contains any available stream') @@ -539,10 +538,10 @@ class FetchBlobResponse { * @param {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`. * @return {String} */ - this.readFile = (encode: 'base64' | 'utf8' | 'ascii') => { + this.readFile = (encoding: 'base64' | 'utf8' | 'ascii') => { if(this.type === 'path') { - encode = encode || 'utf8' - return readFile(this.data, encode) + encoding = encoding || 'utf8' + return readFile(this.data, encoding) } else { console.warn('RNFetchblob', 'this response does not contains a readable file') diff --git a/ios.js b/ios.js index 566b424e2..9d04361d6 100644 --- a/ios.js +++ b/ios.js @@ -13,7 +13,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob /** * Open a file using UIDocumentInteractionController - * @param {string]} path Path of the file to be open. + * @param {string} path Path of the file to be open. * @param {string} scheme URI scheme that needs to support, optional * @return {Promise} */ @@ -26,7 +26,7 @@ function previewDocument(path:string, scheme:string) { /** * Preview a file using UIDocumentInteractionController - * @param {string]} path Path of the file to be open. + * @param {string} path Path of the file to be open. * @param {string} scheme URI scheme that needs to support, optional * @return {Promise} */ diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 246d6707c..5eee349b2 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -194,7 +194,7 @@ - (NSDictionary *)constantsToExport if(path) { resolve(path); } else { - reject(@"RNFetchBlob file not found", @"could not find path for app group", nil); + reject(@"RNFetchBlob pathForAppGroup Error", @"could not find path for app group", nil); } } @@ -223,7 +223,7 @@ - (NSDictionary *)constantsToExport BOOL isDir = nil; BOOL exist = [fm fileExistsAtPath:path isDirectory:&isDir]; if( exist == NO || isDir == YES) { - callback(@[[NSString stringWithFormat:@"target path `%@` may not exists or it's a folder", path]]); + callback(@[[NSString stringWithFormat:@"target path `%@` may not exist or it is a folder", path]]); return; } NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append]; @@ -326,7 +326,7 @@ - (NSDictionary *)constantsToExport exist = [fm fileExistsAtPath:path isDirectory:&isDir]; if(exist == NO) { - callback(@[[NSString stringWithFormat:@"failed to stat path `%@` for it is not exist or it is not exist", path]]); + callback(@[[NSString stringWithFormat:@"failed to stat path `%@` because it does not exist or it is not a folder", path]]); return ; } result = [RNFetchBlobFS stat:path error:&error]; @@ -362,7 +362,7 @@ - (NSDictionary *)constantsToExport exist = [fm fileExistsAtPath:path isDirectory:&isDir]; if(exist == NO) { - callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not exist", path]]); + callback(@[[NSString stringWithFormat:@"failed to lstat path `%@` because it does not exist or it is not a folder", path]]); return ; } NSError * error = nil; @@ -447,7 +447,7 @@ - (NSDictionary *)constantsToExport [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(id content, NSString * err) { if(err != nil) { - reject(@"RNFetchBlob failed to read file", err, nil); + reject(@"RNFetchBlob readFile Error", err, nil); return; } if(encoding == @"ascii") @@ -529,7 +529,7 @@ - (NSDictionary *)constantsToExport }); resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil); + reject(@"RNFetchBlob previewDocument Error", @"scheme is not supported", nil); } } @@ -549,7 +549,7 @@ - (NSDictionary *)constantsToExport }); resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil); + reject(@"RNFetchBlob openDocument Error", @"scheme is not supported", nil); } } @@ -563,7 +563,7 @@ - (NSDictionary *)constantsToExport { resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob could not open document", [error description], nil); + reject(@"RNFetchBlob excludeFromBackupKey Error", [error description], nil); } } diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 9d4e00b0d..183569e35 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -163,7 +163,7 @@ + (void) readStream:(NSString *)uri { if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO) { - NSString * message = [NSString stringWithFormat:@"File not exists at path %@", path]; + NSString * message = [NSString stringWithFormat:@"File does not exist at path %@", path]; NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": message }; [event sendDeviceEventWithName:streamId body:payload]; free(buffer); @@ -254,11 +254,11 @@ + (void) emitDataChunks:(NSData *)data encoding:(NSString *) encoding streamId:( [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]]; } } - + NSDictionary * payload = @{ @"event": FS_EVENT_DATA, @"detail" : asciiArray }; [event sendDeviceEventWithName:streamId body:payload]; } - + } @catch (NSException * ex) { @@ -335,7 +335,7 @@ + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString @try { NSFileManager * fm = [NSFileManager defaultManager]; NSError * err = nil; - // check if the folder exists, if not exists, create folders recursively + // check if the folder exists, if it does not exist create folders recursively // after the folders created, write data into the file NSString * folder = [path stringByDeletingLastPathComponent]; encoding = [encoding lowercaseString]; @@ -452,13 +452,13 @@ + (void) readFile:(NSString *)path else { if(![[NSFileManager defaultManager] fileExistsAtPath:path]) { - onComplete(nil, @"file not exists"); + onComplete(nil, @"file does not exist"); return; } fileContent = [NSData dataWithContentsOfFile:path]; - + } - + if(encoding != nil) { if([[encoding lowercaseString] isEqualToString:@"utf8"]) @@ -485,7 +485,7 @@ + (void) readFile:(NSString *)path { onComplete(fileContent, nil); } - + }]; } @@ -495,7 +495,7 @@ + (void) readFile:(NSString *)path + (BOOL) mkdir:(NSString *) path { BOOL isDir; NSError * err = nil; - // if temp folder not exists, create one + // if temp folder does not exist create it if(![[NSFileManager defaultManager] fileExistsAtPath: path isDirectory:&isDir]) { [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err]; } @@ -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]; + decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters]; } - if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) { + else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) { decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding]; } else if([[self.encoding lowercaseString] isEqualToString:@"ascii"]) { @@ -632,10 +632,10 @@ + (void)slice:(NSString *)path NSFileManager * fm = [NSFileManager defaultManager]; NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO]; [os open]; - // abort for the source file not exists + // abort because the source file does not exist if([fm fileExistsAtPath:path] == NO) { - reject(@"RNFetchBlob slice failed : the file does not exists", path, nil); + reject(@"RNFetchBlob slice Error : the file does not exist", path, nil); return; } long size = [fm attributesOfItemAtPath:path error:nil].fileSize; @@ -712,7 +712,7 @@ + (void)slice:(NSString *)path } else { - reject(@"slice error", [NSString stringWithFormat: @"could not resolve URI %@", path ], nil); + reject(@"RNFetchBlob slice Error", [NSString stringWithFormat: @"could not resolve URI %@", path ], nil); } }]; @@ -765,7 +765,7 @@ +(void) df:(RCTResponseSenderBlock)callback if (dictionary) { NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize]; NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize]; - + callback(@[[NSNull null], @{ @"free" : freeFileSystemSizeInBytes, @"total" : fileSystemSizeInBytes, diff --git a/json-stream.js b/json-stream.js index 8d901305d..cb19d02d8 100644 --- a/json-stream.js +++ b/json-stream.js @@ -2,14 +2,17 @@ import Oboe from './lib/oboe-browser.min.js' import XMLHttpRequest from './polyfill/XMLHttpRequest' import URIUtil from './utils/uri' -const OboeExtended = (arg: string | object) => { +const OboeExtended = (arg: string | Object) => { window.location = '' if(!window.XMLHttpRequest.isRNFBPolyfill ) { window.XMLHttpRequest = XMLHttpRequest - console.warn('Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. You are seeing this warning because you did not replace it maually.') + console.warn( + 'Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. ' + + 'You are seeing this warning because you did not replace it manually.' + ) } if(typeof arg === 'string') { 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)