From 0dd09a4138ac7d057c09ccf125e8aa8dba0f69d1 Mon Sep 17 00:00:00 2001 From: grylance Date: Wed, 9 Aug 2017 16:12:33 +0100 Subject: [PATCH 01/43] Fix path argument in iOS excludeFromBackupKey (#473) --- ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From 57af3535851204b39ea588873c95e709a54963ff Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 10 Aug 2017 21:51:21 +0200 Subject: [PATCH 02/43] Fix link to fs.readStream() and to fs.writeStream() and insert link to new function fs.hash() --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09a964c44..e659af781 100644 --- a/README.md +++ b/README.md @@ -592,8 +592,9 @@ File Access APIs - [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) - [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) From 3a31e35b302218a6e4295ce9d368b2b79633fa97 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 10 Aug 2017 22:50:31 +0200 Subject: [PATCH 03/43] Fix the documentation part of https://github.com/wkh237/react-native-fetch-blob/issues/467 "Example code for writeStream ignores that stream.write() returns a promise?" --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e659af781..116b7124f 100644 --- a/README.md +++ b/README.md @@ -645,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, @@ -653,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 From 23ef0f7e5a16dfb2d68a15ad6810227884b40842 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Sat, 12 Aug 2017 00:05:50 +0200 Subject: [PATCH 04/43] More fixes for issue https://github.com/wkh237/react-native-fetch-blob/issues/460 "Error normalization" IMPORTANT: I wrote the iOS code BLIND (not even syntax highlighting) - this needs to be tested. - Two or three methods that used callbacks to return results were changed to RN promises - All methods using promises now use a Unix like code string for the first parameter, e.g. "ENOENT" for "File does not exist" (http://www.alorelang.org/doc/errno.html). The React Native bridge code itself uses this schema: it inserts "EUNSPECIFIED" when the "code" it gets back from Android/iOS code is undefined (null, nil). The RN bridge assigns the code (or "EUNSPECIFIED") to the "code" property of the error object it returns to Javascript, following the node.js example (the "code" property is not part of "standard" Javascript Error objects) - Important errors like "No such file" are reported (instead of a general error), using the code property. - I added a few extra error checks that the IDE suggested, mostly for Android (for which I have an IDE), if it seemed important I tried to do the same for teh iOS equivalent function - I followed IDE suggestions on some of the Java code, like making fields private - RNFetchBlobFS.java removeSession(): Added reporting of all failures to delete - IS THIS DESIRABLE (or do we not care)? - readStream: The same schema is used for the emitted events when they are error events - iOS: added an import for the crypto-digest headers - they are needed for the hash() function submitted in an earlier commit - Fixed a link in the README.md - unfortunately the anchor-links change whenever even one character of the linked headline in the Wiki page changes --- README.md | 2 +- .../java/com/RNFetchBlob/RNFetchBlob.java | 31 +- .../java/com/RNFetchBlob/RNFetchBlobBody.java | 41 +- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 360 +++++++++++------- .../java/com/RNFetchBlob/RNFetchBlobReq.java | 2 +- .../com/RNFetchBlob/RNFetchBlobUtils.java | 5 +- class/RNFetchBlobReadStream.js | 8 +- fs.js | 101 +++-- ios/RNFetchBlob/RNFetchBlob.m | 85 +++-- ios/RNFetchBlobFS.m | 85 +++-- 10 files changed, 418 insertions(+), 302 deletions(-) diff --git a/README.md b/README.md index 116b7124f..8831db5cf 100644 --- a/README.md +++ b/README.md @@ -590,7 +590,7 @@ 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-buffersize-interval-promisernfbreadstream) - [hash (0.10.9)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithm-promise) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 68cd1d5c3..19c0b58fb 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -85,11 +85,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); } }); @@ -128,17 +138,6 @@ public void onHostDestroy() { } } - @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 +149,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 @@ -304,7 +303,7 @@ public void cancelRequest(String taskId, Callback callback) { } @ReactMethod - public void slice(String src, String dest, int start, int end, Promise promise) { + public void slice(String src, String dest, long start, long end, Promise promise) { RNFetchBlobFS.slice(src, dest, start, end, "", promise); } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java index 8146a99cf..9642cb4d2 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java @@ -23,17 +23,16 @@ public class RNFetchBlobBody extends RequestBody{ - InputStream requestStream; + private InputStream requestStream; long contentLength = 0; ReadableArray form; - String mTaskId; - String rawBody; - RNFetchBlobReq.RequestType requestType; + private String mTaskId; + private String rawBody; + private RNFetchBlobReq.RequestType requestType; MediaType mime; - File bodyCache; + private File bodyCache; int reported = 0; - Boolean chunkedEncoding = false; - + private Boolean chunkedEncoding = false; public RNFetchBlobBody(String taskId) { this.mTaskId = taskId; @@ -49,7 +48,7 @@ RNFetchBlobBody setMIME(MediaType mime) { return this; } - RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) { + RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) { this.requestType = type; return this; } @@ -186,8 +185,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 +256,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(); } @@ -300,11 +295,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 +328,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; @@ -350,7 +345,7 @@ private class FormField { public 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 +363,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/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index bfcd9b588..27f154cc0 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -22,35 +22,31 @@ 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 { - ReactApplicationContext mCtx; + private ReactApplicationContext mCtx; DeviceEventManagerModule.RCTDeviceEventEmitter emitter; String encoding = "base64"; boolean append = false; - OutputStream writeStreamInstance = null; - static HashMap fileStreams = new HashMap<>(); + 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) { + static String getExternalFilePath(ReactApplicationContext ctx, String taskId, com.RNFetchBlob.RNFetchBlobConfig config) { if(config.path != null) return config.path; else if(config.fileCache && config.appendExt != null) @@ -71,20 +67,32 @@ static public void writeFile(String path, String encoding, String data, final bo int written = 0; File f = new File(path); File dir = f.getParentFile(); - if(!dir.exists()) - dir.mkdirs(); + if(!dir.exists()) { + boolean result = dir.mkdirs(); + if (!result) { + promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + path + "'"); + return; + } + } + if(!f.exists()) { + boolean result = f.createNewFile(); + if (!result) { + promise.reject("EUNSPECIFIED", "Failed to create file '" + path + "'"); + 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 '" + 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 +108,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()); } } @@ -112,23 +123,36 @@ static public void writeFile(String path, String encoding, String data, final bo * @param promise RCT Promise */ static public 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(!dir.exists()) { + boolean result = dir.mkdirs(); + if (!result) { + promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + path + "'"); + return; + } + } + if(!f.exists()) { + boolean result = f.createNewFile(); + if (!result) { + promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created, or it is a directory"); + 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 = (int) 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 +225,11 @@ else if(resolved == null) { break; } } + catch(FileNotFoundException err) { + promise.reject("ENOENT", "No such file or directory '" + path + "'; " + err.getLocalizedMessage()); + } catch(Exception err) { - promise.reject("RNFetchBlob readFile error", err.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", err.getLocalizedMessage()); } } @@ -224,9 +263,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) { + + private static String getTmpPath(ReactApplicationContext ctx, String taskId) { return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId; } @@ -287,10 +327,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 +558,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 +580,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); + AssetFileDescriptor fd = com.RNFetchBlob.RNFetchBlob.RCTContext.getAssets().openFd(filename); callback.invoke(true, false); } catch (IOException e) { callback.invoke(false, false); @@ -543,6 +611,8 @@ static void ls(String path, Callback callback) { } 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); } @@ -557,12 +627,16 @@ static void ls(String path, Callback callback) { * @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) { + public static void slice(String src, String dest, long start, long 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"); + if(source.isDirectory()){ + promise.reject("EISDIR", "Expecting a file but '" + src + "' is a directory"); + return; + } + if(!source.exists()){ + promise.reject("ENOENT", "No such file '" + src + "'"); return; } long size = source.length(); @@ -571,8 +645,12 @@ public static void slice(String src, String dest, int start, int end, String enc long now = 0; FileInputStream in = new FileInputStream(new File(src)); FileOutputStream out = new FileOutputStream(new File(dest)); - in.skip(start); - byte [] buffer = new byte[10240]; + long skipped = 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; @@ -588,7 +666,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 +688,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 +705,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,10 +723,10 @@ 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) { + private static WritableMap statFile(String path) { try { path = normalizePath(path); WritableMap stat = Arguments.createMap(); @@ -680,9 +760,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 +788,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 (!algorithm.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 +820,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 +831,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 +841,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 +856,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 +872,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 +981,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 +989,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 +1003,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 FileNotFoundException If the given file does not exist or is a directory */ - static InputStream inputStreamFromPath(String path) throws IOException { + private static InputStream inputStreamFromPath(String path) throws FileNotFoundException { if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { return RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); } @@ -925,10 +1017,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; } @@ -940,10 +1032,8 @@ static boolean isPathExists(String path) { } - static boolean isAsset(String path) { - if(path != null) - return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET); - return false; + private static boolean isAsset(String path) { + return path != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET); } /** @@ -951,7 +1041,7 @@ static boolean isAsset(String path) { * @param path URI string. * @return Normalized string */ - static String normalizePath(String path) { + private static String normalizePath(String path) { if(path == null) return null; if(!path.matches("\\w+\\:.*")) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 8a81a832e..4b544d58e 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -463,7 +463,7 @@ private void done(Response resp) { 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); } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java index 6e976d775..b47c9aa90 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; } @@ -48,7 +49,7 @@ public static void emitWarningEvent(String data) { args.putString("detail", data); // emit event to js context - RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + RNFetchBlob.RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(RNFetchBlobConst.EVENT_MESSAGE, args); } diff --git a/class/RNFetchBlobReadStream.js b/class/RNFetchBlobReadStream.js index fe514b35c..a3d6c0498 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 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..01eea7c99 100644 --- a/fs.js +++ b/fs.js @@ -58,25 +58,20 @@ function asset(path:string):string { return 'bundle-assets://' + path } -function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise { +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')) - } + if(encoding.toLowerCase() === 'ascii') { + if(Array.isArray(data)) + return RNFetchBlob.createFileASCII(path, data) else { - RNFetchBlob.createFile(path, data, encoding, handler) + const err = new TypeError('`data` of ASCII file must be an array with 0..255 numbers') + err.code = 'EINVAL' + return Promise.reject(err) } - }) + } + else { + return RNFetchBlob.createFile(path, data, encoding) + } } /** @@ -128,16 +123,7 @@ function readStream( * @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() - }) - }) - + return RNFetchBlob.mkdir(path, (code, msg, res) } /** @@ -146,7 +132,7 @@ function mkdir(path:string):Promise { * @return {Promise} */ function pathForAppGroup(groupName:string):Promise { - return RNFetchBlob.pathForAppGroup(groupName); + return RNFetchBlob.pathForAppGroup(groupName) } /** @@ -170,35 +156,54 @@ function readFile(path:string, encoding:string):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(typeof path !== 'string') { + const err = new TypeError('Missing argument "path" ') + err.code = 'EINVAL' + return Promise.reject(err) + } 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}`)) + if(!Array.isArray(data)) { + const err = new TypeError('"data" must be an Array when encoding is "ascii"') + err.code = 'EINVAL' + return Promise.reject(err) + } 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') { + const err = new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`) + err.code = 'EINVAL' + return Promise.reject(err) + } 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 { +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(typeof path !== 'string') { + const err = new TypeError('Missing argument "path" ') + err.code = 'EINVAL' + return Promise.reject(err) + } 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}`)) + if(!Array.isArray(data)) { + const err = new TypeError('`data` of ASCII file must be an array with 0..255 numbers') + err.code = 'EINVAL' + return Promise.reject(err) + } else - return RNFetchBlob.writeFileArray(path, data, true); + 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}`)) + if(typeof data !== 'string') { + const err = new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`) + err.code = 'EINVAL' + return Promise.reject(err) + } else - return RNFetchBlob.writeFile(path, encoding, data, true); + return RNFetchBlob.writeFile(path, encoding, data, true) } } @@ -240,11 +245,6 @@ 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" ')) - return RNFetchBlob.hash(path, algorithm) } @@ -350,7 +350,6 @@ function slice(src:string, dest:string, start:number, end:number):Promise { } function isDir(path:string):Promise { - return new Promise((resolve, reject) => { try { RNFetchBlob.exists(path, (exist, isDir) => { diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 78b1cd4bb..08c436f25 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 @@ -389,7 +407,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 +417,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 +426,6 @@ - (NSDictionary *)constantsToExport callback(@[[error localizedDescription], @NO]); } }]; - } @@ -426,15 +443,17 @@ - (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; + reject(@"EEXIST", @[[NSString stringWithFormat:@"Folder '%@' already exists", path]], nil); } - else + else { [RNFetchBlobFS mkdir:path]; - callback(@[[NSNull null]]); + resolve(@[[NSNull null]]); + } } #pragma mark - fs.readFile @@ -443,19 +462,15 @@ - (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:^(id 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 +553,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 +573,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 +587,7 @@ - (NSDictionary *)constantsToExport { resolve(@[[NSNull null]]); } else { - reject(@"RNFetchBlob excludeFromBackupKey Error", [error description], nil); + reject(@"EUNSPECIFIED", [error description], nil); } } diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 89148396b..eda0ecc9f 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -13,6 +13,8 @@ #import "IOS7Polyfill.h" @import AssetsLibrary; +#import + #if __has_include() #import #import @@ -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,14 +346,19 @@ + (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]) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; - [fm createFileAtPath:path contents:nil attributes:nil]; - } - if(err != nil) { - reject(@"RNFetchBlob writeFile Error", @"could not create file at path", nil); - return; + if(err != nil) { + reject(@"EUNSPECIFIED", [err description], nil); + return; + } + if(![fm createFileAtPath:path contents:nil attributes:nil]) { + reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created, or it is a directory", path], nil); + return; + } } + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; NSData * content = nil; if([encoding RNFBContainsString:@"base64"]) { @@ -355,7 +367,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); }]; @@ -378,13 +390,18 @@ + (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; @@ -393,7 +410,12 @@ + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)appen NSString * folder = [path stringByDeletingLastPathComponent]; if(![fm fileExistsAtPath:folder]) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; + if(err != nil) { + reject(@"EUNSPECIFIED", [err description], nil); + return; + } } + NSMutableData * fileContent = [NSMutableData alloc]; // prevent stack overflow, alloc on heap char * bytes = (char*) malloc([data count]); @@ -422,7 +444,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); } } @@ -430,7 +452,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 (^)(id content, NSString * codeStr, NSString * errMsg))onComplete { [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) { __block NSData * fileContent; @@ -442,7 +464,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,7 +474,7 @@ + (void) readFile:(NSString *)path else { if(![[NSFileManager defaultManager] fileExistsAtPath:path]) { - onComplete(nil, @"file does not exist"); + onComplete(nil, @"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path]); return; } fileContent = [NSData dataWithContentsOfFile:path]; @@ -465,12 +487,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]; @@ -478,12 +500,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); } }]; @@ -499,7 +521,7 @@ + (void) readFile:(NSString *)path BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filepath]; if (!fileExists) { - return reject(@"ENOENT", [NSString stringWithFormat:@"ENOENT: no such file or directory, open '%@'", filepath], nil); + return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", filepath], nil); } NSError *error = nil; @@ -511,7 +533,7 @@ + (void) readFile:(NSString *)path } if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) { - return reject(@"EISDIR", @"EISDIR: illegal operation on a directory, read", nil); + return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", filepath], nil); } NSData *content = [[NSFileManager defaultManager] contentsAtPath:filepath]; @@ -532,7 +554,7 @@ + (void) readFile:(NSString *)path int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue]; if (!digestLength) { - return reject(@"Error", [NSString stringWithFormat:@"Invalid hash algorithm '%@'", algorithm], nil); + return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil); } unsigned char buffer[digestLength]; @@ -550,7 +572,7 @@ + (void) readFile:(NSString *)path } 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); + return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil); } NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2]; @@ -703,10 +725,8 @@ + (void)slice:(NSString *)path 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; + if([fm fileExistsAtPath:path] == NO) { + return reject(@"ENOENT", [NSString stringWithFormat: @"No such file '%@'", path ], nil); } long size = [fm attributesOfItemAtPath:path error:nil].fileSize; long max = MIN(size, [end longValue]); @@ -715,9 +735,7 @@ + (void)slice:(NSString *)path [fm createFileAtPath:dest contents:@"" attributes:nil]; } [handle seekToFileOffset:[start longValue]]; - while(read < expected) - { - + while(read < expected) { NSData * chunk; long chunkSize = 0; if([start longValue] + read + 10240 > max) @@ -753,9 +771,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) @@ -780,9 +796,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); } }]; From 647e88f89af28a53291f616011334416760c20fa Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Sat, 12 Aug 2017 09:43:59 +0200 Subject: [PATCH 05/43] Fix one issue raised in https://github.com/wkh237/react-native-fetch-blob/issues/477 by using code from https://stackoverflow.com/a/40874952/544779 --- .../java/com/RNFetchBlob/RNFetchBlobReq.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 4b544d58e..9451d5d32 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -22,8 +22,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 +40,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; @@ -83,7 +90,6 @@ enum ResponseFormat { static HashMap uploadProgressReport = new HashMap<>(); static ConnectionPool pool = new ConnectionPool(); - ReactApplicationContext ctx; RNFetchBlobConfig options; String taskId; String method; @@ -188,7 +194,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()); @@ -199,7 +205,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; @@ -459,7 +465,7 @@ 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; @@ -657,11 +663,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(); } } } @@ -695,7 +701,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) From 05d6e03a5b73efc72841ff40c74b2fa194ebfb9e Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 14 Aug 2017 15:30:16 +0200 Subject: [PATCH 06/43] fix some access rights, remove unused items --- android/build.gradle | 1 + .../gradle/wrapper/gradle-wrapper.properties | 4 +- .../java/com/RNFetchBlob/RNFetchBlob.java | 7 +-- .../java/com/RNFetchBlob/RNFetchBlobBody.java | 19 +++--- .../com/RNFetchBlob/RNFetchBlobConfig.java | 5 +- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 59 +++++++------------ .../RNFetchBlobProgressConfig.java | 14 ++--- .../java/com/RNFetchBlob/RNFetchBlobReq.java | 9 +-- .../com/RNFetchBlob/RNFetchBlobUtils.java | 2 +- .../Response/RNFetchBlobFileResp.java | 5 +- 10 files changed, 54 insertions(+), 71 deletions(-) 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..464ccb10f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#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 zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 19c0b58fb..6fdf9299e 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -34,9 +34,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule { - // Cookies - private final ForwardingCookieHandler mCookieHandler; - private final CookieJarContainer mCookieJarContainer; private final OkHttpClient mClient; static ReactApplicationContext RCTContext; @@ -52,8 +49,8 @@ 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; diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java index 9642cb4d2..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,20 +22,20 @@ import okhttp3.RequestBody; import okio.BufferedSink; -public class RNFetchBlobBody extends RequestBody{ +class RNFetchBlobBody extends RequestBody{ private InputStream requestStream; - long contentLength = 0; - ReadableArray form; + private long contentLength = 0; + private ReadableArray form; private String mTaskId; private String rawBody; private RNFetchBlobReq.RequestType requestType; - MediaType mime; + private MediaType mime; private File bodyCache; int reported = 0; private Boolean chunkedEncoding = false; - public RNFetchBlobBody(String taskId) { + RNFetchBlobBody(String taskId) { this.mTaskId = taskId; } @@ -113,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) { @@ -286,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; @@ -341,8 +342,8 @@ else if (field.filename != null) { */ private class FormField { public String name; - public String filename; - public String mime; + String filename; + String mime; public String data; FormField(ReadableMap rawData) { 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 27f154cc0..898fbe332 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; @@ -32,12 +30,11 @@ import java.util.Map; import java.util.UUID; -public class RNFetchBlobFS { +class RNFetchBlobFS { private ReactApplicationContext mCtx; - DeviceEventManagerModule.RCTDeviceEventEmitter emitter; - String encoding = "base64"; - boolean append = false; + private DeviceEventManagerModule.RCTDeviceEventEmitter emitter; + private String encoding = "base64"; private OutputStream writeStreamInstance = null; private static HashMap fileStreams = new HashMap<>(); @@ -46,15 +43,6 @@ public class RNFetchBlobFS { this.emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); } - static String getExternalFilePath(ReactApplicationContext ctx, String taskId, com.RNFetchBlob.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. @@ -62,9 +50,9 @@ 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()) { @@ -122,7 +110,7 @@ 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(); @@ -162,7 +150,7 @@ static public void writeFile(String path, ReadableArray data, final boolean appe * @param encoding Encoding of read stream. * @param promise JS promise */ - static public void readFile(String path, String encoding, final Promise promise ) { + static void readFile(String path, String encoding, final Promise promise) { String resolved = normalizePath(path); if(resolved != null) path = resolved; @@ -187,7 +175,7 @@ else if(resolved == null) { // 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 = (int) in.available(); + length = in.available(); bytes = new byte[length]; bytesRead = in.read(bytes); in.close(); @@ -238,7 +226,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()); @@ -261,12 +249,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 String */ - - private static String getTmpPath(ReactApplicationContext ctx, String taskId) { + static String getTmpPath(String taskId) { return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId; } @@ -276,7 +262,7 @@ private static 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; @@ -369,7 +355,7 @@ else if(resolved == null) { * @param append Flag represents if the file stream overwrite existing content * @param callback Callback */ - public void writeStream(String path, String encoding, boolean append, Callback callback) { + void writeStream(String path, String encoding, boolean append, Callback callback) { File dest = new File(path); if(!dest.exists() || dest.isDirectory()) { callback.invoke("target path `" + path + "` may not exist or it is a folder"); @@ -378,7 +364,6 @@ public void writeStream(String path, String encoding, boolean append, Callback c try { OutputStream fs = new FileOutputStream(path, append); this.encoding = encoding; - this.append = append; String streamId = UUID.randomUUID().toString(); RNFetchBlobFS.fileStreams.put(streamId, this); this.writeStreamInstance = fs; @@ -497,7 +482,7 @@ static void mkdir(String path, Promise promise) { promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); return; } - promise.resolve(); + promise.resolve(null); } /** @@ -583,7 +568,7 @@ static void exists(String path, Callback callback) { if(isAsset(path)) { try { String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); - AssetFileDescriptor fd = com.RNFetchBlob.RNFetchBlob.RCTContext.getAssets().openFd(filename); + com.RNFetchBlob.RNFetchBlob.RCTContext.getAssets().openFd(filename); callback.invoke(true, false); } catch (IOException e) { callback.invoke(false, false); @@ -627,7 +612,7 @@ static void ls(String path, Callback callback) { * @param end End byte offset * @param encode NOT IMPLEMENTED */ - public static void slice(String src, String dest, long start, long end, String encode, Promise promise) { + static void slice(String src, String dest, long start, long end, String encode, Promise promise) { try { src = normalizePath(src); File source = new File(src); @@ -726,7 +711,7 @@ static void stat(String path, Callback callback) { * @param path Path * @return Stat Result of a file or path */ - private static WritableMap statFile(String path) { + static WritableMap statFile(String path) { try { path = normalizePath(path); WritableMap stat = Arguments.createMap(); @@ -788,7 +773,7 @@ static void hash(String path, String algorithm, Promise promise) { algorithms.put("sha384", "SHA-384"); algorithms.put("sha512", "SHA-512"); - if (!algorithm.containsKey(algorithm)) { + if (!algorithms.containsKey(algorithm)) { promise.reject("EINVAL", "Invalid algorithm '" + algorithm + "', must be one of md5, sha1, sha224, sha256, sha384, sha512"); return; } @@ -918,7 +903,7 @@ static void removeSession(ReadableArray paths, final Callback callback) { @Override protected Integer doInBackground(ReadableArray ...paths) { try { - ArrayList failuresToDelete = new ArrayList(); + ArrayList failuresToDelete = new ArrayList<>(); for (int i = 0; i < paths[0].size(); i++) { String fileName = paths[0].getString(i); File f = new File(fileName); @@ -1003,9 +988,9 @@ private void emitStreamEvent(String streamName, String event, String code, Strin * the stream is created by Assets Manager, otherwise use FileInputStream. * @param path The file to open stream * @return InputStream instance - * @throws FileNotFoundException If the given file does not exist or is a directory + * @throws IOException If the given file does not exist or is a directory FileInputStream will throw a FileNotFoundException */ - private static InputStream inputStreamFromPath(String path) throws FileNotFoundException { + 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, "")); } @@ -1032,7 +1017,7 @@ private static boolean isPathExists(String path) { } - private static boolean isAsset(String path) { + static boolean isAsset(String path) { return path != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET); } @@ -1041,7 +1026,7 @@ private static boolean isAsset(String path) { * @param path URI string. * @return Normalized string */ - private static String normalizePath(String path) { + static String normalizePath(String path) { if(path == null) return null; if(!path.matches("\\w+\\:.*")) 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 9451d5d32..61f1224fe 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; @@ -168,7 +169,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()) { @@ -327,7 +328,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; @@ -389,7 +390,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(); @@ -406,7 +407,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 ) { diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java index b47c9aa90..6dbcbe7c7 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java @@ -49,7 +49,7 @@ public static void emitWarningEvent(String data) { args.putString("detail", data); // emit event to js context - RNFetchBlob.RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(RNFetchBlobConst.EVENT_MESSAGE, args); } diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java index 88fc7c69a..686ac8d5b 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; @@ -52,7 +53,7 @@ public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseB 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); From 395229306d49f11040ed342778a167495a860585 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 14 Aug 2017 15:56:25 +0200 Subject: [PATCH 07/43] update gradle version setting in build.gradle --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index d68693bf1..1f4ad8ed6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.14.1' } } From 4c7bc551ab57772eff4622ed5db7cc490a06f217 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 14 Aug 2017 16:06:29 +0200 Subject: [PATCH 08/43] Revert gradle settings to previous values :-( --- android/build.gradle | 2 +- android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 1f4ad8ed6..d68693bf1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.14.1' + classpath 'com.android.tools.build:gradle:2.2.3' } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 464ccb10f..c60fdf0b6 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip From d22bdc6d1031785e87c8ab085c69ed53273957cb Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 14 Aug 2017 16:07:30 +0200 Subject: [PATCH 09/43] add a missing closing ")" --- fs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.js b/fs.js index 01eea7c99..3a791612b 100644 --- a/fs.js +++ b/fs.js @@ -123,7 +123,7 @@ function readStream( * @return {Promise} */ function mkdir(path:string):Promise { - return RNFetchBlob.mkdir(path, (code, msg, res) + return RNFetchBlob.mkdir(path, (code, msg, res)) } /** From 0fea62a65d01281e24e6b3479fb7c0afa7ac06ac Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 14 Aug 2017 16:10:16 +0200 Subject: [PATCH 10/43] Removed the part of an obsolete callback function parameter that I had left in when I converted mkdir to promises (low-level code) --- fs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.js b/fs.js index 3a791612b..3fac5ceda 100644 --- a/fs.js +++ b/fs.js @@ -123,7 +123,7 @@ function readStream( * @return {Promise} */ function mkdir(path:string):Promise { - return RNFetchBlob.mkdir(path, (code, msg, res)) + return RNFetchBlob.mkdir(path) } /** From a2f13b1cabc2c1434eabaf719f86d1ebf6d37dbf Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 14 Aug 2017 21:53:38 +0200 Subject: [PATCH 11/43] let mkdir resolve with "undefined" instead of "null" (my mistake) --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 898fbe332..bbea71570 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -482,7 +482,7 @@ static void mkdir(String path, Promise promise) { promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); return; } - promise.resolve(null); + promise.resolve(); } /** From d3e47cd45b3ea6e21edf80fd6f74cd2acde9ba9e Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 00:03:37 +0200 Subject: [PATCH 12/43] mkdir: normalize iOS and Android error if something already exists (file OR folder); return "true" (boolean) on success (failure is rejected promise) - it is not possibel to return "undefined" from a React Native promise from Java --- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 4 ++-- fs.js | 6 +++--- ios/RNFetchBlob/RNFetchBlob.m | 12 ++---------- ios/RNFetchBlobFS.m | 18 +++++++++++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index bbea71570..b1d8e4100 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -469,7 +469,7 @@ private static void deleteRecursive(File fileOrDirectory) throws IOException { static void mkdir(String path, Promise promise) { File dest = new File(path); if(dest.exists()) { - promise.reject("EEXIST", "Folder '" + path + "' already exists"); + promise.reject("EEXIST", dest.isDirectory() ? "Folder" : "File" + " '" + path + "' already exists"); return; } try { @@ -482,7 +482,7 @@ static void mkdir(String path, Promise promise) { promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); return; } - promise.resolve(); + promise.resolve(true); } /** diff --git a/fs.js b/fs.js index 3fac5ceda..592228dcf 100644 --- a/fs.js +++ b/fs.js @@ -312,9 +312,9 @@ function unlink(path:string):Promise { /** * Check if file exists and if it is a folder. * @param {string} path Path to check - * @return {Promise} + * @return {Promise} */ -function exists(path:string):Promise { +function exists(path:string):Promise { return new Promise((resolve, reject) => { try { RNFetchBlob.exists(path, (exist) => { @@ -349,7 +349,7 @@ function slice(src:string, dest:string, start:number, end:number):Promise { return p.then(() => RNFetchBlob.slice(src, dest, start, end)) } -function isDir(path:string):Promise { +function isDir(path:string):Promise { return new Promise((resolve, reject) => { try { RNFetchBlob.exists(path, (exist, isDir) => { diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 08c436f25..e3b9b9980 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -443,17 +443,9 @@ - (NSDictionary *)constantsToExport } #pragma mark - fs.mkdir -RCT_EXPORT_METHOD(mkdir:(NSString *)path - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXPORT_METHOD(mkdir:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if([[NSFileManager defaultManager] fileExistsAtPath:path]) { - reject(@"EEXIST", @[[NSString stringWithFormat:@"Folder '%@' already exists", path]], nil); - } - else { - [RNFetchBlobFS mkdir:path]; - resolve(@[[NSNull null]]); - } + [RNFetchBlobFS mkdir:path resolver:resolve rejecter:reject]; } #pragma mark - fs.readFile diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index eda0ecc9f..5c3fdee8e 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -584,14 +584,22 @@ + (void) readFile:(NSString *)path # 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); + } + 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 From 2a4b11b9076404e85658c96192fa8a450516e6ac Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 00:31:44 +0200 Subject: [PATCH 13/43] fix a long/int issue --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index b1d8e4100..2664147be 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -637,7 +637,7 @@ static void slice(String src, String dest, long start, long end, String encode, } byte[] buffer = new byte[10240]; while(now < expected) { - long read = in.read(buffer, 0, 10240); + long read = (long) in.read(buffer, 0, 10240); long remain = expected - now; if(read <= 0) { break; From 22339801456521a9357b1b99aedd3dfe3b54d754 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 00:40:38 +0200 Subject: [PATCH 14/43] my mistake - according to https://facebook.github.io/react-native/docs/native-modules-android.html#argument-types "long" is not possible as argument type of an exported RN native module function --- .../main/java/com/RNFetchBlob/RNFetchBlob.java | 2 +- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 6fdf9299e..5401b850d 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -300,7 +300,7 @@ public void cancelRequest(String taskId, Callback callback) { } @ReactMethod - public void slice(String src, String dest, long start, long end, Promise promise) { + public void slice(String src, String dest, int start, int end, Promise promise) { RNFetchBlobFS.slice(src, dest, start, end, "", promise); } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 2664147be..a55c50109 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -156,7 +156,7 @@ static void readFile(String path, String encoding, final Promise promise) { path = resolved; try { byte[] bytes; - long bytesRead; + int bytesRead; int length; // max. array length limited to "int", also see https://stackoverflow.com/a/10787175/544779 if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { @@ -612,7 +612,7 @@ static void ls(String path, Callback callback) { * @param end End byte offset * @param encode NOT IMPLEMENTED */ - static void slice(String src, String dest, long start, long end, String encode, Promise promise) { + static void slice(String src, String dest, int start, int end, String encode, Promise promise) { try { src = normalizePath(src); File source = new File(src); @@ -624,21 +624,21 @@ static void slice(String src, String dest, long start, long end, String encode, promise.reject("ENOENT", "No such file '" + src + "'"); return; } - long size = source.length(); - long max = Math.min(size, end); - long expected = max - start; - long now = 0; + int size = (int) source.length(); + int max = Math.min(size, end); + int expected = max - start; + int now = 0; FileInputStream in = new FileInputStream(new File(src)); FileOutputStream out = new FileOutputStream(new File(dest)); - long skipped = in.skip(start); + 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 = (long) in.read(buffer, 0, 10240); - long remain = expected - now; + int read = in.read(buffer, 0, 10240); + int remain = expected - now; if(read <= 0) { break; } From 34e2377c3b953236a0877af40b368f786b283367 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 11:10:22 +0200 Subject: [PATCH 15/43] Adde "utf8" as default encoding for fs.readFile - fixes #450 and #484 --- fs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/fs.js b/fs.js index 592228dcf..dcaf21585 100644 --- a/fs.js +++ b/fs.js @@ -142,6 +142,7 @@ function pathForAppGroup(groupName:string):Promise { * @return {Promise | string>} */ function readFile(path:string, encoding:string):Promise { + encoding = encoding || 'utf8' if(typeof path !== 'string') return Promise.reject(new Error('Invalid argument "path" ')) return RNFetchBlob.readFile(path, encoding) From a530f529caf8e55eb5ac6f40eb37fcf522dda5fb Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 14:35:40 +0200 Subject: [PATCH 16/43] follow my IDEA IDE's recommendations - SparseArray instead of HashMap, and make some fields private --- .../src/main/java/com/RNFetchBlob/RNFetchBlob.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 5401b850d..6925efdea 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; @@ -36,13 +37,13 @@ public class RNFetchBlob extends ReactContextBaseJavaModule { private final OkHttpClient mClient; - static ReactApplicationContext RCTContext; - static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); - static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); + ReactApplicationContext RCTContext; + 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) { From 7588f82c6dd0d3479097f8fb4b26b162da4f1ece Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 14:36:48 +0200 Subject: [PATCH 17/43] polyfill/File.js: add a parameter===undefined? check (this happened silently in the test suite) --- polyfill/File.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) From 9682c2ce258fb912e1a92057d52026e268139307 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 14:45:53 +0200 Subject: [PATCH 18/43] make var static again --- android/src/main/java/com/RNFetchBlob/RNFetchBlob.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 6925efdea..835c474d6 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -37,7 +37,7 @@ public class RNFetchBlob extends ReactContextBaseJavaModule { private final OkHttpClient mClient; - ReactApplicationContext RCTContext; + static ReactApplicationContext RCTContext; private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); static LinkedBlockingQueue fsTaskQueue = new LinkedBlockingQueue<>(); @@ -352,7 +352,7 @@ 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); From 0f371ac6525ca932728527c74843f3a1f34dd1b9 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 21:40:59 +0200 Subject: [PATCH 19/43] Normalized errors for fs.ls() --- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 50 +++++++++++-------- fs.js | 9 +--- ios/RNFetchBlob/RNFetchBlob.m | 15 +++--- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index a55c50109..93c6bfd4e 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -587,48 +587,56 @@ 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(); - // 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); + 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("ENODIR", "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); + } + } 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 */ - 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); + path = normalizePath(path); + File source = new File(path); if(source.isDirectory()){ - promise.reject("EISDIR", "Expecting a file but '" + src + "' is a directory"); + promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory"); return; } if(!source.exists()){ - promise.reject("ENOENT", "No such file '" + src + "'"); + promise.reject("ENOENT", "No such file '" + path + "'"); return; } int size = (int) source.length(); int max = Math.min(size, end); int expected = max - start; int now = 0; - FileInputStream in = new FileInputStream(new File(src)); + FileInputStream in = new FileInputStream(new File(path)); FileOutputStream out = new FileOutputStream(new File(dest)); int skipped = (int) in.skip(start); if (skipped != start) { diff --git a/fs.js b/fs.js index dcaf21585..1b22bc35d 100644 --- a/fs.js +++ b/fs.js @@ -283,14 +283,7 @@ function lstat(path:string):Promise> { } function ls(path:string):Promise> { - return new Promise((resolve, reject) => { - RNFetchBlob.ls(path, (err, res) => { - if(err) - reject(new Error(err)) - else - resolve(res) - }) - }) + return RNFetchBlob.ls(path) } /** diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index e3b9b9980..fe126f358 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -309,24 +309,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(@"ENODIR", [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 From 0bac254e2b22f4871b29e2e31080222b37892c24 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 21:43:57 +0200 Subject: [PATCH 20/43] forgot one parameter --- android/src/main/java/com/RNFetchBlob/RNFetchBlob.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 835c474d6..7c6af6fc1 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -173,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 From 30af8e97c4387ca79c9f2aabbcc2492392097505 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 21:54:42 +0200 Subject: [PATCH 21/43] more parameter checks --- fs.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/fs.js b/fs.js index 1b22bc35d..fab830703 100644 --- a/fs.js +++ b/fs.js @@ -143,8 +143,11 @@ function pathForAppGroup(groupName:string):Promise { */ function readFile(path:string, encoding:string):Promise { encoding = encoding || 'utf8' - if(typeof path !== 'string') - return Promise.reject(new Error('Invalid argument "path" ')) + if(typeof path !== 'string') { + const err = new TypeError('Missing argument "path" ') + err.code = 'EINVAL' + return Promise.reject(err) + } return RNFetchBlob.readFile(path, encoding) } @@ -215,6 +218,11 @@ function appendFile(path:string, data:string | Array, encoding:?string): */ function stat(path:string):Promise { return new Promise((resolve, reject) => { + if(typeof path !== 'string') { + const err = new TypeError('Missing argument "path" ') + err.code = 'EINVAL' + return reject(err) + } RNFetchBlob.stat(path, (err, stat) => { if(err) reject(new Error(err)) @@ -246,6 +254,11 @@ function scanFile(pairs:any):Promise { } function hash(path: string, algorithm: string): Promise { + if(typeof path !== 'string' || typeof algorithm !== 'string') { + const err = new TypeError('Missing argument "path" and/or "algorithm"') + err.code = 'EINVAL' + return Promise.reject(err) + } return RNFetchBlob.hash(path, algorithm) } @@ -283,7 +296,12 @@ function lstat(path:string):Promise> { } function ls(path:string):Promise> { - return RNFetchBlob.ls(path) + if(typeof path !== 'string') { + const err = new TypeError('Missing argument "path" ') + err.code = 'EINVAL' + return Promise.reject(err) + } + return RNFetchBlob.ls(path) } /** From c2e1d630211b5ac721b0990c32d508a8751f08fe Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 21:58:36 +0200 Subject: [PATCH 22/43] forgot to resolve the promise --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 93c6bfd4e..fc5ea9d9f 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -606,6 +606,7 @@ static void ls(String path, Promise promise) { for (String i : files) { arg.pushString(i); } + promise.resolve(arg) } catch (Exception e) { e.printStackTrace(); promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); From 51d7453117fa6e0a76aa30b07df4219c7aeb44af Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Tue, 15 Aug 2017 22:01:19 +0200 Subject: [PATCH 23/43] Forgot ; --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index fc5ea9d9f..878802d01 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -606,7 +606,7 @@ static void ls(String path, Promise promise) { for (String i : files) { arg.pushString(i); } - promise.resolve(arg) + promise.resolve(arg); } catch (Exception e) { e.printStackTrace(); promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); From 46746927cc1df8eefd6eb3d601495d3e5533c4fe Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Wed, 16 Aug 2017 00:15:29 +0200 Subject: [PATCH 24/43] add more error parameter checks --- fs.js | 283 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 143 insertions(+), 140 deletions(-) diff --git a/fs.js b/fs.js index fab830703..41c8657a3 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,25 +46,20 @@ 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' - if(encoding.toLowerCase() === 'ascii') { - if(Array.isArray(data)) - return RNFetchBlob.createFileASCII(path, data) - else { - const err = new TypeError('`data` of ASCII file must be an array with 0..255 numbers') - err.code = 'EINVAL' - return Promise.reject(err) - } +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) @@ -82,17 +74,16 @@ 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' = 'base64', + 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) + RNFetchBlob.writeStream(path, encoding, append, (err, streamId: string) => { + if (err) reject(new Error(err)) else resolve(new RNFetchBlobWriteStream(streamId, encoding)) @@ -109,11 +100,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' = 'base64', + 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)) } @@ -122,8 +116,11 @@ function readStream( * @param {string} path Path of directory to be created * @return {Promise} */ -function mkdir(path:string):Promise { - return RNFetchBlob.mkdir(path) +function mkdir(path: string): Promise { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) + } + return RNFetchBlob.mkdir(path) } /** @@ -131,7 +128,7 @@ function mkdir(path:string):Promise { * @param {string} groupName Name of app group * @return {Promise} */ -function pathForAppGroup(groupName:string):Promise { +function pathForAppGroup(groupName: string): Promise { return RNFetchBlob.pathForAppGroup(groupName) } @@ -141,12 +138,9 @@ function pathForAppGroup(groupName:string):Promise { * @param {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream. * @return {Promise | string>} */ -function readFile(path:string, encoding:string):Promise { - encoding = encoding || 'utf8' - if(typeof path !== 'string') { - const err = new TypeError('Missing argument "path" ') - err.code = 'EINVAL' - return Promise.reject(err) +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) } @@ -158,50 +152,39 @@ 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') { - const err = new TypeError('Missing argument "path" ') - err.code = 'EINVAL' - return Promise.reject(err) +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)) { - const err = new TypeError('"data" must be an Array when encoding is "ascii"') - err.code = 'EINVAL' - return Promise.reject(err) + 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') { - const err = new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`) - err.code = 'EINVAL' - return Promise.reject(err) + 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) } } -function appendFile(path:string, data:string | Array, encoding:?string): Promise { - encoding = encoding || 'utf8' - if(typeof path !== 'string') { - const err = new TypeError('Missing argument "path" ') - err.code = 'EINVAL' - return Promise.reject(err) +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)) { - const err = new TypeError('`data` of ASCII file must be an array with 0..255 numbers') - err.code = 'EINVAL' - return Promise.reject(err) + 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') { + } + else { + if (typeof data !== 'string') { const err = new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`) err.code = 'EINVAL' return Promise.reject(err) @@ -216,18 +199,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') { - const err = new TypeError('Missing argument "path" ') - err.code = 'EINVAL' - return reject(err) + 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) } @@ -242,11 +223,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() }) @@ -254,52 +238,57 @@ function scanFile(pairs:any):Promise { } function hash(path: string, algorithm: string): Promise { - if(typeof path !== 'string' || typeof algorithm !== 'string') { - const err = new TypeError('Missing argument "path" and/or "algorithm"') - err.code = 'EINVAL' - return Promise.reject(err) + 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> { - if(typeof path !== 'string') { - const err = new TypeError('Missing argument "path" ') - err.code = 'EINVAL' - return Promise.reject(err) +function ls(path: string): Promise> { + if (typeof path !== 'string') { + return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) } return RNFetchBlob.ls(path) } @@ -309,11 +298,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() @@ -326,59 +318,70 @@ function unlink(path:string):Promise { * @param {string} path Path to check * @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) }) From 3bb3bf123db8e1b26db1778fb5793d69f65cd65d Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Wed, 16 Aug 2017 10:54:49 +0200 Subject: [PATCH 25/43] change readStream()/writeStream() default encoding to utf8 to match the tests in react-native-fetch-blob-dev --- fs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs.js b/fs.js index 41c8657a3..e34a475be 100644 --- a/fs.js +++ b/fs.js @@ -75,7 +75,7 @@ function createFile(path: string, data: string, encoding: 'base64' | 'ascii' | ' */ function writeStream( path: string, - encoding?: 'utf8' | 'ascii' | 'base64' = 'base64', + encoding?: 'utf8' | 'ascii' | 'base64' = 'utf8', append?: boolean = false, ): Promise { if (typeof path !== 'string') { @@ -101,7 +101,7 @@ function writeStream( */ function readStream( path: string, - encoding: 'utf8' | 'ascii' | 'base64' = 'base64', + encoding: 'utf8' | 'ascii' | 'base64' = 'utf8', bufferSize?: number, tick?: number = 10 ): Promise { From 45dd56c453392f95b5bec9290423a1ce3af89647 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Wed, 16 Aug 2017 15:02:19 +0200 Subject: [PATCH 26/43] default encoding is set in fs.js (now), no need to do it twice --- index.js | 1 - 1 file changed, 1 deletion(-) 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 { From c2803a1cb2cf977ec824674912d3154aed261512 Mon Sep 17 00:00:00 2001 From: Ben Hsieh Date: Thu, 3 Aug 2017 09:37:08 +0800 Subject: [PATCH 27/43] fs.js: forgot one more error refactoring --- fs.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs.js b/fs.js index e34a475be..fad2931ff 100644 --- a/fs.js +++ b/fs.js @@ -185,9 +185,7 @@ function appendFile(path: string, data: string | Array, encoding?: strin } else { if (typeof data !== 'string') { - const err = new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`) - err.code = 'EINVAL' - return Promise.reject(err) + 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) From a9cb6063441a1c29208eb56bffd7b637509ed531 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 08:55:19 +0200 Subject: [PATCH 28/43] ReadStream error events: Set a default error code "EUNSPECIFIED" if no code is returned (should not happen, actually) --- class/RNFetchBlobReadStream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class/RNFetchBlobReadStream.js b/class/RNFetchBlobReadStream.js index a3d6c0498..84585d99a 100644 --- a/class/RNFetchBlobReadStream.js +++ b/class/RNFetchBlobReadStream.js @@ -45,7 +45,7 @@ export default class RNFetchBlobReadStream { } else { const err = new Error(detail) - err.code = code + err.code = code || 'EUNSPECIFIED' if(this._onError) this._onError(err) else From 1080fae5b6675fdf3cb287f3615c05642b021303 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 08:57:18 +0200 Subject: [PATCH 29/43] writeFile() Android and iOS: improve errors; ReadStream: Add "ENOENT" (no such file) error event to Android version and add the thus far missing "code" parameter to iOS version --- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 56 ++++--- ios/RNFetchBlobFS.m | 150 ++++++++++-------- 2 files changed, 118 insertions(+), 88 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 878802d01..4ef7d894b 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -55,27 +55,27 @@ static void writeFile(String path, String encoding, String data, final boolean a int written; File f = new File(path); File dir = f.getParentFile(); - if(!dir.exists()) { - boolean result = dir.mkdirs(); - if (!result) { - promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + path + "'"); - return; - } - } + if(!f.exists()) { - boolean result = f.createNewFile(); - if (!result) { - promise.reject("EUNSPECIFIED", "Failed to create file '" + path + "'"); + if(!dir.exists()) { + if (!dir.mkdirs()) { + promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + 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)) { String normalizedData = normalizePath(data); File src = new File(normalizedData); if (!src.exists()) { - promise.reject("ENOENT", "No such file '" + normalizedData + "'"); + promise.reject("ENOENT", "No such file '" + path + "' " + "('" + normalizedData + "')"); fout.close(); return; } @@ -114,20 +114,20 @@ static void writeFile(String path, ReadableArray data, final boolean append, fin try { File f = new File(path); File dir = f.getParentFile(); - if(!dir.exists()) { - boolean result = dir.mkdirs(); - if (!result) { - promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + path + "'"); - return; - } - } + if(!f.exists()) { - boolean result = f.createNewFile(); - if (!result) { - promise.reject("ENOENT", "File '" + path + "' does not exist and could not be created, or it is a directory"); + if(!dir.exists()) { + if (!dir.mkdirs()) { + promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + 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()]; for(int i=0;i 0) chunkSize = bufferSize; @@ -276,7 +276,6 @@ void readStream(String path, String encoding, int bufferSize, int tick, final St 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) { @@ -336,7 +335,14 @@ else if(resolved == null) { emitStreamEvent(streamId, "end", ""); fs.close(); buffer = null; - + } catch (FileNotFoundException err) { + emitStreamEvent( + streamId, + "error", + "ENOENT", + "No such file '" + path + "'" + ); + } } catch (Exception err) { emitStreamEvent( streamId, diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 5c3fdee8e..479e48863 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -166,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 ; @@ -199,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": @"ENOENT", @"detail": @"Unable to resolve URI" }; [event sendDeviceEventWithName:streamId body:payload]; } // release buffer @@ -209,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 @@ -347,15 +347,21 @@ + (void) writeFile:(NSString *)path NSString * folder = [path stringByDeletingLastPathComponent]; encoding = [encoding lowercaseString]; - 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) { - reject(@"EUNSPECIFIED", [err description], nil); - return; + return reject(@"EUNSPECIFIED", @[[NSString stringWithFormat:@"Failed to create parent directory '%@', error: %@", path, [err description]]]], nil); } if(![fm createFileAtPath:path contents:nil attributes:nil]) { - reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created, or it is a directory", path], nil); - return; + return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil); } } @@ -408,11 +414,19 @@ + (void) writeFileArray:(NSString *)path // 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) { - reject(@"EUNSPECIFIED", [err description], nil); - return; + return reject(@"EUNSPECIFIED", @[[NSString stringWithFormat:@"Failed to create parent directory '%@', error: %@", path, [err description]]]], nil); } } @@ -423,8 +437,11 @@ + (void) writeFileArray:(NSString *)path 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 { @@ -513,73 +530,78 @@ + (void) readFile:(NSString *)path # pragma mark - hash -RCT_EXPORT_METHOD(hash:(NSString *)filepath +RCT_EXPORT_METHOD(hash:(NSString *)path algorithm:(NSString *)algorithm resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filepath]; - - if (!fileExists) { - return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", 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 (!fileExists) { + 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", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", filepath], nil); - } + if (error) { + return [self reject:reject withError:error]; + } - NSData *content = [[NSFileManager defaultManager] contentsAtPath:filepath]; + if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) { + return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + } - 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(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", 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(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", 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 { + return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil); + } - 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 @@ -740,7 +762,9 @@ + (void)slice:(NSString *)path 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) { From 36195ca90341dd9c4350828e0324cfdcedee784e Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 09:07:22 +0200 Subject: [PATCH 30/43] oops - one "}" too many - removed --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 1 - 1 file changed, 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 4ef7d894b..4e46f56c5 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -342,7 +342,6 @@ else if(resolved == null) { "ENOENT", "No such file '" + path + "'" ); - } } catch (Exception err) { emitStreamEvent( streamId, From 9cde86a5cc89a1adce0b9f0dc6b50935b0f7a56e Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 09:29:56 +0200 Subject: [PATCH 31/43] add EISDIR error to readFile()s error vocabulary (iOS and Android) --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 7 ++++++- ios/RNFetchBlobFS.m | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 4e46f56c5..2c6d1f16e 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -214,7 +214,12 @@ else if(resolved == null) { } } catch(FileNotFoundException err) { - promise.reject("ENOENT", "No such file or directory '" + path + "'; " + err.getLocalizedMessage()); + 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 or directory '" + path + "'; " + msg); + } } catch(Exception err) { promise.reject("EUNSPECIFIED", err.getLocalizedMessage()); diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 479e48863..cb297db25 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -490,8 +490,13 @@ + (void) readFile:(NSString *)path } else { - if(![[NSFileManager defaultManager] fileExistsAtPath:path]) { - onComplete(nil, @"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path]); + 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]; From 54afd0124b983903334a3ad6d033741e3a466777 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 09:47:26 +0200 Subject: [PATCH 32/43] "or directory" is misplaced in a "no such file" error message for readFile() --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 2c6d1f16e..d6f8dab1f 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -218,7 +218,7 @@ else if(resolved == null) { if (msg.contains("EISDIR")) { promise.reject("EISDIR", "Expecting a file but '" + path + "' is a directory; " + msg); } else { - promise.reject("ENOENT", "No such file or directory '" + path + "'; " + msg); + promise.reject("ENOENT", "No such file '" + path + "'; " + msg); } } catch(Exception err) { From ea6e4d9bba0218329d0c89bb1b4b0965ed5ba197 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 12:11:02 +0200 Subject: [PATCH 33/43] Android: two reject() calls did not have a code, iOS: slice() did not have code EISDIR, and "could not resolve URI" now is EINVAL everywhere --- .../src/main/java/com/RNFetchBlob/RNFetchBlob.java | 6 +++--- ios/RNFetchBlobFS.m | 14 +++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 7c6af6fc1..7ab1f19b4 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -132,7 +132,7 @@ public void onHostDestroy() { }; RCTContext.addLifecycleEventListener(listener); } catch(Exception ex) { - promise.reject(ex.getLocalizedMessage()); + promise.reject("EUNSPECIFIED", ex.getLocalizedMessage()); } } @@ -355,7 +355,7 @@ public void addCompleteDownload (ReadableMap config, Promise promise) { 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 { @@ -372,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/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index cb297db25..7ccd2b85a 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -199,7 +199,7 @@ + (void) readStream:(NSString *)uri } else { - NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"ENOENT", @"detail": @"Unable to resolve URI" }; + NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EINVAL", @"detail": @"Unable to resolve URI" }; [event sendDeviceEventWithName:streamId body:payload]; } // release buffer @@ -759,10 +759,18 @@ + (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) { + + 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]); From 9d64f5bab0bb9ca06d93c58e18c5e3c4881fd4cc Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 13:33:54 +0200 Subject: [PATCH 34/43] writeStream: return ENOENT, EISDIR and EUNSPECIFIED according to the normalized schema (#460); Open a new question about behavior on ENOENT (#491) --- .../main/java/com/RNFetchBlob/RNFetchBlobFS.java | 13 ++++++++----- fs.js | 6 ++++-- ios/RNFetchBlob/RNFetchBlob.m | 12 ++++++++---- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index d6f8dab1f..cd3a250b9 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -367,8 +367,12 @@ else if(resolved == null) { */ void writeStream(String path, String encoding, boolean append, Callback callback) { File dest = new File(path); - if(!dest.exists() || dest.isDirectory()) { - callback.invoke("target path `" + path + "` may not exist or it is a folder"); + if(!dest.exists()) { + callback.invoke("ENOENT", "No such file `" + path + "'"); + return; + } + if(dest.isDirectory()) { + callback.invoke("EISDIR", "Expecting a file but '" + path + "' is a directory"); return; } try { @@ -377,11 +381,10 @@ void writeStream(String path, String encoding, boolean append, Callback callback String streamId = UUID.randomUUID().toString(); RNFetchBlobFS.fileStreams.put(streamId, this); this.writeStreamInstance = fs; - callback.invoke(null, streamId); + callback.invoke(null, null, streamId); } catch(Exception err) { - callback.invoke("EUNSPECIFIED: Failed to create write stream at path `" + path + "` " + err.getLocalizedMessage()); + callback.invoke("EUNSPECIFIED", "Failed to create write stream at path `" + path + "`; " + err.getLocalizedMessage()); } - } /** diff --git a/fs.js b/fs.js index fad2931ff..943482af7 100644 --- a/fs.js +++ b/fs.js @@ -82,9 +82,11 @@ function writeStream( return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) } return new Promise((resolve, reject) => { - RNFetchBlob.writeStream(path, encoding, append, (err, streamId: string) => { + RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => { if (err) - reject(new Error(err)) + const err = new Error(errMsg) + err.code = errCode + reject(err) else resolve(new RNFetchBlobWriteStream(streamId, encoding)) }) diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index fe126f358..eb5972ef1 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -212,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); } } @@ -240,12 +240,16 @@ - (NSDictionary *)constantsToExport 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]]); + if(exist == NO) { + callback(@[@"ENOENT", [NSString stringWithFormat:@"No such file `%@`", path]]); + return; + } + if(isDir == YES) { + callback(@[@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]]); return; } NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append]; - callback(@[[NSNull null], streamId]); + callback(@[[NSNull null], @[NSNull null], streamId]); } #pragma mark - fs.writeArrayChunk From 3394cd174fa2bea315c4120f311476aba6e0d668 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 13:39:59 +0200 Subject: [PATCH 35/43] "+ +" was one plus sign too many --- android/src/main/java/com/RNFetchBlob/RNFetchBlob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 7ab1f19b4..c4980d445 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -355,7 +355,7 @@ public void addCompleteDownload (ReadableMap config, Promise promise) { DownloadManager dm = (DownloadManager) RCTContext.getSystemService(RCTContext.DOWNLOAD_SERVICE); String path = RNFetchBlobFS.normalizePath(config.getString("path")); if(path == null) { - promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + + config.getString("path")); + promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path")); return; } try { From 354dc48cd7f50619500f994cb19a7d8f98cafea5 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 13:43:42 +0200 Subject: [PATCH 36/43] this if has a whole block (that ois why I prefer a style where {} are mandatory even for single-statement blocks) --- fs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs.js b/fs.js index 943482af7..9733c217a 100644 --- a/fs.js +++ b/fs.js @@ -83,10 +83,11 @@ function writeStream( } return new Promise((resolve, reject) => { RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => { - if (err) + if (err) { const err = new Error(errMsg) err.code = errCode reject(err) + } else resolve(new RNFetchBlobWriteStream(streamId, encoding)) }) From ef9745db124b634c801c1150ada4911312c904ff Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 13:44:42 +0200 Subject: [PATCH 37/43] I renamed this variable --- fs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.js b/fs.js index 9733c217a..a286e937a 100644 --- a/fs.js +++ b/fs.js @@ -83,7 +83,7 @@ function writeStream( } return new Promise((resolve, reject) => { RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => { - if (err) { + if (errMsg) { const err = new Error(errMsg) err.code = errCode reject(err) From 3292b3821d4501cf003e8ac9ef5f538762746e9f Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 20:51:06 +0200 Subject: [PATCH 38/43] 1) #491 "writeStream() does not create file if it doesn't exist?" 2) I had gone overboard with the "@[..]" in the ios code, making some error strings arrays 3) fix typos: rename all ENODIR => ENOTDIR --- .../java/com/RNFetchBlob/RNFetchBlobFS.java | 34 ++++++++++++------- ios/RNFetchBlob/RNFetchBlob.m | 32 ++++++++++------- ios/RNFetchBlobFS.m | 10 +++--- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index cd3a250b9..a8d8f96f9 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -59,7 +59,7 @@ static void writeFile(String path, String encoding, String data, final boolean a if(!f.exists()) { if(!dir.exists()) { if (!dir.mkdirs()) { - promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + path + "'"); + promise.reject("EUNSPECIFIED", "Failed to create parent directory of '" + path + "'"); return; } } @@ -118,7 +118,7 @@ static void writeFile(String path, ReadableArray data, final boolean append, fin if(!f.exists()) { if(!dir.exists()) { if (!dir.mkdirs()) { - promise.reject("EUNSPECIFIED", "Failed to create parent directory '" + path + "'"); + promise.reject("ENOTDIR", "Failed to create parent directory of '" + path + "'"); return; } } @@ -366,16 +366,26 @@ else if(resolved == null) { * @param callback Callback */ void writeStream(String path, String encoding, boolean append, Callback callback) { - File dest = new File(path); - if(!dest.exists()) { - callback.invoke("ENOENT", "No such file `" + path + "'"); - return; - } - if(dest.isDirectory()) { - callback.invoke("EISDIR", "Expecting a file but '" + path + "' is a directory"); - return; - } try { + File dest = new File(path); + File dir = dest.getParentFile(); + + if(!dest.exists()) { + if(!dir.exists()) { + if (!dir.mkdirs()) { + callback.invoke("ENOTDIR", "Failed to create parent directory of '" + path + "'"); + return; + } + } + if(!dest.createNewFile()) { + callback.invoke("ENOENT", "File '" + path + "' does not exist and could not be created"); + return; + } + } else if(dest.isDirectory()) { + callback.invoke("EISDIR", "Expecting a file but '" + path + "' is a directory"); + return; + } + OutputStream fs = new FileOutputStream(path, append); this.encoding = encoding; String streamId = UUID.randomUUID().toString(); @@ -609,7 +619,7 @@ static void ls(String path, Promise promise) { return; } if (!src.isDirectory()) { - promise.reject("ENODIR", "Not a directory '" + path + "'"); + promise.reject("ENOTDIR", "Not a directory '" + path + "'"); return; } String[] files = new File(path).list(); diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index eb5972ef1..0c2ef5361 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -159,14 +159,14 @@ - (NSDictionary *)constantsToExport } if ([fm fileExistsAtPath:path]) { - reject(@"EEXIST", @[[NSString stringWithFormat:@"File '%@' already exists", path]], nil); + 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); + reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create new file at path '%@', please ensure the folder exists", path], nil); } } @@ -189,14 +189,14 @@ - (NSDictionary *)constantsToExport [fileContent appendBytes:bytes length:dataArray.count]; if ([fm fileExistsAtPath:path]) { - reject(@"EEXIST", @[[NSString stringWithFormat:@"File '%@' already exists", path]], nil); + 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); + reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"failed to create new file at path '%@', please ensure the folder exists", path], nil); } free(bytes); @@ -238,16 +238,24 @@ - (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) { - callback(@[@"ENOENT", [NSString stringWithFormat:@"No such file `%@`", path]]); - return; + NSString * folder = [path stringByDeletingLastPathComponent]; + + 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]]); + } } - if(isDir == YES) { + else if(isDir) { callback(@[@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]]); - return; } + NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append]; callback(@[[NSNull null], @[NSNull null], streamId]); } @@ -323,7 +331,7 @@ - (NSDictionary *)constantsToExport return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil); } if(isDir == NO) { - return reject(@"ENODIR", [NSString stringWithFormat:@"Not a directory '%@'", path], nil); + return reject(@"ENOTDIR", [NSString stringWithFormat:@"Not a directory '%@'", path], nil); } NSError * error = nil; NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 7ccd2b85a..4af0c50cc 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -349,7 +349,7 @@ + (void) writeFile:(NSString *)path BOOL isDir = NO; BOOL exists = NO; - exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir]; + exists = [fm fileExistsAtPath:path isDirectory: &isDir]; if (isDir) { return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); @@ -358,7 +358,7 @@ + (void) writeFile:(NSString *)path if(!exists) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; if(err != nil) { - return reject(@"EUNSPECIFIED", @[[NSString stringWithFormat:@"Failed to create parent directory '%@', error: %@", path, [err description]]]], nil); + return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); } if(![fm createFileAtPath:path contents:nil attributes:nil]) { return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil); @@ -426,7 +426,7 @@ + (void) writeFileArray:(NSString *)path if(!exists) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; if(err != nil) { - return reject(@"EUNSPECIFIED", @[[NSString stringWithFormat:@"Failed to create parent directory '%@', error: %@", path, [err description]]]], nil); + return reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); } } @@ -616,7 +616,7 @@ + (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve reject BOOL isDir = NO; NSError * err = nil; if([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) { - reject(@"EEXIST", @[[NSString stringWithFormat:@"%@ '%@' already exists", isDir ? @"Directory" : @"File", path]], nil); + reject(@"EEXIST", [NSString stringWithFormat:@"%@ '%@' already exists", isDir ? @"Directory" : @"File", path], nil); } else { [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err]; @@ -625,7 +625,7 @@ + (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve reject resolve(@YES); } else { - reject(@"EUNSPECIFIED", @[[NSString stringWithFormat:@"Error creating folder '%@', error: %@", path, [err description]]]], nil); + reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Error creating folder '%@', error: %@", path, [err description]], nil); } } From 131dab2984b5caa4417aa203120b3f7e9989f9f1 Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Thu, 17 Aug 2017 21:03:23 +0200 Subject: [PATCH 39/43] Java: getParentFolder() may return null - prevent a NullPointerException by adding one more check --- android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java | 6 +++--- .../java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index a8d8f96f9..dde39bc4c 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -57,7 +57,7 @@ static void writeFile(String path, String encoding, String data, final boolean a File dir = f.getParentFile(); if(!f.exists()) { - if(!dir.exists()) { + if(dir != null && !dir.exists()) { if (!dir.mkdirs()) { promise.reject("EUNSPECIFIED", "Failed to create parent directory of '" + path + "'"); return; @@ -116,7 +116,7 @@ static void writeFile(String path, ReadableArray data, final boolean append, fin File dir = f.getParentFile(); if(!f.exists()) { - if(!dir.exists()) { + if(dir != null && !dir.exists()) { if (!dir.mkdirs()) { promise.reject("ENOTDIR", "Failed to create parent directory of '" + path + "'"); return; @@ -371,7 +371,7 @@ void writeStream(String path, String encoding, boolean append, Callback callback File dir = dest.getParentFile(); if(!dest.exists()) { - if(!dir.exists()) { + if(dir != null && !dir.exists()) { if (!dir.mkdirs()) { callback.invoke("ENOTDIR", "Failed to create parent directory of '" + path + "'"); return; diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java index 686ac8d5b..e4f725777 100644 --- a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java +++ b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java @@ -49,7 +49,7 @@ 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); } From e74c944a26854543b2a15d8fc561f166b19955da Mon Sep 17 00:00:00 2001 From: Michael Hasenstein Date: Mon, 21 Aug 2017 23:52:54 +0200 Subject: [PATCH 40/43] Relating to #298 -- looping through an array is not supposed to be done with for...in --- polyfill/XMLHttpRequest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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}` } } From dbafc988251fe4e4858bee09e578bbc06dc7c2b4 Mon Sep 17 00:00:00 2001 From: Ben Hsieh Date: Sun, 27 Aug 2017 13:26:43 +0800 Subject: [PATCH 41/43] Fix IOS syntax errors in #489 --- ios/RNFetchBlob/RNFetchBlob.m | 7 ++++--- ios/RNFetchBlobFS.h | 7 ++++++- ios/RNFetchBlobFS.m | 17 ++++++++++------- ios/RNFetchBlobReqBuilder.m | 8 ++++---- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 0c2ef5361..47bda76c5 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -239,7 +239,7 @@ - (NSDictionary *)constantsToExport RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge]; NSFileManager * fm = [NSFileManager defaultManager]; NSString * folder = [path stringByDeletingLastPathComponent]; - + NSError* err = nil; BOOL isDir = NO; BOOL exists = [fm fileExistsAtPath:path isDirectory: &isDir]; @@ -257,7 +257,7 @@ - (NSDictionary *)constantsToExport } NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append]; - callback(@[[NSNull null], @[NSNull null], streamId]); + callback(@[[NSNull null], [NSNull null], streamId]); } #pragma mark - fs.writeArrayChunk @@ -467,7 +467,8 @@ - (NSDictionary *)constantsToExport resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(id content, NSString * code, NSString * err) { + + [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(NSData * content, NSString * code, NSString * err) { if(err != nil) { reject(code, err, nil); return; 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 4af0c50cc..9153b0546 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -469,7 +469,7 @@ + (void) writeFileArray:(NSString *)path + (void) readFile:(NSString *)path encoding:(NSString *)encoding - onComplete:(void (^)(id content, NSString * codeStr, NSString * errMsg))onComplete + onComplete:(void (^)(NSData * content, NSString * codeStr, NSString * errMsg))onComplete { [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) { __block NSData * fileContent; @@ -535,10 +535,10 @@ + (void) readFile:(NSString *)path # pragma mark - hash -RCT_EXPORT_METHOD(hash:(NSString *)path ++ (void) hash:(NSString *)path algorithm:(NSString *)algorithm resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + rejecter:(RCTPromiseRejectBlock)reject { BOOL isDir = NO; BOOL exists = NO; @@ -547,7 +547,7 @@ + (void) readFile:(NSString *)path if (isDir) { return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); } - if (!fileExists) { + if (!exists) { return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil); } @@ -556,11 +556,13 @@ + (void) readFile:(NSString *)path NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; if (error) { - return [self reject:reject withError:error]; + reject(@"EUNKNOWN", [error description], nil); + return; } if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) { - return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil); + return; } NSData *content = [[NSFileManager defaultManager] contentsAtPath:path]; @@ -599,7 +601,8 @@ + (void) readFile:(NSString *)path } else if ([algorithm isEqualToString:@"sha512"]) { CC_SHA512(content.bytes, (CC_LONG)content.length, buffer); } else { - return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil); + 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]; 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); From 0b904488b10ea1cc1f39d7af350d0e947673f558 Mon Sep 17 00:00:00 2001 From: Ben Hsieh Date: Thu, 31 Aug 2017 22:25:00 +0800 Subject: [PATCH 42/43] #489 Fix typo and missing return statement --- ios/RNFetchBlobFS.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 9153b0546..65a3bc3de 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -358,7 +358,7 @@ + (void) writeFile:(NSString *)path 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); + return reject(@"ENODIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); } if(![fm createFileAtPath:path contents:nil attributes:nil]) { return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil); @@ -620,6 +620,7 @@ + (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve reject NSError * err = nil; 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]; From 9dc25068c091e19efd1a95fe0475e90e01cfdf37 Mon Sep 17 00:00:00 2001 From: Ben Hsieh Date: Thu, 31 Aug 2017 23:11:42 +0800 Subject: [PATCH 43/43] fix error code --- ios/RNFetchBlobFS.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 65a3bc3de..1d16ffa40 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -358,7 +358,7 @@ + (void) writeFile:(NSString *)path if(!exists) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; if(err != nil) { - return reject(@"ENODIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); + return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil); } if(![fm createFileAtPath:path contents:nil attributes:nil]) { return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);