diff --git a/README.md b/README.md index 09a964c44..8831db5cf 100644 --- a/README.md +++ b/README.md @@ -590,10 +590,11 @@ File Access APIs - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs) - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise) - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise) -- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--array-encodingstringpromise) +- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--arraynumber-encodingstring-promisenumber) - [readFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readfilepath-encodingpromise) -- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersizepromise) -- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstring-appendbooleanpromise) +- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersize-interval-promisernfbreadstream) +- [hash (0.10.9)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithm-promise) +- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstringpromise) - [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise) - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise) - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise) @@ -644,6 +645,45 @@ RNFetchBlob.fs.readStream( When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`. +Since version 0.10.9 `write()` resolves with the `RNFetchBlob` instance so you can promise-chain write calls: + +```js +RNFetchBlob.fs.writeStream( + PATH_TO_FILE, + // encoding, should be one of `base64`, `utf8`, `ascii` + 'utf8', + // should data append to existing content ? + true +) +.then(ofstream => ofstream.write('foo')) +.then(ofstream => ofstream.write('bar')) +.then(ofstream => ofstream.write('foobar')) +.then(ofstream => ofstream.close()) +.catch(console.error) +``` + +or + +```js +RNFetchBlob.fs.writeStream( + PATH_TO_FILE, + // encoding, should be one of `base64`, `utf8`, `ascii` + 'utf8', + // should data append to existing content ? + true +) +.then(stream => Promise.all([ + stream.write('foo'), + stream.write('bar'), + stream.write('foobar') +])) +// Use array destructuring to get the stream object from the first item of the array we get from Promise.all() +.then(([stream]) => stream.close()) +.catch(console.error) +``` + +You should **NOT** do something like this: + ```js RNFetchBlob.fs.writeStream( PATH_TO_FILE, @@ -652,13 +692,18 @@ RNFetchBlob.fs.writeStream( // should data append to existing content ? true) .then((ofstream) => { + // BAD IDEA - Don't do this, those writes are unchecked: ofstream.write('foo') ofstream.write('bar') ofstream.close() }) - +.catch(console.error) // Cannot catch any write() errors! ``` +The problem with the above code is that the promises from the `ofstream.write()` calls are detached and "Lost". +That means the entire promise chain A) resolves without waiting for the writes to finish and B) any errors caused by them are lost. +That code may _seem_ to work if there are no errors, but those writes are of the type "fire and forget": You start them and then turn away and never know if they really succeeded. + ### Cache File Management When using `fileCache` or `path` options along with `fetch` API, response data will automatically store into the file system. The files will **NOT** removed unless you `unlink` it. There're several ways to remove the files diff --git a/android/build.gradle b/android/build.gradle index 919e64ac8..d68693bf1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -34,5 +34,6 @@ android { dependencies { compile 'com.facebook.react:react-native:+' + //compile 'com.squareup.okhttp3:okhttp:+' //{RNFetchBlob_PRE_0.28_DEPDENDENCY} } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7e9ac9256..c60fdf0b6 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Wed May 18 12:33:41 CST 2016 +#Sat Aug 12 07:48:35 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 68cd1d5c3..c4980d445 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -4,6 +4,7 @@ import android.app.DownloadManager; import android.content.Intent; import android.net.Uri; +import android.util.SparseArray; import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Callback; @@ -34,26 +35,23 @@ 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); + private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); + private 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<>(); + private static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); + private static boolean ActionViewVisible = false; + private static SparseArray promiseTable = new SparseArray<>(); public RNFetchBlob(ReactApplicationContext reactContext) { super(reactContext); mClient = OkHttpClientProvider.getOkHttpClient(); - mCookieHandler = new ForwardingCookieHandler(reactContext); - mCookieJarContainer = (CookieJarContainer) mClient.cookieJar(); + ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler(reactContext); + CookieJarContainer mCookieJarContainer = (CookieJarContainer) mClient.cookieJar(); mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler)); RCTContext = reactContext; @@ -85,11 +83,21 @@ public Map getConstants() { } @ReactMethod - public void createFile(final String path, final String content, final String encode, final Callback callback) { + public void createFile(final String path, final String content, final String encode, final Promise promise) { threadPool.execute(new Runnable() { @Override public void run() { - RNFetchBlobFS.createFile(path, content, encode, callback); + RNFetchBlobFS.createFile(path, content, encode, promise); + } + }); + } + + @ReactMethod + public void createFileASCII(final String path, final ReadableArray dataArray, final Promise promise) { + threadPool.execute(new Runnable() { + @Override + public void run() { + RNFetchBlobFS.createFileASCII(path, dataArray, promise); } }); @@ -124,21 +132,10 @@ public void onHostDestroy() { }; RCTContext.addLifecycleEventListener(listener); } catch(Exception ex) { - promise.reject(ex.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", ex.getLocalizedMessage()); } } - @ReactMethod - public void createFileASCII(final String path, final ReadableArray dataArray, final Callback callback) { - threadPool.execute(new Runnable() { - @Override - public void run() { - RNFetchBlobFS.createFileASCII(path, dataArray, callback); - } - }); - - } - @ReactMethod public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) { RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback); @@ -150,8 +147,8 @@ public void unlink(String path, Callback callback) { } @ReactMethod - public void mkdir(String path, Callback callback) { - RNFetchBlobFS.mkdir(path, callback); + public void mkdir(String path, Promise promise) { + RNFetchBlobFS.mkdir(path, promise); } @ReactMethod @@ -176,8 +173,8 @@ public void mv(String path, String dest, Callback callback) { } @ReactMethod - public void ls(String path, Callback callback) { - RNFetchBlobFS.ls(path, callback); + public void ls(String path, Promise promise) { + RNFetchBlobFS.ls(path, promise); } @ReactMethod @@ -355,10 +352,10 @@ public void getContentIntent(String mime, Promise promise) { @ReactMethod public void addCompleteDownload (ReadableMap config, Promise promise) { - DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE); + DownloadManager dm = (DownloadManager) RCTContext.getSystemService(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); + promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path")); return; } try { @@ -375,7 +372,7 @@ public void addCompleteDownload (ReadableMap config, Promise promise) { promise.resolve(null); } catch(Exception ex) { - promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString()); + promise.reject("EUNSPECIFIED", ex.getLocalizedMessage()); } } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java index 8146a99cf..15045cffa 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java @@ -1,5 +1,6 @@ package com.RNFetchBlob; +import android.support.annotation.NonNull; import android.util.Base64; import com.facebook.react.bridge.Arguments; @@ -21,21 +22,20 @@ import okhttp3.RequestBody; import okio.BufferedSink; -public class RNFetchBlobBody extends RequestBody{ +class RNFetchBlobBody extends RequestBody{ - InputStream requestStream; - long contentLength = 0; - ReadableArray form; - String mTaskId; - String rawBody; - RNFetchBlobReq.RequestType requestType; - MediaType mime; - File bodyCache; + private InputStream requestStream; + private long contentLength = 0; + private ReadableArray form; + private String mTaskId; + private String rawBody; + private RNFetchBlobReq.RequestType requestType; + private MediaType mime; + private File bodyCache; int reported = 0; - Boolean chunkedEncoding = false; + private Boolean chunkedEncoding = false; - - public RNFetchBlobBody(String taskId) { + RNFetchBlobBody(String taskId) { this.mTaskId = taskId; } @@ -49,7 +49,7 @@ RNFetchBlobBody setMIME(MediaType mime) { return this; } - RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) { + RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) { this.requestType = type; return this; } @@ -114,7 +114,7 @@ public MediaType contentType() { } @Override - public void writeTo(BufferedSink sink) { + public void writeTo(@NonNull BufferedSink sink) { try { pipeStreamToSink(requestStream, sink); } catch(Exception ex) { @@ -186,8 +186,7 @@ private File createMultipartBodyCache() throws IOException { ArrayList fields = countFormDataLength(); ReactApplicationContext ctx = RNFetchBlob.RCTContext; - for(int i = 0;i < fields.size(); i++) { - FormField field = fields.get(i); + for(FormField field : fields) { String data = field.data; String name = field.name; // skip invalid fields @@ -258,17 +257,14 @@ private File createMultipartBodyCache() throws IOException { * @param sink The request body buffer sink * @throws IOException */ - private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws Exception { - - byte [] chunk = new byte[10240]; + private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException { + byte[] chunk = new byte[10240]; int totalWritten = 0; int read; while((read = stream.read(chunk, 0, 10240)) > 0) { - if(read > 0) { - sink.write(chunk, 0, read); - totalWritten += read; - emitUploadProgress(totalWritten); - } + sink.write(chunk, 0, read); + totalWritten += read; + emitUploadProgress(totalWritten); } stream.close(); } @@ -291,7 +287,7 @@ private void pipeStreamToFileStream(InputStream is, FileOutputStream os) throws /** * Compute approximate content length for form data - * @return + * @return ArrayList */ private ArrayList countFormDataLength() { long total = 0; @@ -300,11 +296,11 @@ private ArrayList countFormDataLength() { for(int i = 0;i < form.size(); i++) { FormField field = new FormField(form.getMap(i)); list.add(field); - String data = field.data; - if(data == null) { + if(field.data == null) { RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly."); } else if (field.filename != null) { + String data = field.data; // upload from storage if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) { String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length()); @@ -333,7 +329,7 @@ else if (field.filename != null) { } // data field else { - total += field.data != null ? field.data.getBytes().length : 0; + total += field.data.getBytes().length; } } contentLength = total; @@ -346,11 +342,11 @@ else if (field.filename != null) { */ private class FormField { public String name; - public String filename; - public String mime; + String filename; + String mime; public String data; - public FormField(ReadableMap rawData) { + FormField(ReadableMap rawData) { if(rawData.hasKey("name")) name = rawData.getString("name"); if(rawData.hasKey("filename")) @@ -368,7 +364,7 @@ public FormField(ReadableMap rawData) { /** * Emit progress event - * @param written + * @param written Integer */ private void emitUploadProgress(int written) { RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId); diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java index 112ce16c3..a5c68b689 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java @@ -3,10 +3,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import java.util.HashMap; - - -public class RNFetchBlobConfig { +class RNFetchBlobConfig { public Boolean fileCache; public String path; diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index bfcd9b588..dde39bc4c 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -1,7 +1,5 @@ package com.RNFetchBlob; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; import android.media.MediaScannerConnection; import android.net.Uri; @@ -22,43 +20,29 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.security.MessageDigest; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.UUID; -public class RNFetchBlobFS { +class RNFetchBlobFS { - ReactApplicationContext mCtx; - DeviceEventManagerModule.RCTDeviceEventEmitter emitter; - String encoding = "base64"; - boolean append = false; - OutputStream writeStreamInstance = null; - static HashMap fileStreams = new HashMap<>(); + private ReactApplicationContext mCtx; + private DeviceEventManagerModule.RCTDeviceEventEmitter emitter; + private String encoding = "base64"; + private OutputStream writeStreamInstance = null; + private static HashMap fileStreams = new HashMap<>(); RNFetchBlobFS(ReactApplicationContext ctx) { this.mCtx = ctx; this.emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); } - static String getExternalFilePath(ReactApplicationContext ctx, String taskId, RNFetchBlobConfig config) { - if(config.path != null) - return config.path; - else if(config.fileCache && config.appendExt != null) - return RNFetchBlobFS.getTmpPath(ctx, taskId) + "." + config.appendExt; - else - return RNFetchBlobFS.getTmpPath(ctx, taskId); - } - /** * Write string with encoding to file * @param path Destination file path. @@ -66,25 +50,37 @@ else if(config.fileCache && config.appendExt != null) * @param data Array passed from JS context. * @param promise RCT Promise */ - static public void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) { + static void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) { try { - int written = 0; + int written; File f = new File(path); File dir = f.getParentFile(); - if(!dir.exists()) - dir.mkdirs(); + + if(!f.exists()) { + if(dir != null && !dir.exists()) { + if (!dir.mkdirs()) { + promise.reject("EUNSPECIFIED", "Failed to create parent directory of '" + path + "'"); + return; + } + } + if(!f.createNewFile()) { + promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created"); + return; + } + } + FileOutputStream fout = new FileOutputStream(f, append); // write data from a file if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) { - data = normalizePath(data); - File src = new File(data); - if(!src.exists()) { - promise.reject("RNfetchBlob writeFile error", "source file : " + data + " does not exist"); + String normalizedData = normalizePath(data); + File src = new File(normalizedData); + if (!src.exists()) { + promise.reject("ENOENT", "No such file '" + path + "' " + "('" + normalizedData + "')"); fout.close(); - return ; + return; } FileInputStream fin = new FileInputStream(src); - byte [] buffer = new byte [10240]; + byte[] buffer = new byte [10240]; int read; written = 0; while((read = fin.read(buffer)) > 0) { @@ -100,8 +96,11 @@ static public void writeFile(String path, String encoding, String data, final bo } fout.close(); promise.resolve(written); + } catch (FileNotFoundException e) { + // According to https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html + promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created, or it is a directory"); } catch (Exception e) { - promise.reject("RNFetchBlob writeFile error", e.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); } } @@ -111,24 +110,37 @@ static public void writeFile(String path, String encoding, String data, final bo * @param data Array passed from JS context. * @param promise RCT Promise */ - static public void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) { - + static void writeFile(String path, ReadableArray data, final boolean append, final Promise promise) { try { - File f = new File(path); File dir = f.getParentFile(); - if(!dir.exists()) - dir.mkdirs(); + + if(!f.exists()) { + if(dir != null && !dir.exists()) { + if (!dir.mkdirs()) { + promise.reject("ENOTDIR", "Failed to create parent directory of '" + path + "'"); + return; + } + } + if(!f.createNewFile()) { + promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created"); + return; + } + } + FileOutputStream os = new FileOutputStream(f, append); - byte [] bytes = new byte[data.size()]; + byte[] bytes = new byte[data.size()]; for(int i=0;i2GB + length = (int) RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength(); + bytes = new byte[length]; InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName); - in.read(bytes, 0, (int) length); + bytesRead = in.read(bytes, 0, length); in.close(); } // issue 287 else if(resolved == null) { InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path)); - int length = (int) in.available(); + // TODO See https://developer.android.com/reference/java/io/InputStream.html#available() + // Quote: "Note that while some implementations of InputStream will return the total number of bytes + // in the stream, many will not. It is never correct to use the return value of this method to + // allocate a buffer intended to hold all data in this stream." + length = in.available(); bytes = new byte[length]; - in.read(bytes); + bytesRead = in.read(bytes); in.close(); } else { File f = new File(path); - int length = (int) f.length(); + length = (int) f.length(); bytes = new byte[length]; FileInputStream in = new FileInputStream(f); - in.read(bytes); + bytesRead = in.read(bytes); in.close(); } + if (bytesRead < length) { + promise.reject("EUNSPECIFIED", "Read only " + bytesRead + " bytes of " + length); + return; + } + switch (encoding.toLowerCase()) { case "base64" : promise.resolve(Base64.encodeToString(bytes, Base64.NO_WRAP)); break; case "ascii" : WritableArray asciiResult = Arguments.createArray(); - for(byte b : bytes) { - asciiResult.pushInt((int)b); + for (byte b : bytes) { + asciiResult.pushInt((int) b); } promise.resolve(asciiResult); break; @@ -189,8 +213,16 @@ else if(resolved == null) { break; } } + catch(FileNotFoundException err) { + String msg = err.getLocalizedMessage(); + if (msg.contains("EISDIR")) { + promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory; " + msg); + } else { + promise.reject("ENOENT", "No such file '" + path + "'; " + msg); + } + } catch(Exception err) { - promise.reject("RNFetchBlob readFile error", err.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", err.getLocalizedMessage()); } } @@ -199,7 +231,7 @@ else if(resolved == null) { * Static method that returns system folders to JS context * @param ctx React Native application context */ - static public Map getSystemfolders(ReactApplicationContext ctx) { + static Map getSystemfolders(ReactApplicationContext ctx) { Map res = new HashMap<>(); res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath()); @@ -222,11 +254,10 @@ static public Map getSystemfolders(ReactApplicationContext ctx) /** * Static method that returns a temp file path - * @param ctx React Native application context * @param taskId An unique string for identify - * @return + * @return String */ - static public String getTmpPath(ReactApplicationContext ctx, String taskId) { + static String getTmpPath(String taskId) { return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId; } @@ -236,12 +267,12 @@ static public String getTmpPath(ReactApplicationContext ctx, String taskId) { * @param encoding File stream decoder, should be one of `base64`, `utf8`, `ascii` * @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) { + void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) { String resolved = normalizePath(path); if(resolved != null) path = resolved; - try { + try { int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096; if(bufferSize > 0) chunkSize = bufferSize; @@ -250,7 +281,6 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f 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) { @@ -287,10 +317,8 @@ else if(resolved == null) { } else if (encoding.equalsIgnoreCase("base64")) { while ((cursor = fs.read(buffer)) != -1) { if(cursor < chunkSize) { - byte [] copy = new byte[cursor]; - for(int i =0;i 0) { out.write(buf, 0, len); } - } catch (Exception err) { callback.invoke(err.getLocalizedMessage()); } finally { @@ -498,10 +566,19 @@ 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("source file at path `" + path + "` does not exist"); + callback.invoke("Source file at path `" + path + "` does not exist"); + return; + } + try { + boolean result = src.renameTo(new File(dest)); + if (!result) { + callback.invoke("mv failed for unknown reasons"); + return; + } + } catch (Exception e) { + callback.invoke(e.getLocalizedMessage()); return; } - src.renameTo(new File(dest)); callback.invoke(); } @@ -511,11 +588,10 @@ static void mv(String path, String dest, Callback callback) { * @param callback JS context callback */ static void exists(String path, Callback callback) { - if(isAsset(path)) { try { String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); - AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(filename); + com.RNFetchBlob.RNFetchBlob.RCTContext.getAssets().openFd(filename); callback.invoke(true, false); } catch (IOException e) { callback.invoke(false, false); @@ -534,48 +610,67 @@ static void exists(String path, Callback callback) { * @param path Target folder * @param callback JS context callback */ - static void ls(String path, Callback callback) { - path = normalizePath(path); - File src = new File(path); - if (!src.exists() || !src.isDirectory()) { - 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(); - WritableArray arg = Arguments.createArray(); - for (String i : files) { - arg.pushString(i); + static void ls(String path, Promise promise) { + try { + path = normalizePath(path); + File src = new File(path); + if (!src.exists()) { + promise.reject("ENOENT", "No such file '" + path + "'"); + return; + } + if (!src.isDirectory()) { + promise.reject("ENOTDIR", "Not a directory '" + path + "'"); + return; + } + String[] files = new File(path).list(); + WritableArray arg = Arguments.createArray(); + // File => list(): "If this abstract pathname does not denote a directory, then this method returns null." + // We excluded that possibility above - ignore the "can produce NullPointerException" warning of the IDE. + for (String i : files) { + arg.pushString(i); + } + promise.resolve(arg); + } catch (Exception e) { + e.printStackTrace(); + promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); } - callback.invoke(null, arg); } /** * Create a file by slicing given file path - * @param src Source file path + * @param path Source file path * @param dest Destination of created file * @param start Start byte offset in source file * @param end End byte offset * @param encode NOT IMPLEMENTED */ - public static void slice(String src, String dest, int start, int end, String encode, Promise promise) { + static void slice(String path, String dest, int start, int end, String encode, Promise promise) { try { - src = normalizePath(src); - File source = new File(src); - if(!source.exists()) { - promise.reject("RNFetchBlob slice error", "source file : " + src + " does not exist"); + path = normalizePath(path); + File source = new File(path); + if(source.isDirectory()){ + promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory"); + return; + } + if(!source.exists()){ + promise.reject("ENOENT", "No such file '" + path + "'"); return; } - long size = source.length(); - long max = Math.min(size, end); - long expected = max - start; - long now = 0; - FileInputStream in = new FileInputStream(new File(src)); + int size = (int) source.length(); + int max = Math.min(size, end); + int expected = max - start; + int now = 0; + FileInputStream in = new FileInputStream(new File(path)); FileOutputStream out = new FileOutputStream(new File(dest)); - in.skip(start); - byte [] buffer = new byte[10240]; + int skipped = (int) in.skip(start); + if (skipped != start) { + promise.reject("EUNSPECIFIED", "Skipped " + skipped + " instead of the specified " + start + " bytes, size is " + size); + return; + } + byte[] buffer = new byte[10240]; while(now < expected) { - long read = in.read(buffer, 0, 10240); - long remain = expected - now; + int read = in.read(buffer, 0, 10240); + int remain = expected - now; if(read <= 0) { break; } @@ -588,7 +683,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("RNFetchBlob slice error", e.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); } } @@ -610,6 +705,8 @@ protected Integer doInBackground(String ...args) { } if(src.isDirectory()) { String [] files = src.list(); + // File => list(): "If this abstract pathname does not denote a directory, then this method returns null." + // We excluded that possibility above - ignore the "can produce NullPointerException" warning of the IDE. for(String p : files) { res.pushMap(statFile(src.getPath() + "/" + p)); } @@ -625,8 +722,8 @@ protected Integer doInBackground(String ...args) { /** * show status of a file or directory - * @param path - * @param callback + * @param path Path + * @param callback Callback */ static void stat(String path, Callback callback) { try { @@ -643,8 +740,8 @@ static void stat(String path, Callback callback) { /** * Basic stat method - * @param path - * @return Stat result of a file or path + * @param path Path + * @return Stat Result of a file or path */ static WritableMap statFile(String path) { try { @@ -680,9 +777,9 @@ static WritableMap statFile(String path) { /** * Media scanner scan file - * @param path - * @param mimes - * @param callback + * @param path Path to file + * @param mimes Array of MIME type strings + * @param callback Callback for results */ void scanFile(String [] path, String[] mimes, final Callback callback) { try { @@ -708,17 +805,20 @@ static void hash(String path, String algorithm, Promise promise) { algorithms.put("sha384", "SHA-384"); algorithms.put("sha512", "SHA-512"); - if (!algorithms.containsKey(algorithm)) throw new Exception("Invalid hash algorithm"); + if (!algorithms.containsKey(algorithm)) { + promise.reject("EINVAL", "Invalid algorithm '" + algorithm + "', must be one of md5, sha1, sha224, sha256, sha384, sha512"); + return; + } File file = new File(path); if (file.isDirectory()) { - promise.reject("hash error", "EISDIR: illegal operation on a directory, read"); + promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory"); return; } if (!file.exists()) { - promise.reject("hash error", "ENOENT: no such file or directory, open '" + path + "'"); + promise.reject("ENOENT", "No such file '" + path + "'"); return; } @@ -737,9 +837,9 @@ static void hash(String path, String algorithm, Promise promise) { hexString.append(String.format("%02x", digestByte)); promise.resolve(hexString.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - promise.reject("hash error", ex.getLocalizedMessage()); + } catch (Exception e) { + e.printStackTrace(); + promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); } } @@ -748,9 +848,9 @@ static void hash(String path, String algorithm, Promise promise) { * @param path The destination path of the new file. * @param data Initial data of the new file. * @param encoding Encoding of initial data. - * @param callback RCT bridge callback. + * @param promise Promise for Javascript */ - static void createFile(String path, String data, String encoding, Callback callback) { + static void createFile(String path, String data, String encoding, Promise promise) { try { File dest = new File(path); boolean created = dest.createNewFile(); @@ -758,7 +858,7 @@ 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("source file : " + data + " does not exist"); + promise.reject("ENOENT", "Source file : " + data + " does not exist"); return ; } FileInputStream fin = new FileInputStream(src); @@ -773,17 +873,15 @@ static void createFile(String path, String data, String encoding, Callback callb ostream.close(); } else { if (!created) { - 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."); + promise.reject("EEXIST", "File `" + path + "` already exists"); return; } OutputStream ostream = new FileOutputStream(dest); ostream.write(RNFetchBlobFS.stringToBytes(data, encoding)); } - callback.invoke(null, path); + promise.resolve(path); } catch(Exception err) { - callback.invoke(err.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", err.getLocalizedMessage()); } } @@ -791,30 +889,25 @@ static void createFile(String path, String data, String encoding, Callback callb * Create file for ASCII encoding * @param path Path of new file. * @param data Content of new file - * @param callback JS context callback + * @param promise JS Promise */ - static void createFileASCII(String path, ReadableArray data, Callback callback) { + static void createFileASCII(String path, ReadableArray data, Promise promise) { try { File dest = new File(path); - if(dest.exists()) { - callback.invoke("failed to create new file at path `" + path + "`, file already exists."); - return; - } boolean created = dest.createNewFile(); if(!created) { - callback.invoke("failed to create new file at path `" + path + "` because its parent path may not exist"); + promise.reject("EEXIST", "File at path `" + path + "` already exists"); return; } OutputStream ostream = new FileOutputStream(dest); - byte [] chunk = new byte[data.size()]; - for(int i =0; i task = new AsyncTask() { @Override protected Integer doInBackground(ReadableArray ...paths) { try { + ArrayList failuresToDelete = new ArrayList<>(); for (int i = 0; i < paths[0].size(); i++) { - File f = new File(paths[0].getString(i)); - if (f.exists()) - f.delete(); + String fileName = paths[0].getString(i); + File f = new File(fileName); + if (f.exists()) { + boolean result = f.delete(); + if (!result) { + failuresToDelete.add(fileName); + } + } + } + if (failuresToDelete.isEmpty()) { + callback.invoke(null, true); + } else { + StringBuilder listString = new StringBuilder(); + listString.append("Failed to delete: "); + for (String s : failuresToDelete) { + listString.append(s).append(", "); + } + callback.invoke(listString.toString()); } - callback.invoke(null, true); } catch(Exception err) { callback.invoke(err.getLocalizedMessage()); } @@ -891,6 +998,7 @@ private void emitStreamEvent(String streamName, String event, String data) { this.emitter.emit(streamName, eventData); } + // "event" always is "data"... private void emitStreamEvent(String streamName, String event, WritableArray data) { WritableMap eventData = Arguments.createMap(); eventData.putString("event", event); @@ -898,12 +1006,13 @@ private void emitStreamEvent(String streamName, String event, WritableArray data this.emitter.emit(streamName, eventData); } - // TODO : should we remove this ? - void emitFSData(String taskId, String event, String data) { + // "event" always is "error"... + private void emitStreamEvent(String streamName, String event, String code, String message) { WritableMap eventData = Arguments.createMap(); eventData.putString("event", event); - eventData.putString("detail", data); - this.emitter.emit("RNFetchBlobStream" + taskId, eventData); + eventData.putString("code", code); + eventData.putString("detail", message); + this.emitter.emit(streamName, eventData); } /** @@ -911,9 +1020,9 @@ void emitFSData(String taskId, String event, String data) { * the stream is created by Assets Manager, otherwise use FileInputStream. * @param path The file to open stream * @return InputStream instance - * @throws IOException + * @throws IOException If the given file does not exist or is a directory FileInputStream will throw a FileNotFoundException */ - static InputStream inputStreamFromPath(String path) throws IOException { + private static InputStream inputStreamFromPath(String path) throws IOException { if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { return RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); } @@ -925,10 +1034,10 @@ static InputStream inputStreamFromPath(String path) throws IOException { * @param path A file path URI string * @return A boolean value represents if the path exists. */ - static boolean isPathExists(String path) { + private static boolean isPathExists(String path) { if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { try { - RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); + RNFetchBlob.RCTContext.getAssets().open(path.replace(com.RNFetchBlob.RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); } catch (IOException e) { return false; } @@ -941,9 +1050,7 @@ static boolean isPathExists(String path) { } static boolean isAsset(String path) { - if(path != null) - return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET); - return false; + return path != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET); } /** diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java index 4540ec464..6b97bef9e 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java @@ -5,17 +5,17 @@ */ public class RNFetchBlobProgressConfig { - public enum ReportType { + enum ReportType { Upload, Download }; - long lastTick = 0; - int tick = 0; - int count = -1; - public int interval = -1; - public boolean enable = false; - public ReportType type = ReportType.Download; + private long lastTick = 0; + private int tick = 0; + private int count = -1; + private int interval = -1; + private boolean enable = false; + private ReportType type = ReportType.Download; RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) { this.enable = report; diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 5e66e48a6..aaa837cee 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -8,6 +8,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; +import android.support.annotation.NonNull; import android.util.Base64; import com.RNFetchBlob.Response.RNFetchBlobDefaultResp; @@ -22,8 +23,11 @@ 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 com.facebook.react.modules.network.TLSSocketFactory; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileOutputStream; @@ -37,12 +41,16 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.security.KeyStore; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.HashMap; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLSocketFactory; + import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.ConnectionSpec; @@ -84,7 +92,6 @@ enum ResponseFormat { static HashMap uploadProgressReport = new HashMap<>(); static ConnectionPool pool = new ConnectionPool(); - ReactApplicationContext ctx; RNFetchBlobConfig options; String taskId; String method; @@ -170,7 +177,7 @@ public void run() { } // set headers ReadableMapKeySetIterator it = headers.keySetIterator(); - if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable") == true ) { + if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable")) { req.allowScanningByMediaScanner(); } while (it.hasNextKey()) { @@ -197,7 +204,7 @@ public void run() { cacheKey = this.taskId; } - File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext); + File file = new File(RNFetchBlobFS.getTmpPath(cacheKey) + ext); if (file.exists()) { callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath()); @@ -208,7 +215,7 @@ public void run() { if(this.options.path != null) this.destPath = this.options.path; else if(this.options.fileCache) - this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext; + this.destPath = RNFetchBlobFS.getTmpPath(cacheKey) + ext; OkHttpClient.Builder clientBuilder; @@ -330,7 +337,7 @@ public Response intercept(Chain chain) throws IOException { // Add request interceptor for upload progress event clientBuilder.addInterceptor(new Interceptor() { @Override - public Response intercept(Chain chain) throws IOException { + public Response intercept(@NonNull Chain chain) throws IOException { try { Response originalResponse = chain.proceed(req); ResponseBody extended; @@ -392,7 +399,7 @@ public Response intercept(Chain chain) throws IOException { call.enqueue(new okhttp3.Callback() { @Override - public void onFailure(Call call, IOException e) { + public void onFailure(@NonNull Call call, IOException e) { cancelTask(taskId); if(respInfo == null) { respInfo = Arguments.createMap(); @@ -409,7 +416,7 @@ public void onFailure(Call call, IOException e) { } @Override - public void onResponse(Call call, Response response) throws IOException { + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { ReadableMap notifyConfig = options.addAndroidDownloads; // Download manager settings if(notifyConfig != null ) { @@ -470,11 +477,11 @@ private void done(Response resp) { // For XMLHttpRequest, automatic response data storing strategy, when response // data is considered as binary data, write it to file system if(isBlobResp && options.auto) { - String dest = RNFetchBlobFS.getTmpPath(ctx, taskId); + String dest = RNFetchBlobFS.getTmpPath(taskId); InputStream ins = resp.body().byteStream(); FileOutputStream os = new FileOutputStream(new File(dest)); int read; - byte [] buffer = new byte[10240]; + byte[] buffer = new byte[10240]; while ((read = ins.read(buffer)) != -1) { os.write(buffer, 0, read); } @@ -670,11 +677,11 @@ public void onReceive(Context context, Intent intent) { 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); + cursor.close(); } } } @@ -708,7 +715,19 @@ 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()); + // Code from https://stackoverflow.com/a/40874952/544779 + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); + } + X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, new TrustManager[] { trustManager }, null); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + client.sslSocketFactory(sslSocketFactory, trustManager); ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java index 6e976d775..6dbcbe7c7 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java @@ -27,7 +27,7 @@ public static String getMD5(String input) { md.update(input.getBytes()); byte[] digest = md.digest(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b & 0xff)); @@ -37,6 +37,7 @@ public static String getMD5(String input) { } catch (Exception ex) { ex.printStackTrace(); } finally { + // TODO: Is discarding errors the intent? (https://www.owasp.org/index.php/Return_Inside_Finally_Block) return result; } diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java index 88fc7c69a..e4f725777 100644 --- a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java +++ b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java @@ -1,5 +1,6 @@ package com.RNFetchBlob.Response; +import android.support.annotation.NonNull; import android.util.Log; import com.RNFetchBlob.RNFetchBlobConst; @@ -48,11 +49,11 @@ public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseB File f = new File(path); File parent = f.getParentFile(); - if(!parent.exists() && !parent.mkdirs()){ + if(parent != null && !parent.exists() && !parent.mkdirs()){ throw new IllegalStateException("Couldn't create dir: " + parent); } - if(f.exists() == false) + if(!f.exists()) f.createNewFile(); ofStream = new FileOutputStream(new File(path), appendToExistingFile); } @@ -76,7 +77,7 @@ public BufferedSource source() { private class ProgressReportingSource implements Source { @Override - public long read(Buffer sink, long byteCount) throws IOException { + public long read(@NonNull Buffer sink, long byteCount) throws IOException { try { byte[] bytes = new byte[(int) byteCount]; long read = originalBody.byteStream().read(bytes, 0, (int) byteCount); diff --git a/class/RNFetchBlobReadStream.js b/class/RNFetchBlobReadStream.js index fe514b35c..84585d99a 100644 --- a/class/RNFetchBlobReadStream.js +++ b/class/RNFetchBlobReadStream.js @@ -35,7 +35,7 @@ export default class RNFetchBlobReadStream { // register for file stream event let subscription = emitter.addListener(this.streamId, (e) => { - let {event, detail} = e + let {event, code, detail} = e if(this._onData && event === 'data') { this._onData(detail) return @@ -44,10 +44,12 @@ export default class RNFetchBlobReadStream { this._onEnd(detail) } else { + const err = new Error(detail) + err.code = code || 'EUNSPECIFIED' if(this._onError) - this._onError(detail) + this._onError(err) else - throw new Error(detail) + throw err } // when stream closed or error, remove event handler if (event === 'error' || event === 'end') { diff --git a/fs.js b/fs.js index 7423c2d01..a286e937a 100644 --- a/fs.js +++ b/fs.js @@ -2,36 +2,33 @@ // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file. -import { - NativeModules, - DeviceEventEmitter, - Platform, - NativeAppEventEmitter, -} from 'react-native' +// import type {RNFetchBlobConfig, RNFetchBlobNative, RNFetchBlobStream} from './types' + +import {NativeModules, Platform} from 'react-native' import RNFetchBlobSession from './class/RNFetchBlobSession' import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream' import RNFetchBlobReadStream from './class/RNFetchBlobReadStream' import RNFetchBlobFile from './class/RNFetchBlobFile' -import type { - RNFetchBlobNative, - RNFetchBlobConfig, - RNFetchBlobStream -} from './types' -const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob +const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob const dirs = { - DocumentDir : RNFetchBlob.DocumentDir, - CacheDir : RNFetchBlob.CacheDir, - PictureDir : RNFetchBlob.PictureDir, - MusicDir : RNFetchBlob.MusicDir, - MovieDir : RNFetchBlob.MovieDir, - DownloadDir : RNFetchBlob.DownloadDir, - DCIMDir : RNFetchBlob.DCIMDir, - SDCardDir : RNFetchBlob.SDCardDir, - SDCardApplicationDir : RNFetchBlob.SDCardApplicationDir, - MainBundleDir : RNFetchBlob.MainBundleDir, - LibraryDir : RNFetchBlob.LibraryDir + DocumentDir: RNFetchBlob.DocumentDir, + CacheDir: RNFetchBlob.CacheDir, + PictureDir: RNFetchBlob.PictureDir, + MusicDir: RNFetchBlob.MusicDir, + MovieDir: RNFetchBlob.MovieDir, + DownloadDir: RNFetchBlob.DownloadDir, + DCIMDir: RNFetchBlob.DCIMDir, + SDCardDir: RNFetchBlob.SDCardDir, + SDCardApplicationDir: RNFetchBlob.SDCardApplicationDir, + MainBundleDir: RNFetchBlob.MainBundleDir, + LibraryDir: RNFetchBlob.LibraryDir +} + +function addCode(code: string, error: Error): Error { + error.code = code + return error } /** @@ -39,9 +36,9 @@ const dirs = { * @param {string} name Stream ID * @return {RNFetchBlobSession} */ -function session(name:string):RNFetchBlobSession { +function session(name: string): RNFetchBlobSession { let s = RNFetchBlobSession.getSession(name) - if(s) + if (s) return new RNFetchBlobSession(name) else { RNFetchBlobSession.setSession(name, []) @@ -49,34 +46,24 @@ function session(name:string):RNFetchBlobSession { } } -function asset(path:string):string { - if(Platform.OS === 'ios') { +function asset(path: string): string { + if (Platform.OS === 'ios') { // path from camera roll - if(/^assets-library\:\/\//.test(path)) + if (/^assets-library\:\/\//.test(path)) return path } return 'bundle-assets://' + path } -function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise { - encoding = encoding || 'utf8' - return new Promise((resolve, reject) => { - let handler = (err) => { - if(err) - reject(new Error(err)) - else - resolve() - } - if(encoding.toLowerCase() === 'ascii') { - if(Array.isArray(data)) - RNFetchBlob.createFileASCII(path, data, handler) - else - reject(new Error('`data` of ASCII file must be an array contains numbers')) - } - else { - RNFetchBlob.createFile(path, data, encoding, handler) - } - }) +function createFile(path: string, data: string, encoding: 'base64' | 'ascii' | 'utf8' = 'utf8'): Promise { + if (encoding.toLowerCase() === 'ascii') { + return Array.isArray(data) ? + RNFetchBlob.createFileASCII(path, data) : + Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers'))) + } + else { + return RNFetchBlob.createFile(path, data, encoding) + } } /** @@ -87,18 +74,20 @@ function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'ut * @return {Promise} A promise resolves a `WriteStream` object. */ function writeStream( - path : string, - encoding : 'utf8' | 'ascii' | 'base64', - append? : ?boolean, -):Promise { - if(!path) - throw Error('RNFetchBlob could not open file stream with empty `path`') - encoding = encoding || 'utf8' - append = append || false + path: string, + encoding?: 'utf8' | 'ascii' | 'base64' = 'utf8', + append?: boolean = false, +): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } return new Promise((resolve, reject) => { - RNFetchBlob.writeStream(path, encoding || 'base64', append || false, (err, streamId:string) => { - if(err) - reject(new Error(err)) + RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => { + if (errMsg) { + const err = new Error(errMsg) + err.code = errCode + reject(err) + } else resolve(new RNFetchBlobWriteStream(streamId, encoding)) }) @@ -114,11 +103,14 @@ function writeStream( * @return {RNFetchBlobStream} RNFetchBlobStream stream instance. */ function readStream( - path : string, - encoding : 'utf8' | 'ascii' | 'base64', - bufferSize? : ?number, - tick : ?number = 10 -):Promise { + path: string, + encoding: 'utf8' | 'ascii' | 'base64' = 'utf8', + bufferSize?: number, + tick?: number = 10 +): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick)) } @@ -127,17 +119,11 @@ function readStream( * @param {string} path Path of directory to be created * @return {Promise} */ -function mkdir(path:string):Promise { - - return new Promise((resolve, reject) => { - RNFetchBlob.mkdir(path, (err, res) => { - if(err) - reject(new Error(err)) - else - resolve() - }) - }) - +function mkdir(path: string): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } + return RNFetchBlob.mkdir(path) } /** @@ -145,8 +131,8 @@ function mkdir(path:string):Promise { * @param {string} groupName Name of app group * @return {Promise} */ -function pathForAppGroup(groupName:string):Promise { - return RNFetchBlob.pathForAppGroup(groupName); +function pathForAppGroup(groupName: string): Promise { + return RNFetchBlob.pathForAppGroup(groupName) } /** @@ -155,9 +141,10 @@ function pathForAppGroup(groupName:string):Promise { * @param {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream. * @return {Promise | string>} */ -function readFile(path:string, encoding:string):Promise { - if(typeof path !== 'string') - return Promise.reject(new Error('Invalid argument "path" ')) +function readFile(path: string, encoding: string = 'utf8'): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } return RNFetchBlob.readFile(path, encoding) } @@ -168,37 +155,43 @@ function readFile(path:string, encoding:string):Promise { * @param {string} encoding Encoding of data (Optional). * @return {Promise} */ -function writeFile(path:string, data:string | Array, encoding:?string):Promise { - encoding = encoding || 'utf8' - if(typeof path !== 'string') - 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}`)) +function writeFile(path: string, data: string | Array, encoding: ?string = 'utf8'): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } + if (encoding.toLocaleLowerCase() === 'ascii') { + if (!Array.isArray(data)) { + return Promise.reject(addCode('EINVAL', new TypeError('"data" must be an Array when encoding is "ascii"'))) + } else - return RNFetchBlob.writeFileArray(path, data, false); - } else { - if(typeof data !== 'string') - return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) + return RNFetchBlob.writeFileArray(path, data, false) + } + else { + if (typeof data !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`))) + } else - return RNFetchBlob.writeFile(path, encoding, data, false); + return RNFetchBlob.writeFile(path, encoding, data, false) } } -function appendFile(path:string, data:string | Array, encoding:?string):Promise { - encoding = encoding || 'utf8' - if(typeof path !== 'string') - 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}`)) +function appendFile(path: string, data: string | Array, encoding?: string = 'utf8'): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } + if (encoding.toLocaleLowerCase() === 'ascii') { + if (!Array.isArray(data)) { + return Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers'))) + } else - return RNFetchBlob.writeFileArray(path, data, true); - } else { - if(typeof data !== 'string') - return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) + return RNFetchBlob.writeFileArray(path, data, true) + } + else { + if (typeof data !== 'string') { + return Promise.reject(addCode('EINVAL'), new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`)) + } else - return RNFetchBlob.writeFile(path, encoding, data, true); + return RNFetchBlob.writeFile(path, encoding, data, true) } } @@ -207,13 +200,16 @@ function appendFile(path:string, data:string | Array, encoding:?string): * @param {string} path Target path * @return {RNFetchBlobFile} */ -function stat(path:string):Promise { +function stat(path: string): Promise { return new Promise((resolve, reject) => { + if (typeof path !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } RNFetchBlob.stat(path, (err, stat) => { - if(err) + if (err) reject(new Error(err)) else { - if(stat) { + if (stat) { stat.size = parseInt(stat.size) stat.lastModified = parseInt(stat.lastModified) } @@ -228,11 +224,14 @@ function stat(path:string):Promise { * @param {Array>} pairs Array contains Key value pairs with key `path` and `mime`. * @return {Promise} */ -function scanFile(pairs:any):Promise { +function scanFile(pairs: any): Promise { return new Promise((resolve, reject) => { + if (pairs === undefined) { + return reject(addCode('EINVAL', new TypeError('Missing argument'))) + } RNFetchBlob.scanFile(pairs, (err) => { - if(err) - reject(new Error(err)) + if (err) + reject(addCode('EUNSPECIFIED', new Error(err))) else resolve() }) @@ -240,56 +239,59 @@ function scanFile(pairs:any):Promise { } function hash(path: string, algorithm: string): Promise { - if(typeof path !== 'string') - return Promise.reject(new Error('Invalid argument "path" ')) - if(typeof algorithm !== 'string') - return Promise.reject(new Error('Invalid argument "algorithm" ')) - + if (typeof path !== 'string' || typeof algorithm !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "algorithm"'))) + } return RNFetchBlob.hash(path, algorithm) } -function cp(path:string, dest:string):Promise { +function cp(path: string, dest: string): Promise { return new Promise((resolve, reject) => { + if (typeof path !== 'string' || typeof dest !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"'))) + } RNFetchBlob.cp(path, dest, (err, res) => { - if(err) - reject(new Error(err)) + if (err) + reject(addCode('EUNSPECIFIED', new Error(err))) else resolve(res) }) }) } -function mv(path:string, dest:string):Promise { +function mv(path: string, dest: string): Promise { return new Promise((resolve, reject) => { + if (typeof path !== 'string' || typeof dest !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"'))) + } RNFetchBlob.mv(path, dest, (err, res) => { - if(err) - reject(new Error(err)) + if (err) + reject(addCode('EUNSPECIFIED', new Error(err))) else resolve(res) }) }) } -function lstat(path:string):Promise> { +function lstat(path: string): Promise> { return new Promise((resolve, reject) => { + if (typeof path !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } RNFetchBlob.lstat(path, (err, stat) => { - if(err) - reject(new Error(err)) + if (err) + reject(addCode('EUNSPECIFIED', new Error(err))) else resolve(stat) }) }) } -function ls(path:string):Promise> { - return new Promise((resolve, reject) => { - RNFetchBlob.ls(path, (err, res) => { - if(err) - reject(new Error(err)) - else - resolve(res) - }) - }) +function ls(path: string): Promise> { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } + return RNFetchBlob.ls(path) } /** @@ -297,11 +299,14 @@ function ls(path:string):Promise> { * @param {string} path:string Path of target file. * @return {Promise} */ -function unlink(path:string):Promise { +function unlink(path: string): Promise { return new Promise((resolve, reject) => { + if (typeof path !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } RNFetchBlob.unlink(path, (err) => { - if(err) { - reject(new Error(err)) + if (err) { + reject(addCode('EUNSPECIFIED', new Error(err))) } else resolve() @@ -312,62 +317,72 @@ 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) => { + if (typeof path !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } try { RNFetchBlob.exists(path, (exist) => { resolve(exist) }) - } catch(err) { - reject(new Error(err)) + }catch (err){ + reject(addCode('EUNSPECIFIED', new Error(err))) } }) } -function slice(src:string, dest:string, start:number, end:number):Promise { +function slice(src: string, dest: string, start: number, end: number): Promise { + if (typeof src !== 'string' || typeof dest !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "src" and/or "destination"'))) + } + let p = Promise.resolve() let size = 0 + function normalize(num, size) { - if(num < 0) + if (num < 0) return Math.max(0, size + num) - if(!num && num !== 0) + if (!num && num !== 0) return size return num } - if(start < 0 || end < 0 || !start || !end) { + + if (start < 0 || end < 0 || !start || !end) { p = p.then(() => stat(src)) - .then((stat) => { - size = Math.floor(stat.size) - start = normalize(start || 0, size) - end = normalize(end, size) - return Promise.resolve() - }) + .then((stat) => { + size = Math.floor(stat.size) + start = normalize(start || 0, size) + end = normalize(end, size) + }) } return p.then(() => RNFetchBlob.slice(src, dest, start, end)) } -function isDir(path:string):Promise { - +function isDir(path: string): Promise { return new Promise((resolve, reject) => { + if (typeof path !== 'string') { + return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } try { RNFetchBlob.exists(path, (exist, isDir) => { resolve(isDir) }) - } catch(err) { - reject(new Error(err)) + }catch (err){ + reject(addCode('EUNSPECIFIED', new Error(err))) } }) } -function df():Promise<{ free : number, total : number }> { +function df(): Promise<{ free: number, total: number }> { return new Promise((resolve, reject) => { RNFetchBlob.df((err, stat) => { - if(err) - reject(new Error(err)) + if (err) + reject(addCode('EUNSPECIFIED', new Error(err))) else resolve(stat) }) diff --git a/index.js b/index.js index a4812cb7d..bd67986fa 100644 --- a/index.js +++ b/index.js @@ -540,7 +540,6 @@ class FetchBlobResponse { */ this.readFile = (encoding: 'base64' | 'utf8' | 'ascii') => { if(this.type === 'path') { - encoding = encoding || 'utf8' return readFile(this.data, encoding) } else { diff --git a/ios.js b/ios.js index 9d04361d6..8e4cf140a 100644 --- a/ios.js +++ b/ios.js @@ -43,7 +43,7 @@ function openDocument(path:string, scheme:string) { * @param {string} url URL of the resource, only file URL is supported * @return {Promise} */ -function excludeFromBackupKey(url:string) { +function excludeFromBackupKey(path:string) { return RNFetchBlob.excludeFromBackupKey('file://' + path); } diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 78b1cd4bb..47bda76c5 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -135,8 +135,12 @@ - (NSDictionary *)constantsToExport } #pragma mark - fs.createFile -RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) { - +RCT_EXPORT_METHOD(createFile:(NSString *)path + data:(NSString *)data + encoding:(NSString *)encoding + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ NSFileManager * fm = [NSFileManager defaultManager]; NSData * fileContent = nil; @@ -154,18 +158,25 @@ - (NSDictionary *)constantsToExport fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]]; } - BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL]; - if(success == YES) - callback(@[[NSNull null]]); - else - callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]); - + if ([fm fileExistsAtPath:path]) { + reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil); + } + else { + BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL]; + if(success == YES) + resolve(@[[NSNull null]]); + else + reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create new file at path '%@', please ensure the folder exists", path], nil); + } } #pragma mark - fs.createFileASCII // method for create file with ASCII content -RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray callback:(RCTResponseSenderBlock)callback) { - +RCT_EXPORT_METHOD(createFileASCII:(NSString *)path + data:(NSArray *)dataArray + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ NSFileManager * fm = [NSFileManager defaultManager]; NSMutableData * fileContent = [NSMutableData alloc]; // prevent stack overflow, alloc on heap @@ -174,14 +185,21 @@ - (NSDictionary *)constantsToExport for(int i = 0; i < dataArray.count; i++) { bytes[i] = [[dataArray objectAtIndex:i] charValue]; } + [fileContent appendBytes:bytes length:dataArray.count]; - BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL]; - free(bytes); - if(success == YES) - callback(@[[NSNull null]]); - else - callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]); + if ([fm fileExistsAtPath:path]) { + reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil); + } + else { + BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL]; + if(success == YES) + resolve(@[[NSNull null]]); + else + reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"failed to create new file at path '%@', please ensure the folder exists", path], nil); + } + + free(bytes); } #pragma mark - fs.pathForAppGroup @@ -194,7 +212,7 @@ - (NSDictionary *)constantsToExport if(path) { resolve(path); } else { - reject(@"RNFetchBlob pathForAppGroup Error", @"could not find path for app group", nil); + reject(@"EUNSPECIFIED", @"could not find path for app group", nil); } } @@ -220,14 +238,26 @@ - (NSDictionary *)constantsToExport { RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge]; NSFileManager * fm = [NSFileManager defaultManager]; - BOOL isDir = nil; - BOOL exist = [fm fileExistsAtPath:path isDirectory:&isDir]; - if( exist == NO || isDir == YES) { - callback(@[[NSString stringWithFormat:@"target path `%@` may not exist or it is a folder", path]]); - return; + NSString * folder = [path stringByDeletingLastPathComponent]; + NSError* err = nil; + BOOL isDir = NO; + BOOL exists = [fm fileExistsAtPath:path isDirectory: &isDir]; + + if(!exists) { + [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; + if(err != nil) { + callback(@[@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]]]); + } + if(![fm createFileAtPath:path contents:nil attributes:nil]) { + callback(@[@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path]]); + } } + else if(isDir) { + callback(@[@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]]); + } + NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append]; - callback(@[[NSNull null], streamId]); + callback(@[[NSNull null], [NSNull null], streamId]); } #pragma mark - fs.writeArrayChunk @@ -291,24 +321,25 @@ - (NSDictionary *)constantsToExport } #pragma mark - fs.ls -RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(ls:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSFileManager* fm = [NSFileManager defaultManager]; BOOL exist = nil; BOOL isDir = nil; exist = [fm fileExistsAtPath:path isDirectory:&isDir]; - if(exist == NO || isDir == NO) { - callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not a folder", path]]); - return ; + if(exist == NO) { + return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil); + } + if(isDir == NO) { + return reject(@"ENOTDIR", [NSString stringWithFormat:@"Not a directory '%@'", path], nil); } NSError * error = nil; NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; if(error == nil) - callback(@[[NSNull null], result == nil ? [NSNull null] :result ]); + resolve(result); else - callback(@[[error localizedDescription], [NSNull null]]); - + reject(@"EUNSPECIFIED", [error description], nil); } #pragma mark - fs.stat @@ -389,7 +420,6 @@ - (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; @@ -400,6 +430,7 @@ - (NSDictionary *)constantsToExport } else { + // If the destination exists there will be an error BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error]; if(error == nil) @@ -408,7 +439,6 @@ - (NSDictionary *)constantsToExport callback(@[[error localizedDescription], @NO]); } }]; - } @@ -426,15 +456,9 @@ - (NSDictionary *)constantsToExport } #pragma mark - fs.mkdir -RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(mkdir:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if([[NSFileManager defaultManager] fileExistsAtPath:path]) { - callback(@[@"mkdir failed, folder already exists"]); - return; - } - else - [RNFetchBlobFS mkdir:path]; - callback(@[[NSNull null]]); + [RNFetchBlobFS mkdir:path resolver:resolve rejecter:reject]; } #pragma mark - fs.readFile @@ -443,19 +467,16 @@ - (NSDictionary *)constantsToExport resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - - [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(id content, NSString * err) { - if(err != nil) - { - reject(@"RNFetchBlob readFile Error", err, nil); + + [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(NSData * content, NSString * code, NSString * err) { + if(err != nil) { + reject(code, err, nil); return; } - if(encoding == @"ascii") - { + if(encoding == @"ascii") { resolve((NSMutableArray *)content); } - else - { + else { resolve((NSString *)content); } }]; @@ -538,7 +559,7 @@ - (NSDictionary *)constantsToExport }); resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob previewDocument Error", @"scheme is not supported", nil); + reject(@"EINVAL", @"scheme is not supported", nil); } } @@ -558,7 +579,7 @@ - (NSDictionary *)constantsToExport }); resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob openDocument Error", @"scheme is not supported", nil); + reject(@"EINVAL", @"scheme is not supported", nil); } } @@ -572,7 +593,7 @@ - (NSDictionary *)constantsToExport { resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob excludeFromBackupKey Error", [error description], nil); + reject(@"EUNSPECIFIED", [error description], nil); } } diff --git a/ios/RNFetchBlobFS.h b/ios/RNFetchBlobFS.h index 97386ef9d..7cf0e4faa 100644 --- a/ios/RNFetchBlobFS.h +++ b/ios/RNFetchBlobFS.h @@ -58,11 +58,16 @@ // fs methods + (RNFetchBlobFS *) getFileStreams; + (BOOL) mkdir:(NSString *) path; ++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject; ++ (void) hash:(NSString *)path + algorithm:(NSString *)algorithm + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject; + (NSDictionary *) stat:(NSString *) path error:(NSError **) error; + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback; + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject; + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject; -+ (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString * errMsg))onComplete; ++ (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString* code, NSString * errMsg))onComplete; + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock; + (void) slice:(NSString *)path dest:(NSString *)dest diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index e906575c5..1d16ffa40 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -13,6 +13,8 @@ #import "IOS7Polyfill.h" @import AssetsLibrary; +#import + #if __has_include() #import #import @@ -164,7 +166,7 @@ + (void) readStream:(NSString *)uri if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO) { NSString * message = [NSString stringWithFormat:@"File does not exist at path %@", path]; - NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": message }; + NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"ENOENT", @"detail": message }; [event sendDeviceEventWithName:streamId body:payload]; free(buffer); return ; @@ -197,7 +199,7 @@ + (void) readStream:(NSString *)uri } else { - NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": @"RNFetchBlob.readStream unable to resolve URI" }; + NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EINVAL", @"detail": @"Unable to resolve URI" }; [event sendDeviceEventWithName:streamId body:payload]; } // release buffer @@ -207,7 +209,7 @@ + (void) readStream:(NSString *)uri } @catch (NSError * err) { - NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": [NSString stringWithFormat:@"RNFetchBlob.readStream error %@", [err description]] }; + NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EUNSPECIFIED", @"detail": [err description] }; [event sendDeviceEventWithName:streamId body:payload]; } @finally @@ -330,7 +332,12 @@ + (NSNumber *) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append: # pragma mark - write file -+ (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject ++ (void) writeFile:(NSString *)path + encoding:(NSString *)encoding + data:(NSString *)data + append:(BOOL)append + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject { @try { NSFileManager * fm = [NSFileManager defaultManager]; @@ -339,7 +346,16 @@ + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString // after the folders created, write data into the file NSString * folder = [path stringByDeletingLastPathComponent]; encoding = [encoding lowercaseString]; - if(![fm fileExistsAtPath:folder]) { + + BOOL isDir = NO; + BOOL exists = NO; + exists = [fm fileExistsAtPath:path isDirectory: &isDir]; + + if (isDir) { + return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + } + + if(!exists) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; if(err != nil) { return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); @@ -348,6 +364,7 @@ + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil); } } + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; NSData * content = nil; if([encoding RNFBContainsString:@"base64"]) { @@ -356,7 +373,7 @@ + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString else if([encoding isEqualToString:@"uri"]) { NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) { if(errMsg != nil) - reject(@"RNFetchBlob writeFile Error", errMsg, nil); + reject(@"EUNSPECIFIED", errMsg, nil); else resolve(size); }]; @@ -379,22 +396,40 @@ + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString } @catch (NSException * e) { - reject(@"RNFetchBlob writeFile Error", @"Error", [e description]); + reject(@"EUNSPECIFIED", [e description], nil); } } # pragma mark - write file (array) -+ (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { ++ (void) writeFileArray:(NSString *)path + data:(NSArray *)data + append:(BOOL)append + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject +{ @try { NSFileManager * fm = [NSFileManager defaultManager]; NSError * err = nil; // check if the folder exists, if not exists, create folders recursively // after the folders created, write data into the file NSString * folder = [path stringByDeletingLastPathComponent]; - if(![fm fileExistsAtPath:folder]) { + + BOOL isDir = NO; + BOOL exists = NO; + exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir]; + + if (isDir) { + return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + } + + if(!exists) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; + if(err != nil) { + return reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); + } } + NSMutableData * fileContent = [NSMutableData alloc]; // prevent stack overflow, alloc on heap char * bytes = (char*) malloc([data count]); @@ -402,8 +437,11 @@ + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)appen bytes[i] = [[data objectAtIndex:i] charValue]; } [fileContent appendBytes:bytes length:data.count]; - if(![fm fileExistsAtPath:path]) { - [fm createFileAtPath:path contents:fileContent attributes:NULL]; + + if(!exists) { + if(![fm createFileAtPath:path contents:fileContent attributes:NULL]) { + return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil); + } } // if file exists, write file else { @@ -423,7 +461,7 @@ + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)appen } @catch (NSException * e) { - reject(@"RNFetchBlob writeFile Error", @"Error", [e description]); + reject(@"EUNSPECIFIED", [e description], nil); } } @@ -431,7 +469,7 @@ + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)appen + (void) readFile:(NSString *)path encoding:(NSString *)encoding - onComplete:(void (^)(id content, NSString * errMsg))onComplete + onComplete:(void (^)(NSData * content, NSString * codeStr, NSString * errMsg))onComplete { [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) { __block NSData * fileContent; @@ -443,7 +481,7 @@ + (void) readFile:(NSString *)path [asset getBytes:buffer fromOffset:0 length:asset.size error:&err]; if(err != nil) { - onComplete(nil, [err description]); + onComplete(nil, @"EUNSPECIFIED", [err description]); free(buffer); return; } @@ -452,8 +490,13 @@ + (void) readFile:(NSString *)path } else { - if(![[NSFileManager defaultManager] fileExistsAtPath:path]) { - onComplete(nil, @"file does not exist"); + BOOL isDir = NO; + if(![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir]) { + if (isDir) { + onComplete(nil, @"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]); + } else { + onComplete(nil, @"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path]); + } return; } fileContent = [NSData dataWithContentsOfFile:path]; @@ -466,12 +509,12 @@ + (void) readFile:(NSString *)path { NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding]; if(utf8 == nil) - onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil); + onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil, nil); else - onComplete(utf8, nil); + onComplete(utf8, nil, nil); } else if ([[encoding lowercaseString] isEqualToString:@"base64"]) { - onComplete([fileContent base64EncodedStringWithOptions:0], nil); + onComplete([fileContent base64EncodedStringWithOptions:0], nil, nil); } else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) { NSMutableArray * resultArray = [NSMutableArray array]; @@ -479,12 +522,12 @@ + (void) readFile:(NSString *)path for(int i=0;i<[fileContent length];i++) { [resultArray addObject:[NSNumber numberWithChar:bytes[i]]]; } - onComplete(resultArray, nil); + onComplete(resultArray, nil, nil); } } else { - onComplete(fileContent, nil); + onComplete(fileContent, nil, nil); } }]; @@ -492,85 +535,102 @@ + (void) readFile:(NSString *)path # pragma mark - hash -RCT_EXPORT_METHOD(hash:(NSString *)filepath ++ (void) hash:(NSString *)path algorithm:(NSString *)algorithm resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject { - BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filepath]; - - if (!fileExists) { - return reject(@"ENOENT", [NSString stringWithFormat:@"ENOENT: no such file or directory, open '%@'", filepath], nil); - } + BOOL isDir = NO; + BOOL exists = NO; + exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir]; - NSError *error = nil; + if (isDir) { + return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + } + if (!exists) { + return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil); + } - NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filepath error:&error]; + NSError *error = nil; - if (error) { - return [self reject:reject withError:error]; - } + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; - if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) { - return reject(@"EISDIR", @"EISDIR: illegal operation on a directory, read", nil); - } + if (error) { + reject(@"EUNKNOWN", [error description], nil); + return; + } - NSData *content = [[NSFileManager defaultManager] contentsAtPath:filepath]; + if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) { + reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + return; + } - NSArray *keys = [NSArray arrayWithObjects:@"md5", @"sha1", @"sha224", @"sha256", @"sha384", @"sha512", nil]; + NSData *content = [[NSFileManager defaultManager] contentsAtPath:path]; - NSArray *digestLengths = [NSArray arrayWithObjects: - @CC_MD5_DIGEST_LENGTH, - @CC_SHA1_DIGEST_LENGTH, - @CC_SHA224_DIGEST_LENGTH, - @CC_SHA256_DIGEST_LENGTH, - @CC_SHA384_DIGEST_LENGTH, - @CC_SHA512_DIGEST_LENGTH, - nil]; + NSArray *keys = [NSArray arrayWithObjects:@"md5", @"sha1", @"sha224", @"sha256", @"sha384", @"sha512", nil]; - NSDictionary *keysToDigestLengths = [NSDictionary dictionaryWithObjects:digestLengths forKeys:keys]; + NSArray *digestLengths = [NSArray arrayWithObjects: + @CC_MD5_DIGEST_LENGTH, + @CC_SHA1_DIGEST_LENGTH, + @CC_SHA224_DIGEST_LENGTH, + @CC_SHA256_DIGEST_LENGTH, + @CC_SHA384_DIGEST_LENGTH, + @CC_SHA512_DIGEST_LENGTH, + nil]; - int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue]; + NSDictionary *keysToDigestLengths = [NSDictionary dictionaryWithObjects:digestLengths forKeys:keys]; - if (!digestLength) { - return reject(@"Error", [NSString stringWithFormat:@"Invalid hash algorithm '%@'", algorithm], nil); - } + int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue]; - unsigned char buffer[digestLength]; + if (!digestLength) { + return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil); + } - if ([algorithm isEqualToString:@"md5"]) { - CC_MD5(content.bytes, (CC_LONG)content.length, buffer); - } else if ([algorithm isEqualToString:@"sha1"]) { - CC_SHA1(content.bytes, (CC_LONG)content.length, buffer); - } else if ([algorithm isEqualToString:@"sha224"]) { - CC_SHA224(content.bytes, (CC_LONG)content.length, buffer); - } else if ([algorithm isEqualToString:@"sha256"]) { - CC_SHA256(content.bytes, (CC_LONG)content.length, buffer); - } else if ([algorithm isEqualToString:@"sha384"]) { - CC_SHA384(content.bytes, (CC_LONG)content.length, buffer); - } else if ([algorithm isEqualToString:@"sha512"]) { - CC_SHA512(content.bytes, (CC_LONG)content.length, buffer); - } else { - return reject(@"Error", [NSString stringWithFormat:@"Invalid hash algorithm '%@'", algorithm], nil); - } + unsigned char buffer[digestLength]; + + if ([algorithm isEqualToString:@"md5"]) { + CC_MD5(content.bytes, (CC_LONG)content.length, buffer); + } else if ([algorithm isEqualToString:@"sha1"]) { + CC_SHA1(content.bytes, (CC_LONG)content.length, buffer); + } else if ([algorithm isEqualToString:@"sha224"]) { + CC_SHA224(content.bytes, (CC_LONG)content.length, buffer); + } else if ([algorithm isEqualToString:@"sha256"]) { + CC_SHA256(content.bytes, (CC_LONG)content.length, buffer); + } else if ([algorithm isEqualToString:@"sha384"]) { + CC_SHA384(content.bytes, (CC_LONG)content.length, buffer); + } else if ([algorithm isEqualToString:@"sha512"]) { + CC_SHA512(content.bytes, (CC_LONG)content.length, buffer); + } else { + reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil); + return; + } - NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2]; - for(int i = 0; i < digestLength; i++) - [output appendFormat:@"%02x",buffer[i]]; + NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2]; + for(int i = 0; i < digestLength; i++) + [output appendFormat:@"%02x",buffer[i]]; - resolve(output); + resolve(output); } # pragma mark - mkdir -+ (BOOL) mkdir:(NSString *) path { - BOOL isDir; ++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject +{ + BOOL isDir = NO; NSError * err = nil; - // if temp folder does not exist create it - if(![[NSFileManager defaultManager] fileExistsAtPath: path isDirectory:&isDir]) { + if([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) { + reject(@"EEXIST", [NSString stringWithFormat:@"%@ '%@' already exists", isDir ? @"Directory" : @"File", path], nil); + return; + } + else { [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err]; } - return err == nil; + if(err == nil) { + resolve(@YES); + } + else { + reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Error creating folder '%@', error: %@", path, [err description]], nil); + } } # pragma mark - stat @@ -703,22 +763,28 @@ + (void)slice:(NSString *)path NSFileManager * fm = [NSFileManager defaultManager]; NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO]; [os open]; - // abort because the source file does not exist - if([fm fileExistsAtPath:path] == NO) - { - reject(@"RNFetchBlob slice Error : the file does not exist", path, nil); - return; + + BOOL isDir = NO; + BOOL exists = NO; + exists = [fm fileExistsAtPath:path isDirectory: &isDir]; + + if (isDir) { + return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + } + if(!exists) { + return reject(@"ENOENT", [NSString stringWithFormat: @"No such file '%@'", path ], nil); } + long size = [fm attributesOfItemAtPath:path error:nil].fileSize; long max = MIN(size, [end longValue]); if(![fm fileExistsAtPath:dest]) { - [fm createFileAtPath:dest contents:@"" attributes:nil]; + if(![fm createFileAtPath:dest contents:@"" attributes:nil]) { + return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil); + } } [handle seekToFileOffset:[start longValue]]; - while(read < expected) - { - + while(read < expected) { NSData * chunk; long chunkSize = 0; if([start longValue] + read + 10240 > max) @@ -754,9 +820,7 @@ + (void)slice:(NSString *)path long size = asset.size; long max = MIN(size, [end longValue]); - while(read < expected) - { - + while(read < expected) { uint8_t * chunk[10240]; long chunkSize = 0; if([start longValue] + read + 10240 > max) @@ -781,9 +845,8 @@ + (void)slice:(NSString *)path [os close]; resolve(dest); } - else - { - reject(@"RNFetchBlob slice Error", [NSString stringWithFormat: @"could not resolve URI %@", path ], nil); + else { + reject(@"EINVAL", [NSString stringWithFormat: @"Could not resolve URI %@", path ], nil); } }]; diff --git a/ios/RNFetchBlobReqBuilder.m b/ios/RNFetchBlobReqBuilder.m index 15465a1ae..b6048f553 100644 --- a/ios/RNFetchBlobReqBuilder.m +++ b/ios/RNFetchBlobReqBuilder.m @@ -118,17 +118,17 @@ +(void) buildOctetRequest:(NSDictionary *)options if([orgPath hasPrefix:AL_PREFIX]) { - [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) { + [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(id content, NSString* code, NSString * err) { if(err != nil) { onComplete(nil, nil); } else { - [request setHTTPBody:content]; + [request setHTTPBody:((NSData *)content)]; [request setHTTPMethod: method]; [request setAllHTTPHeaderFields:mheaders]; - onComplete(request, [content length]); + onComplete(request, [((NSData *)content) length]); } }]; @@ -222,7 +222,7 @@ void __block (^getFieldData)(id field) = ^(id field) NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]]; orgPath = [RNFetchBlobFS getPathOfAsset:orgPath]; - [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) { + [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString* code, NSString * err) { if(err != nil) { onComplete(formData, YES); diff --git a/polyfill/File.js b/polyfill/File.js index eff53747b..761fb996b 100644 --- a/polyfill/File.js +++ b/polyfill/File.js @@ -2,7 +2,6 @@ // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file. -import fs from '../fs.js' import Blob from './Blob.js' export default class File extends Blob { @@ -11,6 +10,9 @@ export default class File extends Blob { static build(name:string, data:any, cType:string):Promise { return new Promise((resolve, reject) => { + if (data === undefined) { + reject(new TypeError('data is undefined')) + } new File(data, cType).onCreated((f) => { f.name = name resolve(f) diff --git a/polyfill/XMLHttpRequest.js b/polyfill/XMLHttpRequest.js index 42c987704..9036b2bc8 100644 --- a/polyfill/XMLHttpRequest.js +++ b/polyfill/XMLHttpRequest.js @@ -224,8 +224,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/, /tt/ ] - for(let i in invalidPatterns) { - if(invalidPatterns[i].test(name) || typeof name !== 'string') { + for(let pattern of invalidPatterns) { + if(pattern.test(name) || typeof name !== 'string') { throw `SyntaxError : Invalid header field name ${name}` } }