Skip to content

Commit 7fa5761

Browse files
lll000111wkh237
authored andcommitted
My proposed 0.10.9 changes (#489)
* fs.js: forgot one more error refactoring * Fix path argument in iOS excludeFromBackupKey (#473) * Fix link to fs.readStream() and to fs.writeStream() and insert link to new function fs.hash() * Fix the documentation part of wkh237#467 "Example code for writeStream ignores that stream.write() returns a promise?" * More fixes for issue wkh237#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 * Fix one issue raised in wkh237#477 by using code from https://stackoverflow.com/a/40874952/544779 * fix some access rights, remove unused items * update gradle version setting in build.gradle * Revert gradle settings to previous values :-( * add a missing closing ")" * Removed the part of an obsolete callback function parameter that I had left in when I converted mkdir to promises (low-level code) * let mkdir resolve with "undefined" instead of "null" (my mistake) * 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 * fix a long/int issue * 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 * Adde "utf8" as default encoding for fs.readFile - fixes #450 and #484 * follow my IDEA IDE's recommendations - SparseArray instead of HashMap, and make some fields private * polyfill/File.js: add a parameter===undefined? check (this happened silently in the test suite) * make var static again * Normalized errors for fs.ls() * forgot one parameter * more parameter checks * forgot to resolve the promise * Forgot ; * add more error parameter checks * change readStream()/writeStream() default encoding to utf8 to match the tests in react-native-fetch-blob-dev * default encoding is set in fs.js (now), no need to do it twice * ReadStream error events: Set a default error code "EUNSPECIFIED" if no code is returned (should not happen, actually) * 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 * oops - one "}" too many - removed * add EISDIR error to readFile()s error vocabulary (iOS and Android) * "or directory" is misplaced in a "no such file" error message for readFile() * 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 * writeStream: return ENOENT, EISDIR and EUNSPECIFIED according to the normalized schema (#460); Open a new question about behavior on ENOENT (#491) * "+ +" was one plus sign too many * this if has a whole block (that ois why I prefer a style where {} are mandatory even for single-statement blocks) * I renamed this variable * 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: getParentFolder() may return null - prevent a NullPointerException by adding one more check * Relating to #298 -- looping through an array is not supposed to be done with for...in * Fix IOS syntax errors in #489 * #489 Fix typo and missing return statement * fix error code
1 parent a78acc7 commit 7fa5761

21 files changed

+867
-596
lines changed

Diff for: README.md

+49-4
Original file line numberDiff line numberDiff line change
@@ -590,10 +590,11 @@ File Access APIs
590590
- [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
591591
- [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
592592
- [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
593-
- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--array-encodingstringpromise)
593+
- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--arraynumber-encodingstring-promisenumber)
594594
- [readFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readfilepath-encodingpromise)
595-
- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersizepromise)
596-
- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstring-appendbooleanpromise)
595+
- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersize-interval-promisernfbreadstream)
596+
- [hash (0.10.9)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithm-promise)
597+
- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstringpromise)
597598
- [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
598599
- [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
599600
- [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
@@ -644,6 +645,45 @@ RNFetchBlob.fs.readStream(
644645

645646
When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`.
646647

648+
Since version 0.10.9 `write()` resolves with the `RNFetchBlob` instance so you can promise-chain write calls:
649+
650+
```js
651+
RNFetchBlob.fs.writeStream(
652+
PATH_TO_FILE,
653+
// encoding, should be one of `base64`, `utf8`, `ascii`
654+
'utf8',
655+
// should data append to existing content ?
656+
true
657+
)
658+
.then(ofstream => ofstream.write('foo'))
659+
.then(ofstream => ofstream.write('bar'))
660+
.then(ofstream => ofstream.write('foobar'))
661+
.then(ofstream => ofstream.close())
662+
.catch(console.error)
663+
```
664+
665+
or
666+
667+
```js
668+
RNFetchBlob.fs.writeStream(
669+
PATH_TO_FILE,
670+
// encoding, should be one of `base64`, `utf8`, `ascii`
671+
'utf8',
672+
// should data append to existing content ?
673+
true
674+
)
675+
.then(stream => Promise.all([
676+
stream.write('foo'),
677+
stream.write('bar'),
678+
stream.write('foobar')
679+
]))
680+
// Use array destructuring to get the stream object from the first item of the array we get from Promise.all()
681+
.then(([stream]) => stream.close())
682+
.catch(console.error)
683+
```
684+
685+
You should **NOT** do something like this:
686+
647687
```js
648688
RNFetchBlob.fs.writeStream(
649689
PATH_TO_FILE,
@@ -652,13 +692,18 @@ RNFetchBlob.fs.writeStream(
652692
// should data append to existing content ?
653693
true)
654694
.then((ofstream) => {
695+
// BAD IDEA - Don't do this, those writes are unchecked:
655696
ofstream.write('foo')
656697
ofstream.write('bar')
657698
ofstream.close()
658699
})
659-
700+
.catch(console.error) // Cannot catch any write() errors!
660701
```
661702

703+
The problem with the above code is that the promises from the `ofstream.write()` calls are detached and "Lost".
704+
That means the entire promise chain A) resolves without waiting for the writes to finish and B) any errors caused by them are lost.
705+
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.
706+
662707
### Cache File Management
663708

664709
When using `fileCache` or `path` options along with `fetch` API, response data will automatically store into the file system. The files will **NOT** removed unless you `unlink` it. There're several ways to remove the files

Diff for: android/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@ android {
3434

3535
dependencies {
3636
compile 'com.facebook.react:react-native:+'
37+
//compile 'com.squareup.okhttp3:okhttp:+'
3738
//{RNFetchBlob_PRE_0.28_DEPDENDENCY}
3839
}

Diff for: android/gradle/wrapper/gradle-wrapper.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#Wed May 18 12:33:41 CST 2016
1+
#Sat Aug 12 07:48:35 CEST 2017
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME

Diff for: android/src/main/java/com/RNFetchBlob/RNFetchBlob.java

+28-31
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.app.DownloadManager;
55
import android.content.Intent;
66
import android.net.Uri;
7+
import android.util.SparseArray;
78

89
import com.facebook.react.bridge.ActivityEventListener;
910
import com.facebook.react.bridge.Callback;
@@ -34,26 +35,23 @@
3435

3536
public class RNFetchBlob extends ReactContextBaseJavaModule {
3637

37-
// Cookies
38-
private final ForwardingCookieHandler mCookieHandler;
39-
private final CookieJarContainer mCookieJarContainer;
4038
private final OkHttpClient mClient;
4139

4240
static ReactApplicationContext RCTContext;
43-
static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
44-
static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
41+
private static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
42+
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
4543
static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
46-
static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
47-
static public boolean ActionViewVisible = false;
48-
static HashMap<Integer, Promise> promiseTable = new HashMap<>();
44+
private static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
45+
private static boolean ActionViewVisible = false;
46+
private static SparseArray<Promise> promiseTable = new SparseArray<>();
4947

5048
public RNFetchBlob(ReactApplicationContext reactContext) {
5149

5250
super(reactContext);
5351

5452
mClient = OkHttpClientProvider.getOkHttpClient();
55-
mCookieHandler = new ForwardingCookieHandler(reactContext);
56-
mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
53+
ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler(reactContext);
54+
CookieJarContainer mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
5755
mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
5856

5957
RCTContext = reactContext;
@@ -85,11 +83,21 @@ public Map<String, Object> getConstants() {
8583
}
8684

8785
@ReactMethod
88-
public void createFile(final String path, final String content, final String encode, final Callback callback) {
86+
public void createFile(final String path, final String content, final String encode, final Promise promise) {
8987
threadPool.execute(new Runnable() {
9088
@Override
9189
public void run() {
92-
RNFetchBlobFS.createFile(path, content, encode, callback);
90+
RNFetchBlobFS.createFile(path, content, encode, promise);
91+
}
92+
});
93+
}
94+
95+
@ReactMethod
96+
public void createFileASCII(final String path, final ReadableArray dataArray, final Promise promise) {
97+
threadPool.execute(new Runnable() {
98+
@Override
99+
public void run() {
100+
RNFetchBlobFS.createFileASCII(path, dataArray, promise);
93101
}
94102
});
95103

@@ -124,21 +132,10 @@ public void onHostDestroy() {
124132
};
125133
RCTContext.addLifecycleEventListener(listener);
126134
} catch(Exception ex) {
127-
promise.reject(ex.getLocalizedMessage());
135+
promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
128136
}
129137
}
130138

131-
@ReactMethod
132-
public void createFileASCII(final String path, final ReadableArray dataArray, final Callback callback) {
133-
threadPool.execute(new Runnable() {
134-
@Override
135-
public void run() {
136-
RNFetchBlobFS.createFileASCII(path, dataArray, callback);
137-
}
138-
});
139-
140-
}
141-
142139
@ReactMethod
143140
public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
144141
RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
@@ -150,8 +147,8 @@ public void unlink(String path, Callback callback) {
150147
}
151148

152149
@ReactMethod
153-
public void mkdir(String path, Callback callback) {
154-
RNFetchBlobFS.mkdir(path, callback);
150+
public void mkdir(String path, Promise promise) {
151+
RNFetchBlobFS.mkdir(path, promise);
155152
}
156153

157154
@ReactMethod
@@ -176,8 +173,8 @@ public void mv(String path, String dest, Callback callback) {
176173
}
177174

178175
@ReactMethod
179-
public void ls(String path, Callback callback) {
180-
RNFetchBlobFS.ls(path, callback);
176+
public void ls(String path, Promise promise) {
177+
RNFetchBlobFS.ls(path, promise);
181178
}
182179

183180
@ReactMethod
@@ -355,10 +352,10 @@ public void getContentIntent(String mime, Promise promise) {
355352

356353
@ReactMethod
357354
public void addCompleteDownload (ReadableMap config, Promise promise) {
358-
DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
355+
DownloadManager dm = (DownloadManager) RCTContext.getSystemService(RCTContext.DOWNLOAD_SERVICE);
359356
String path = RNFetchBlobFS.normalizePath(config.getString("path"));
360357
if(path == null) {
361-
promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path);
358+
promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"));
362359
return;
363360
}
364361
try {
@@ -375,7 +372,7 @@ public void addCompleteDownload (ReadableMap config, Promise promise) {
375372
promise.resolve(null);
376373
}
377374
catch(Exception ex) {
378-
promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString());
375+
promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
379376
}
380377

381378
}

Diff for: android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java

+28-32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.RNFetchBlob;
22

3+
import android.support.annotation.NonNull;
34
import android.util.Base64;
45

56
import com.facebook.react.bridge.Arguments;
@@ -21,21 +22,20 @@
2122
import okhttp3.RequestBody;
2223
import okio.BufferedSink;
2324

24-
public class RNFetchBlobBody extends RequestBody{
25+
class RNFetchBlobBody extends RequestBody{
2526

26-
InputStream requestStream;
27-
long contentLength = 0;
28-
ReadableArray form;
29-
String mTaskId;
30-
String rawBody;
31-
RNFetchBlobReq.RequestType requestType;
32-
MediaType mime;
33-
File bodyCache;
27+
private InputStream requestStream;
28+
private long contentLength = 0;
29+
private ReadableArray form;
30+
private String mTaskId;
31+
private String rawBody;
32+
private RNFetchBlobReq.RequestType requestType;
33+
private MediaType mime;
34+
private File bodyCache;
3435
int reported = 0;
35-
Boolean chunkedEncoding = false;
36+
private Boolean chunkedEncoding = false;
3637

37-
38-
public RNFetchBlobBody(String taskId) {
38+
RNFetchBlobBody(String taskId) {
3939
this.mTaskId = taskId;
4040
}
4141

@@ -49,7 +49,7 @@ RNFetchBlobBody setMIME(MediaType mime) {
4949
return this;
5050
}
5151

52-
RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) {
52+
RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) {
5353
this.requestType = type;
5454
return this;
5555
}
@@ -114,7 +114,7 @@ public MediaType contentType() {
114114
}
115115

116116
@Override
117-
public void writeTo(BufferedSink sink) {
117+
public void writeTo(@NonNull BufferedSink sink) {
118118
try {
119119
pipeStreamToSink(requestStream, sink);
120120
} catch(Exception ex) {
@@ -186,8 +186,7 @@ private File createMultipartBodyCache() throws IOException {
186186
ArrayList<FormField> fields = countFormDataLength();
187187
ReactApplicationContext ctx = RNFetchBlob.RCTContext;
188188

189-
for(int i = 0;i < fields.size(); i++) {
190-
FormField field = fields.get(i);
189+
for(FormField field : fields) {
191190
String data = field.data;
192191
String name = field.name;
193192
// skip invalid fields
@@ -258,17 +257,14 @@ private File createMultipartBodyCache() throws IOException {
258257
* @param sink The request body buffer sink
259258
* @throws IOException
260259
*/
261-
private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws Exception {
262-
263-
byte [] chunk = new byte[10240];
260+
private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
261+
byte[] chunk = new byte[10240];
264262
int totalWritten = 0;
265263
int read;
266264
while((read = stream.read(chunk, 0, 10240)) > 0) {
267-
if(read > 0) {
268-
sink.write(chunk, 0, read);
269-
totalWritten += read;
270-
emitUploadProgress(totalWritten);
271-
}
265+
sink.write(chunk, 0, read);
266+
totalWritten += read;
267+
emitUploadProgress(totalWritten);
272268
}
273269
stream.close();
274270
}
@@ -291,7 +287,7 @@ private void pipeStreamToFileStream(InputStream is, FileOutputStream os) throws
291287

292288
/**
293289
* Compute approximate content length for form data
294-
* @return
290+
* @return ArrayList<FormField>
295291
*/
296292
private ArrayList<FormField> countFormDataLength() {
297293
long total = 0;
@@ -300,11 +296,11 @@ private ArrayList<FormField> countFormDataLength() {
300296
for(int i = 0;i < form.size(); i++) {
301297
FormField field = new FormField(form.getMap(i));
302298
list.add(field);
303-
String data = field.data;
304-
if(data == null) {
299+
if(field.data == null) {
305300
RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
306301
}
307302
else if (field.filename != null) {
303+
String data = field.data;
308304
// upload from storage
309305
if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
310306
String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
@@ -333,7 +329,7 @@ else if (field.filename != null) {
333329
}
334330
// data field
335331
else {
336-
total += field.data != null ? field.data.getBytes().length : 0;
332+
total += field.data.getBytes().length;
337333
}
338334
}
339335
contentLength = total;
@@ -346,11 +342,11 @@ else if (field.filename != null) {
346342
*/
347343
private class FormField {
348344
public String name;
349-
public String filename;
350-
public String mime;
345+
String filename;
346+
String mime;
351347
public String data;
352348

353-
public FormField(ReadableMap rawData) {
349+
FormField(ReadableMap rawData) {
354350
if(rawData.hasKey("name"))
355351
name = rawData.getString("name");
356352
if(rawData.hasKey("filename"))
@@ -368,7 +364,7 @@ public FormField(ReadableMap rawData) {
368364

369365
/**
370366
* Emit progress event
371-
* @param written
367+
* @param written Integer
372368
*/
373369
private void emitUploadProgress(int written) {
374370
RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);

Diff for: android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
import com.facebook.react.bridge.ReadableArray;
44
import com.facebook.react.bridge.ReadableMap;
55

6-
import java.util.HashMap;
7-
8-
9-
public class RNFetchBlobConfig {
6+
class RNFetchBlobConfig {
107

118
public Boolean fileCache;
129
public String path;

0 commit comments

Comments
 (0)