Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content URI support #395

Merged
merged 1 commit into from
Dec 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions android/src/main/java/com/rnfs/IORejectionException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.rnfs;

class IORejectionException extends Exception {
private String code;

public IORejectionException(String code, String message) {
super(message);
this.code = code;
}

public String getCode() {
return code;
}
}
177 changes: 106 additions & 71 deletions android/src/main/java/com/rnfs/RNFSManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Environment;
import android.os.StatFs;
import android.support.annotation.Nullable;
Expand All @@ -19,10 +20,10 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand All @@ -46,22 +47,85 @@ public class RNFSManager extends ReactContextBaseJavaModule {
private static final String RNFSFileTypeDirectory = "RNFSFileTypeDirectory";

private SparseArray<Downloader> downloaders = new SparseArray<Downloader>();
private ReactApplicationContext reactContext;

public RNFSManager(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}

@Override
public String getName() {
return "RNFSManager";
}

private Uri getFileUri(String filepath) throws IORejectionException {
Uri uri = Uri.parse(filepath);
if (uri.getScheme() == null) {
// No prefix, assuming that provided path is absolute path to file
File file = new File(filepath);
if (file.isDirectory()) {
throw new IORejectionException("EISDIR", "EISDIR: illegal operation on a directory, read '" + filepath + "'");
}
uri = Uri.parse("file://" + filepath);
}
return uri;
}

private InputStream getInputStream(String filepath) throws IORejectionException {
Uri uri = getFileUri(filepath);
InputStream stream;
try {
stream = reactContext.getContentResolver().openInputStream(uri);
} catch (FileNotFoundException ex) {
throw new IORejectionException("ENOENT", "ENOENT: no such file or directory, open '" + filepath + "'");
}
if (stream == null) {
throw new IORejectionException("ENOENT", "ENOENT: could not open an input stream for '" + filepath + "'");
}
return stream;
}

private OutputStream getOutputStream(String filepath, boolean append) throws IORejectionException {
Uri uri = getFileUri(filepath);
OutputStream stream;
try {
stream = reactContext.getContentResolver().openOutputStream(uri, append ? "wa" : "w");
} catch (FileNotFoundException ex) {
throw new IORejectionException("ENOENT", "ENOENT: no such file or directory, open '" + filepath + "'");
}
if (stream == null) {
throw new IORejectionException("ENOENT", "ENOENT: could not open an output stream for '" + filepath + "'");
}
return stream;
}

private static byte[] getInputStreamBytes(InputStream inputStream) throws IOException {
byte[] bytesResult;
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
try {
int len;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
bytesResult = byteBuffer.toByteArray();
} finally {
try {
byteBuffer.close();
} catch (IOException ignored) {
}
}
return bytesResult;
}

@ReactMethod
public void writeFile(String filepath, String base64Content, Promise promise) {
try {
byte[] bytes = Base64.decode(base64Content, Base64.DEFAULT);

FileOutputStream outputStream = new FileOutputStream(filepath, false);
OutputStream outputStream = getOutputStream(filepath, false);
outputStream.write(bytes);
outputStream.close();

Expand All @@ -77,7 +141,7 @@ public void appendFile(String filepath, String base64Content, Promise promise) {
try {
byte[] bytes = Base64.decode(base64Content, Base64.DEFAULT);

FileOutputStream outputStream = new FileOutputStream(filepath, true);
OutputStream outputStream = getOutputStream(filepath, true);
outputStream.write(bytes);
outputStream.close();

Expand All @@ -94,7 +158,7 @@ public void write(String filepath, String base64Content, int position, Promise p
byte[] bytes = Base64.decode(base64Content, Base64.DEFAULT);

if (position < 0) {
FileOutputStream outputStream = new FileOutputStream(filepath, true);
OutputStream outputStream = getOutputStream(filepath, true);
outputStream.write(bytes);
outputStream.close();
} else {
Expand Down Expand Up @@ -125,23 +189,9 @@ public void exists(String filepath, Promise promise) {
@ReactMethod
public void readFile(String filepath, Promise promise) {
try {
File file = new File(filepath);

if (file.isDirectory()) {
rejectFileIsDirectory(promise);
return;
}

if (!file.exists()) {
rejectFileNotFound(promise, filepath);
return;
}

FileInputStream inputStream = new FileInputStream(filepath);
byte[] buffer = new byte[(int)file.length()];
inputStream.read(buffer);

String base64Content = Base64.encodeToString(buffer, Base64.NO_WRAP);
InputStream inputStream = getInputStream(filepath);
byte[] inputData = getInputStreamBytes(inputStream);
String base64Content = Base64.encodeToString(inputData, Base64.NO_WRAP);

promise.resolve(base64Content);
} catch (Exception ex) {
Expand All @@ -151,31 +201,19 @@ public void readFile(String filepath, Promise promise) {
}

@ReactMethod
public void read(String filepath, int length, int position, Promise promise){
public void read(String filepath, int length, int position, Promise promise) {
try {
File file = new File(filepath);

if (file.isDirectory()) {
rejectFileIsDirectory(promise);
return;
}

if (!file.exists()) {
rejectFileNotFound(promise, filepath);
return;
}

FileInputStream inputStream = new FileInputStream(filepath);
InputStream inputStream = getInputStream(filepath);
byte[] buffer = new byte[length];
inputStream.skip(position);
inputStream.read(buffer,0,length);
inputStream.read(buffer, 0, length);

String base64Content = Base64.encodeToString(buffer, Base64.NO_WRAP);

promise.resolve(base64Content);
} catch (Exception ex) {
ex.printStackTrace();
reject(promise, filepath, ex);
ex.printStackTrace();
reject(promise, filepath, ex);
}
}

Expand All @@ -194,7 +232,7 @@ public void readFileAssets(String filepath, Promise promise) {
byte[] buffer = new byte[stream.available()];
stream.read(buffer);
String base64Content = Base64.encodeToString(buffer, Base64.NO_WRAP);
promise.resolve(base64Content);;
promise.resolve(base64Content);
} catch (Exception ex) {
ex.printStackTrace();
reject(promise, filepath, ex);
Expand Down Expand Up @@ -237,7 +275,7 @@ public void hash(String filepath, String algorithm, Promise promise) {
MessageDigest md = MessageDigest.getInstance(algorithms.get(algorithm));

FileInputStream inputStream = new FileInputStream(filepath);
byte[] buffer = new byte[(int)file.length()];
byte[] buffer = new byte[(int) file.length()];

int read;
while ((read = inputStream.read(buffer)) != -1) {
Expand Down Expand Up @@ -285,9 +323,9 @@ public void copyFile(String filepath, String destPath, Promise promise) {
}
}

private void copyFile(String filepath, String destPath) throws IOException {
InputStream in = new FileInputStream(filepath);
OutputStream out = new FileOutputStream(destPath);
private void copyFile(String filepath, String destPath) throws IOException, IORejectionException {
InputStream in = getInputStream(filepath);
OutputStream out = getOutputStream(destPath, false);

byte[] buffer = new byte[1024];
int length;
Expand All @@ -312,10 +350,10 @@ public void readDir(String directory, Promise promise) {
for (File childFile : files) {
WritableMap fileMap = Arguments.createMap();

fileMap.putDouble("mtime", (double)childFile.lastModified()/1000);
fileMap.putDouble("mtime", (double) childFile.lastModified() / 1000);
fileMap.putString("name", childFile.getName());
fileMap.putString("path", childFile.getAbsolutePath());
fileMap.putInt("size", (int)childFile.length());
fileMap.putInt("size", (int) childFile.length());
fileMap.putInt("type", childFile.isDirectory() ? 1 : 0);

fileMaps.pushMap(fileMap);
Expand Down Expand Up @@ -416,35 +454,27 @@ public void existsAssets(String filepath, Promise promise) {

/**
* Internal method for copying that works with any InputStream
* @param in InputStream from assets or file
* @param source source path (only used for logging errors)
*
* @param in InputStream from assets or file
* @param source source path (only used for logging errors)
* @param destination destination path
* @param promise React Callback
* @param promise React Callback
*/
private void copyInputStream(InputStream in, String source, String destination, Promise promise) {
OutputStream out = null;
try {
File outFile = new File(destination);
try {
out = new FileOutputStream(outFile);
} catch (FileNotFoundException e) {
reject(promise, source, e);
return;
}
out = getOutputStream(destination, false);

try {
byte[] buffer = new byte[1024 * 10]; // 10k buffer
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} catch (IOException e) {
reject(promise, source, new Exception(String.format("Failed to copy '%s' to %s (%s)", source, destination, e.getLocalizedMessage())));
return;
byte[] buffer = new byte[1024 * 10]; // 10k buffer
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}

// Success!
promise.resolve(null);
} catch (Exception ex) {
reject(promise, source, new Exception(String.format("Failed to copy '%s' to %s (%s)", source, destination, ex.getLocalizedMessage())));
} finally {
if (in != null) {
try {
Expand Down Expand Up @@ -486,9 +516,9 @@ public void stat(String filepath, Promise promise) {

WritableMap statMap = Arguments.createMap();

statMap.putInt("ctime", (int)(file.lastModified() / 1000));
statMap.putInt("mtime", (int)(file.lastModified() / 1000));
statMap.putInt("size", (int)file.length());
statMap.putInt("ctime", (int) (file.lastModified() / 1000));
statMap.putInt("mtime", (int) (file.lastModified() / 1000));
statMap.putInt("size", (int) file.length());
statMap.putInt("type", file.isDirectory() ? 1 : 0);

promise.resolve(statMap);
Expand Down Expand Up @@ -544,8 +574,8 @@ public void mkdir(String filepath, ReadableMap options, Promise promise) {

private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(RCTNativeAppEventEmitter.class)
.emit(eventName, params);
.getJSModule(RCTNativeAppEventEmitter.class)
.emit(eventName, params);
}

@ReactMethod
Expand Down Expand Up @@ -660,8 +690,8 @@ public void getFSInfo(Promise promise) {
freeSpace = blockSize * stat.getAvailableBlocks();
}
WritableMap info = Arguments.createMap();
info.putDouble("totalSpace", (double)totalSpace); // Int32 too small, must use Double
info.putDouble("freeSpace", (double)freeSpace);
info.putDouble("totalSpace", (double) totalSpace); // Int32 too small, must use Double
info.putDouble("freeSpace", (double) freeSpace);
promise.resolve(info);
}

Expand All @@ -681,6 +711,11 @@ private void reject(Promise promise, String filepath, Exception ex) {
rejectFileNotFound(promise, filepath);
return;
}
if (ex instanceof IORejectionException) {
IORejectionException ioRejectionException = (IORejectionException) ex;
promise.reject(ioRejectionException.getCode(), ioRejectionException.getMessage());
return;
}

promise.reject(null, ex.getMessage());
}
Expand Down