Skip to content

Commit fc7ae6c

Browse files
committed
Merge branch 'master' of https://github.com/joltup/rn-fetch-blob into hash-fix
# Conflicts: # android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
2 parents aca9058 + fb60952 commit fc7ae6c

17 files changed

+197
-94
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ rn-fetch-blob version 0.10.16 is only compatible with react native 0.60 and up.
2020
* [Installation](#user-content-installation)
2121
* [HTTP Data Transfer](#user-content-http-data-transfer)
2222
* [Regular Request](#user-content-regular-request)
23-
* [Download file](#user-content-download-example--fetch-files-that-needs-authorization-token)
23+
* [Download file](#download-example-fetch-files-that-need-authorization-token)
2424
* [Upload file](#user-content-upload-example--dropbox-files-upload-api)
2525
* [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
2626
* [Upload/Download progress](#user-content-uploaddownload-progress)
@@ -79,7 +79,7 @@ If automatically linking doesn't work for you, see instructions on [manually lin
7979
For 0.29.2+ projects, simply link native packages via the following command (note: rnpm has been merged into react-native)
8080

8181
```
82-
react-native link
82+
react-native link rn-fetch-blob
8383
```
8484

8585
As for projects < 0.29 you need `rnpm` to link native packages
@@ -91,7 +91,7 @@ rnpm link
9191
Optionally, use the following command to add Android permissions to `AndroidManifest.xml` automatically
9292

9393
```sh
94-
RNFB_ANDROID_PERMISSIONS=true react-native link
94+
RNFB_ANDROID_PERMISSIONS=true react-native link rn-fetch-blob
9595
```
9696

9797
pre 0.29 projects

android/src/main/java/com/RNFetchBlob/RNFetchBlob.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ public void actionViewIntent(String path, String mime, final Promise promise) {
118118

119119
// Set flag to give temporary permission to external app to use FileProvider
120120
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
121+
// All the activity to be opened outside of an activity
122+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
121123

124+
// All the activity to be opened outside of an activity
125+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
122126
// Validate that the device can open the file
123127
PackageManager pm = getCurrentActivity().getPackageManager();
124128
if (intent.resolveActivity(pm) != null) {
@@ -409,4 +413,4 @@ public void getSDCardDir(Promise promise) {
409413
public void getSDCardApplicationDir(Promise promise) {
410414
RNFetchBlobFS.getSDCardApplicationDir(this.getReactApplicationContext(), promise);
411415
}
412-
}
416+
}

android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java

+37-24
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
import com.facebook.react.modules.core.DeviceEventManagerModule;
2222

2323
import java.io.*;
24-
import java.nio.ByteBuffer;
2524
import java.nio.charset.Charset;
26-
import java.nio.charset.CharsetEncoder;
2725
import java.security.MessageDigest;
2826
import java.util.ArrayList;
2927
import java.util.HashMap;
@@ -69,32 +67,45 @@ static void writeFile(String path, String encoding, String data, final boolean a
6967
}
7068
}
7169

72-
FileOutputStream fout = new FileOutputStream(f, append);
7370
// write data from a file
7471
if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) {
7572
String normalizedData = normalizePath(data);
7673
File src = new File(normalizedData);
7774
if (!src.exists()) {
7875
promise.reject("ENOENT", "No such file '" + path + "' " + "('" + normalizedData + "')");
79-
fout.close();
8076
return;
8177
}
82-
FileInputStream fin = new FileInputStream(src);
8378
byte[] buffer = new byte [10240];
8479
int read;
8580
written = 0;
86-
while((read = fin.read(buffer)) > 0) {
87-
fout.write(buffer, 0, read);
88-
written += read;
81+
FileInputStream fin = null;
82+
FileOutputStream fout = null;
83+
try {
84+
fin = new FileInputStream(src);
85+
fout = new FileOutputStream(f, append);
86+
while ((read = fin.read(buffer)) > 0) {
87+
fout.write(buffer, 0, read);
88+
written += read;
89+
}
90+
} finally {
91+
if (fin != null) {
92+
fin.close();
93+
}
94+
if (fout != null) {
95+
fout.close();
96+
}
8997
}
90-
fin.close();
9198
}
9299
else {
93100
byte[] bytes = stringToBytes(data, encoding);
94-
fout.write(bytes);
95-
written = bytes.length;
101+
FileOutputStream fout = new FileOutputStream(f, append);
102+
try {
103+
fout.write(bytes);
104+
written = bytes.length;
105+
} finally {
106+
fout.close();
107+
}
96108
}
97-
fout.close();
98109
promise.resolve(written);
99110
} catch (FileNotFoundException e) {
100111
// According to https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html
@@ -129,12 +140,15 @@ static void writeFile(String path, ReadableArray data, final boolean append, fin
129140
}
130141

131142
FileOutputStream os = new FileOutputStream(f, append);
132-
byte[] bytes = new byte[data.size()];
133-
for(int i=0;i<data.size();i++) {
134-
bytes[i] = (byte) data.getInt(i);
143+
try {
144+
byte[] bytes = new byte[data.size()];
145+
for (int i = 0; i < data.size(); i++) {
146+
bytes[i] = (byte) data.getInt(i);
147+
}
148+
os.write(bytes);
149+
} finally {
150+
os.close();
135151
}
136-
os.write(bytes);
137-
os.close();
138152
promise.resolve(data.size());
139153
} catch (FileNotFoundException e) {
140154
// According to https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html
@@ -325,9 +339,7 @@ else if(resolved == null) {
325339
boolean error = false;
326340

327341
if (encoding.equalsIgnoreCase("utf8")) {
328-
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
329342
while ((cursor = fs.read(buffer)) != -1) {
330-
encoder.encode(ByteBuffer.wrap(buffer).asCharBuffer());
331343
String chunk = new String(buffer, 0, cursor);
332344
emitStreamEvent(streamId, "data", chunk);
333345
if(tick > 0)
@@ -523,7 +535,7 @@ private static void deleteRecursive(File fileOrDirectory) throws IOException {
523535
static void mkdir(String path, Promise promise) {
524536
File dest = new File(path);
525537
if(dest.exists()) {
526-
promise.reject("EEXIST", dest.isDirectory() ? "Folder" : "File" + " '" + path + "' already exists");
538+
promise.reject("EEXIST", (dest.isDirectory() ? "Folder" : "File") + " '" + path + "' already exists");
527539
return;
528540
}
529541
try {
@@ -876,12 +888,13 @@ static void hash(String path, String algorithm, Promise promise) {
876888
MessageDigest md = MessageDigest.getInstance(algorithms.get(algorithm));
877889

878890
FileInputStream inputStream = new FileInputStream(path);
879-
byte[] buffer = new byte[(int)file.length()];
891+
int chunkSize = 4096 * 256; // 1Mb
892+
byte[] buffer = new byte[chunkSize];
880893

881894
if(file.length() != 0) {
882-
int read;
883-
while ((read = inputStream.read(buffer)) != -1) {
884-
md.update(buffer, 0, read);
895+
int bytesRead;
896+
while ((bytesRead = inputStream.read(buffer)) != -1) {
897+
md.update(buffer, 0, bytesRead);
885898
}
886899
}
887900

android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java

+45-45
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@
3636
import java.net.SocketException;
3737
import java.net.SocketTimeoutException;
3838
import java.net.URL;
39-
import java.nio.ByteBuffer;
40-
import java.nio.charset.CharacterCodingException;
41-
import java.nio.charset.Charset;
42-
import java.nio.charset.CharsetEncoder;
4339
import java.security.KeyStore;
4440
import java.util.ArrayList;
4541
import java.util.Arrays;
@@ -502,44 +498,38 @@ private void done(Response resp) {
502498
// encoding will somehow break the UTF8 string format, to encode UTF8
503499
// string correctly, we should do URL encoding before BASE64.
504500
byte[] b = resp.body().bytes();
505-
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
506501
if(responseFormat == ResponseFormat.BASE64) {
507502
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP));
508503
return;
509504
}
510-
try {
511-
encoder.encode(ByteBuffer.wrap(b).asCharBuffer());
512-
// if the data contains invalid characters the following lines will be
513-
// skipped.
514-
String utf8 = new String(b);
515-
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, utf8);
516-
}
517-
// This usually mean the data is contains invalid unicode characters, it's
518-
// binary data
519-
catch(CharacterCodingException ignored) {
520-
if(responseFormat == ResponseFormat.UTF8) {
521-
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, "");
522-
}
523-
else {
524-
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP));
525-
}
526-
}
505+
String utf8 = new String(b);
506+
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, utf8);
527507
}
528508
} catch (IOException e) {
529509
callback.invoke("RNFetchBlob failed to encode response data to BASE64 string.", null);
530510
}
531511
break;
532512
case FileStorage:
513+
ResponseBody responseBody = resp.body();
514+
533515
try {
534516
// In order to write response data to `destPath` we have to invoke this method.
535517
// It uses customized response body which is able to report download progress
536518
// and write response data to destination path.
537-
resp.body().bytes();
519+
responseBody.bytes();
538520
} catch (Exception ignored) {
539521
// ignored.printStackTrace();
540522
}
541-
this.destPath = this.destPath.replace("?append=true", "");
542-
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
523+
524+
RNFetchBlobFileResp rnFetchBlobFileResp = (RNFetchBlobFileResp) responseBody;
525+
526+
if(rnFetchBlobFileResp != null && rnFetchBlobFileResp.isDownloadComplete() == false){
527+
callback.invoke("RNFetchBlob failed. Download interrupted.", null);
528+
}
529+
else {
530+
this.destPath = this.destPath.replace("?append=true", "");
531+
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
532+
}
543533
break;
544534
default:
545535
try {
@@ -666,30 +656,40 @@ public void onReceive(Context context, Intent intent) {
666656
DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
667657
dm.query(query);
668658
Cursor c = dm.query(query);
669-
659+
// #236 unhandled null check for DownloadManager.query() return value
660+
if (c == null) {
661+
this.callback.invoke("Download manager failed to download from " + this.url + ". Query was unsuccessful ", null, null);
662+
return;
663+
}
670664

671665
String filePath = null;
672-
// the file exists in media content database
673-
if (c.moveToFirst()) {
674-
// #297 handle failed request
675-
int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
676-
if(statusCode == DownloadManager.STATUS_FAILED) {
677-
this.callback.invoke("Download manager failed to download from " + this.url + ". Status Code = " + statusCode, null, null);
678-
return;
679-
}
680-
String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
681-
if ( contentUri != null &&
682-
options.addAndroidDownloads.hasKey("mime") &&
683-
options.addAndroidDownloads.getString("mime").contains("image")) {
684-
Uri uri = Uri.parse(contentUri);
685-
Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
686-
// use default destination of DownloadManager
687-
if (cursor != null) {
688-
cursor.moveToFirst();
689-
filePath = cursor.getString(0);
690-
cursor.close();
666+
try {
667+
// the file exists in media content database
668+
if (c.moveToFirst()) {
669+
// #297 handle failed request
670+
int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
671+
if(statusCode == DownloadManager.STATUS_FAILED) {
672+
this.callback.invoke("Download manager failed to download from " + this.url + ". Status Code = " + statusCode, null, null);
673+
return;
674+
}
675+
String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
676+
if ( contentUri != null &&
677+
options.addAndroidDownloads.hasKey("mime") &&
678+
options.addAndroidDownloads.getString("mime").contains("image")) {
679+
Uri uri = Uri.parse(contentUri);
680+
Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
681+
// use default destination of DownloadManager
682+
if (cursor != null) {
683+
cursor.moveToFirst();
684+
filePath = cursor.getString(0);
685+
cursor.close();
686+
}
691687
}
692688
}
689+
} finally {
690+
if (c != null) {
691+
c.close();
692+
}
693693
}
694694

695695
// When the file is not found in media content database, check if custom path exists

android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ public long contentLength() {
6868
return originalBody.contentLength();
6969
}
7070

71+
public boolean isDownloadComplete() {
72+
return bytesDownloaded == contentLength();
73+
}
74+
7175
@Override
7276
public BufferedSource source() {
7377
ProgressReportingSource countable = new ProgressReportingSource();

android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,25 @@ public static String getRealPathFromURI(final Context context, final Uri uri) {
3737
}
3838
// DownloadsProvider
3939
else if (isDownloadsDocument(uri)) {
40+
try {
41+
final String id = DocumentsContract.getDocumentId(uri);
42+
//Starting with Android O, this "id" is not necessarily a long (row number),
43+
//but might also be a "raw:/some/file/path" URL
44+
if (id != null && id.startsWith("raw:/")) {
45+
Uri rawuri = Uri.parse(id);
46+
String path = rawuri.getPath();
47+
return path;
48+
}
49+
final Uri contentUri = ContentUris.withAppendedId(
50+
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
4051

41-
final String id = DocumentsContract.getDocumentId(uri);
42-
final Uri contentUri = ContentUris.withAppendedId(
43-
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
44-
45-
return getDataColumn(context, contentUri, null, null);
52+
return getDataColumn(context, contentUri, null, null);
53+
}
54+
catch (Exception ex) {
55+
//something went wrong, but android should still be able to handle the original uri by returning null here (see readFile(...))
56+
return null;
57+
}
58+
4659
}
4760
// MediaProvider
4861
else if (isMediaDocument(uri)) {

android/src/main/res/xml/provider_paths.xml

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
<files-path
77
name="files-path"
88
path="." /> <!-- Used to access into application data files -->
9-
</paths>
9+
<cache-path
10+
name="cache-path"
11+
path="." /> <!-- Used to access files in cache directory -->
12+
</paths>

fs.js

+14
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ function pathForAppGroup(groupName: string): Promise {
135135
return RNFetchBlob.pathForAppGroup(groupName)
136136
}
137137

138+
/**
139+
* Returns the path for the app group synchronous.
140+
* @param {string} groupName Name of app group
141+
* @return {string} Path of App Group dir
142+
*/
143+
function syncPathAppGroup(groupName: string): string {
144+
if (Platform.OS === 'ios') {
145+
return RNFetchBlob.syncPathAppGroup(groupName);
146+
} else {
147+
return '';
148+
}
149+
}
150+
138151
/**
139152
* Wrapper method of readStream.
140153
* @param {string} path Path of the file.
@@ -402,6 +415,7 @@ export default {
402415
writeFile,
403416
appendFile,
404417
pathForAppGroup,
418+
syncPathAppGroup,
405419
readFile,
406420
hash,
407421
exists,

0 commit comments

Comments
 (0)