package com.RNFetchBlob; import android.app.LoaderManager; import android.content.ContentResolver; import android.content.CursorLoader; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.os.Looper; import android.provider.MediaStore; import android.util.Base64; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.modules.core.DeviceEventManagerModule; 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; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Created by wkh237 on 2016/5/26. */ public class RNFetchBlobFS { ReactApplicationContext mCtx; DeviceEventManagerModule.RCTDeviceEventEmitter emitter; String encoding = "base64"; boolean append = false; OutputStream writeStreamInstance = null; static HashMap fileStreams = new HashMap<>(); RNFetchBlobFS(ReactApplicationContext ctx) { this.mCtx = ctx; this.emitter = ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); } static String getExternalFilePath(ReactApplicationContext ctx, String taskId, RNFetchBlobConfig config) { if(config.path != null) return config.path; else if(config.fileCache && config.appendExt != null) return RNFetchBlobFS.getTmpPath(ctx, taskId) + "." + config.appendExt; else return RNFetchBlobFS.getTmpPath(ctx, taskId); } /** * Write string with encoding to file * @param path Destination file path. * @param encoding Encoding of the string. * @param data Array passed from JS context. * @param promise */ static public void writeFile(String path, String encoding, String data, final boolean append, final Promise promise) { try { int written = 0; File f = new File(path); File dir = f.getParentFile(); if(!dir.exists()) dir.mkdirs(); FileOutputStream fout = new FileOutputStream(f, append); // write data from a file if(encoding.equalsIgnoreCase(RNFetchBlobConst.DATA_ENCODE_URI)) { File src = new File(data); if(!src.exists()) { promise.reject("RNfetchBlob writeFileError", "source file : " + data + "not exists"); fout.close(); return ; } FileInputStream fin = new FileInputStream(src); byte [] buffer = new byte [10240]; int read; written = 0; while((read = fin.read(buffer)) > 0) { fout.write(buffer, 0, read); written += read; } fin.close(); } else { byte[] bytes = stringToBytes(data, encoding); fout.write(bytes); written = bytes.length; } fout.close(); promise.resolve(written); } catch (Exception e) { promise.reject("RNFetchBlob writeFileError", e.getLocalizedMessage()); } } /** * Write array of bytes into file * @param path Destination file path. * @param data Array passed from JS context. * @param 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(); FileOutputStream os = new FileOutputStream(f, append); byte [] bytes = new byte[data.size()]; for(int i=0;i getSystemfolders(ReactApplicationContext ctx) { Map res = new HashMap<>(); res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath()); res.put("CacheDir", ctx.getCacheDir().getAbsolutePath()); res.put("DCIMDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()); res.put("PictureDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); res.put("MusicDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()); res.put("DownloadDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); res.put("MovieDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath()); res.put("RingtoneDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getAbsolutePath()); return res; } /** * Static method that returns a temp file path * @param ctx React Native application context * @param taskId An unique string for identify * @return */ static public String getTmpPath(ReactApplicationContext ctx, String taskId) { return RNFetchBlob.RCTContext.getFilesDir() + "/RNFetchBlobTmp_" + taskId; } /** * Create a file stream for read * @param path File stream target path * @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) { path = normalizePath(path); AsyncTask task = new AsyncTask() { @Override protected Integer doInBackground(String ... args) { String path = args[0]; String encoding = args[1]; int bufferSize = Integer.parseInt(args[2]); String eventName = "RNFetchBlobStream+" + path; try { int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096; if(bufferSize > 0) chunkSize = bufferSize; InputStream fs; if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); } else { fs = new FileInputStream(new File(path)); } byte[] buffer = new byte[chunkSize]; int cursor = 0; boolean error = false; if (encoding.equalsIgnoreCase("utf8")) { while ((cursor = fs.read(buffer)) != -1) { String chunk = new String(buffer, 0, cursor, "UTF-8"); emitStreamEvent(eventName, "data", chunk); } } else if (encoding.equalsIgnoreCase("ascii")) { while ((cursor = fs.read(buffer)) != -1) { WritableArray chunk = Arguments.createArray(); for(int i =0;i 0) { out.write(buf, 0, len); } } catch (Exception err) { if(err != null) callback.invoke(err.getLocalizedMessage()); } finally { try { in.close(); out.close(); callback.invoke(); } catch (IOException e) { callback.invoke(e.getLocalizedMessage()); } } } /** * Move file * @param path Source file path * @param dest Destination file path * @param callback JS context callback */ static void mv(String path, String dest, Callback callback) { File src = new File(path); if(!src.exists()) { callback.invoke("mv error: source file at path `" + path + "` does not exists"); return; } src.renameTo(new File(dest)); callback.invoke(); } /** * Check if the path exists, also check if it is a folder when exists. * @param path Path to check * @param callback JS context callback */ static void exists(String path, Callback callback) { path = normalizePath(path); if(isAsset(path)) { try { String filename = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(filename); callback.invoke(true, false); } catch (IOException e) { callback.invoke(false, false); } } else { boolean exist = new File(path).exists(); boolean isDir = new File(path).isDirectory(); callback.invoke(exist, isDir); } } /** * List content of folder * @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("ls error: failed to list path `" + path + "` for it is not exist or it is not a folder"); return; } String[] files = new File(path).list(); WritableArray arg = Arguments.createArray(); for (String i : files) { arg.pushString(i); } callback.invoke(null, arg); } /** * Create a file by slicing given file path * @param src Source file path * @param dest Destination of created file * @param start Start byte offset in source file * @param end End byte offset * @param encode * @param callback */ public static void slice(String src, String dest, int start, int end, String encode, Callback callback) { try { long expected = end - start; 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]; while(now < expected) { long read = in.read(buffer, 0, 10240); if(read <= 0) { break; } now += read; out.write(buffer, 0, (int) read); } in.close(); out.close(); callback.invoke(null, dest); } catch (Exception e) { e.printStackTrace(); } } static void lstat(String path, final Callback callback) { path = normalizePath(path); new AsyncTask() { @Override protected Integer doInBackground(String ...args) { WritableArray res = Arguments.createArray(); File src = new File(args[0]); if(!src.exists()) { callback.invoke("lstat error: failed to list path `" + args[0] + "` for it is not exist or it is not a folder"); return 0; } if(src.isDirectory()) { String [] files = src.list(); for(String p : files) { res.pushMap(statFile ( src.getPath() + "/" + p)); } } else { res.pushMap(statFile(src.getAbsolutePath())); } callback.invoke(null, res); return 0; } }.execute(path); } /** * show status of a file or directory * @param path * @param callback */ static void stat(String path, Callback callback) { try { WritableMap result = statFile(path); if(result == null) callback.invoke("stat error: failed to list path `" + path + "` for it is not exist or it is not a folder", null); else callback.invoke(null, result); } catch(Exception err) { callback.invoke(err.getLocalizedMessage()); } } /** * Basic stat method * @param path * @return Stat result of a file or path */ static WritableMap statFile(String path) { try { path = normalizePath(path); WritableMap stat = Arguments.createMap(); if(isAsset(path)) { String name = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); AssetFileDescriptor fd = RNFetchBlob.RCTContext.getAssets().openFd(name); stat.putString("filename", name); stat.putString("path", path); stat.putString("type", "asset"); stat.putString("size", String.valueOf(fd.getLength())); stat.putString("lastModified", "0"); } else { File target = new File(path); if (!target.exists()) { return null; } stat.putString("filename", target.getName()); stat.putString("path", target.getPath()); stat.putString("type", target.isDirectory() ? "directory" : "file"); stat.putString("size", String.valueOf(target.length())); String lastModified = String.valueOf(target.lastModified()); stat.putString("lastModified", lastModified); } return stat; } catch(Exception err) { return null; } } /** * Media scanner scan file * @param path * @param mimes * @param callback */ void scanFile(String [] path, String[] mimes, final Callback callback) { try { MediaScannerConnection.scanFile(mCtx, path, mimes, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String s, Uri uri) { callback.invoke(null, true); } }); } catch(Exception err) { callback.invoke(err.getLocalizedMessage(), null); } } /** * Create new file at path * @param path * @param data * @param encoding * @param callback */ static void createFile(String path, String data, String encoding, Callback callback) { try { File dest = new File(path); boolean created = dest.createNewFile(); if(encoding.equals(RNFetchBlobConst.DATA_ENCODE_URI)) { String orgPath = data.replace(RNFetchBlobConst.FILE_PREFIX, ""); File src = new File(orgPath); if(!src.exists()) { callback.invoke("RNfetchBlob writeFileError", "source file : " + data + "not exists"); return ; } FileInputStream fin = new FileInputStream(src); OutputStream ostream = new FileOutputStream(dest); byte [] buffer = new byte [10240]; int read = fin.read(buffer); while(read > 0) { ostream.write(buffer, 0, read); read = fin.read(buffer); } fin.close(); ostream.close(); } else { if (!created) { callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists"); return; } OutputStream ostream = new FileOutputStream(dest); ostream.write(RNFetchBlobFS.stringToBytes(data, encoding)); } callback.invoke(null, path); } catch(Exception err) { callback.invoke(err.getLocalizedMessage()); } } /** * Create file for ASCII encoding * @param path Path of new file. * @param data Content of new file * @param callback JS context callback */ static void createFileASCII(String path, ReadableArray data, Callback callback) { try { File dest = new File(path); if(dest.exists()) { callback.invoke("create file error: failed to create file at path `" + path + "`, file already exists."); return; } boolean created = dest.createNewFile(); if(!created) { callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not 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 { for (int i = 0; i < paths[0].size(); i++) { File f = new File(paths[0].getString(i)); if (f.exists()) f.delete(); } callback.invoke(null, true); } catch(Exception err) { callback.invoke(err.getLocalizedMessage()); } return paths[0].size(); } }; task.execute(paths); } /** * String to byte converter method * @param data Raw data in string format * @param encoding Decoder name * @return Converted data byte array */ private static byte[] stringToBytes(String data, String encoding) { if(encoding.equalsIgnoreCase("ascii")) { return data.getBytes(Charset.forName("US-ASCII")); } else if(encoding.toLowerCase().contains("base64")) { byte [] b = Base64.decode(data, Base64.NO_WRAP); return b; } else if(encoding.equalsIgnoreCase("utf8")) { return data.getBytes(Charset.forName("UTF-8")); } return data.getBytes(Charset.forName("US-ASCII")); } /** * Private method for emit read stream event. * @param streamName ID of the read stream * @param event Event name, `data`, `end`, `error`, etc. * @param data Event data */ void emitStreamEvent(String streamName, String event, String data) { WritableMap eventData = Arguments.createMap(); eventData.putString("event", event); eventData.putString("detail", data); this.emitter.emit(streamName, eventData); } void emitStreamEvent(String streamName, String event, WritableArray data) { WritableMap eventData = Arguments.createMap(); eventData.putString("event", event); eventData.putArray("detail", data); this.emitter.emit(streamName, eventData); } // TODO : should we remove this ? void emitFSData(String taskId, String event, String data) { WritableMap eventData = Arguments.createMap(); eventData.putString("event", event); eventData.putString("detail", data); this.emitter.emit("RNFetchBlobStream" + taskId, eventData); } /** * Get input stream of the given path, when the path is a string starts with bundle-assets:// * the stream is created by Assets Manager, otherwise use FileInputStream. * @param path * @return * @throws IOException */ 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, "")); } return new FileInputStream(new File(path)); } /** * Check if the asset or the file exists * @param path * @return */ 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, "")); } catch (IOException e) { return false; } return true; } else { return new File(path).exists(); } } public static boolean isAsset(String path) { return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET); } public static String normalizePath(String path) { if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { return path; } else if (path.startsWith(RNFetchBlobConst.FILE_PREFIX_CONTENT)) { String filePath = null; Uri uri = Uri.parse(path); if (uri != null && "content".equals(uri.getScheme())) { ContentResolver resolver = RNFetchBlob.RCTContext.getContentResolver(); Cursor cursor = resolver.query(uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null); cursor.moveToFirst(); filePath = cursor.getString(0); cursor.close(); } else { filePath = uri.getPath(); } return filePath; } return path; } }