|
@@ -0,0 +1,1196 @@
|
|
|
|
+/*
|
|
|
|
+ Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
+ or more contributor license agreements. See the NOTICE file
|
|
|
|
+ distributed with this work for additional information
|
|
|
|
+ regarding copyright ownership. The ASF licenses this file
|
|
|
|
+ to you under the Apache License, Version 2.0 (the
|
|
|
|
+ "License"); you may not use this file except in compliance
|
|
|
|
+ with the License. You may obtain a copy of the License at
|
|
|
|
+
|
|
|
|
+ http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
+
|
|
|
|
+ Unless required by applicable law or agreed to in writing,
|
|
|
|
+ software distributed under the License is distributed on an
|
|
|
|
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
+ KIND, either express or implied. See the License for the
|
|
|
|
+ specific language governing permissions and limitations
|
|
|
|
+ under the License.
|
|
|
|
+ */
|
|
|
|
+package org.apache.cordova.file;
|
|
|
|
+
|
|
|
|
+import android.Manifest;
|
|
|
|
+import android.app.Activity;
|
|
|
|
+import android.content.Context;
|
|
|
|
+import android.content.pm.PackageManager;
|
|
|
|
+import android.net.Uri;
|
|
|
|
+import android.os.Build;
|
|
|
|
+import android.os.Environment;
|
|
|
|
+import android.util.Base64;
|
|
|
|
+import android.util.Log;
|
|
|
|
+
|
|
|
|
+import org.apache.cordova.CallbackContext;
|
|
|
|
+import org.apache.cordova.CordovaInterface;
|
|
|
|
+import org.apache.cordova.CordovaPlugin;
|
|
|
|
+import org.apache.cordova.CordovaWebView;
|
|
|
|
+import org.apache.cordova.PermissionHelper;
|
|
|
|
+import org.apache.cordova.PluginResult;
|
|
|
|
+
|
|
|
|
+import org.json.JSONArray;
|
|
|
|
+import org.json.JSONException;
|
|
|
|
+import org.json.JSONObject;
|
|
|
|
+
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
+import java.io.File;
|
|
|
|
+import java.io.FileNotFoundException;
|
|
|
|
+import java.io.IOException;
|
|
|
|
+import java.io.InputStream;
|
|
|
|
+import java.net.MalformedURLException;
|
|
|
|
+import java.security.Permission;
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
+import java.util.HashMap;
|
|
|
|
+import java.util.HashSet;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * This class provides file and directory services to JavaScript.
|
|
|
|
+ */
|
|
|
|
+public class FileUtils extends CordovaPlugin {
|
|
|
|
+ private static final String LOG_TAG = "FileUtils";
|
|
|
|
+
|
|
|
|
+ public static int NOT_FOUND_ERR = 1;
|
|
|
|
+ public static int SECURITY_ERR = 2;
|
|
|
|
+ public static int ABORT_ERR = 3;
|
|
|
|
+
|
|
|
|
+ public static int NOT_READABLE_ERR = 4;
|
|
|
|
+ public static int ENCODING_ERR = 5;
|
|
|
|
+ public static int NO_MODIFICATION_ALLOWED_ERR = 6;
|
|
|
|
+ public static int INVALID_STATE_ERR = 7;
|
|
|
|
+ public static int SYNTAX_ERR = 8;
|
|
|
|
+ public static int INVALID_MODIFICATION_ERR = 9;
|
|
|
|
+ public static int QUOTA_EXCEEDED_ERR = 10;
|
|
|
|
+ public static int TYPE_MISMATCH_ERR = 11;
|
|
|
|
+ public static int PATH_EXISTS_ERR = 12;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Permission callback codes
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ public static final int ACTION_GET_FILE = 0;
|
|
|
|
+ public static final int ACTION_WRITE = 1;
|
|
|
|
+ public static final int ACTION_GET_DIRECTORY = 2;
|
|
|
|
+
|
|
|
|
+ public static final int WRITE = 3;
|
|
|
|
+ public static final int READ = 4;
|
|
|
|
+
|
|
|
|
+ public static int UNKNOWN_ERR = 1000;
|
|
|
|
+
|
|
|
|
+ private boolean configured = false;
|
|
|
|
+
|
|
|
|
+ private PendingRequests pendingRequests;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * We need both read and write when accessing the storage, I think.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ private String [] permissions = {
|
|
|
|
+ Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
|
|
+ Manifest.permission.WRITE_EXTERNAL_STORAGE };
|
|
|
|
+
|
|
|
|
+ // This field exists only to support getEntry, below, which has been deprecated
|
|
|
|
+ private static FileUtils filePlugin;
|
|
|
|
+
|
|
|
|
+ private interface FileOp {
|
|
|
|
+ void run(JSONArray args) throws Exception;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private ArrayList<Filesystem> filesystems;
|
|
|
|
+
|
|
|
|
+ public void registerFilesystem(Filesystem fs) {
|
|
|
|
+ if (fs != null && filesystemForName(fs.name)== null) {
|
|
|
|
+ this.filesystems.add(fs);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Filesystem filesystemForName(String name) {
|
|
|
|
+ for (Filesystem fs:filesystems) {
|
|
|
|
+ if (fs != null && fs.name != null && fs.name.equals(name)) {
|
|
|
|
+ return fs;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected String[] getExtraFileSystemsPreference(Activity activity) {
|
|
|
|
+ String fileSystemsStr = preferences.getString("androidextrafilesystems", "files,files-external,documents,sdcard,cache,cache-external,root");
|
|
|
|
+ return fileSystemsStr.split(",");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected void registerExtraFileSystems(String[] filesystems, HashMap<String, String> availableFileSystems) {
|
|
|
|
+ HashSet<String> installedFileSystems = new HashSet<String>();
|
|
|
|
+
|
|
|
|
+ /* Register filesystems in order */
|
|
|
|
+ for (String fsName : filesystems) {
|
|
|
|
+ if (!installedFileSystems.contains(fsName)) {
|
|
|
|
+ String fsRoot = availableFileSystems.get(fsName);
|
|
|
|
+ if (fsRoot != null) {
|
|
|
|
+ File newRoot = new File(fsRoot);
|
|
|
|
+ if (newRoot.mkdirs() || newRoot.isDirectory()) {
|
|
|
|
+ registerFilesystem(new LocalFilesystem(fsName, webView.getContext(), webView.getResourceApi(), newRoot));
|
|
|
|
+ installedFileSystems.add(fsName);
|
|
|
|
+ } else {
|
|
|
|
+ Log.d(LOG_TAG, "Unable to create root dir for filesystem \"" + fsName + "\", skipping");
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ Log.d(LOG_TAG, "Unrecognized extra filesystem identifier: " + fsName);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected HashMap<String, String> getAvailableFileSystems(Activity activity) {
|
|
|
|
+ Context context = activity.getApplicationContext();
|
|
|
|
+ HashMap<String, String> availableFileSystems = new HashMap<String,String>();
|
|
|
|
+
|
|
|
|
+ availableFileSystems.put("files", context.getFilesDir().getAbsolutePath());
|
|
|
|
+ availableFileSystems.put("documents", new File(context.getFilesDir(), "Documents").getAbsolutePath());
|
|
|
|
+ availableFileSystems.put("cache", context.getCacheDir().getAbsolutePath());
|
|
|
|
+ availableFileSystems.put("root", "/");
|
|
|
|
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
|
|
+ try {
|
|
|
|
+ availableFileSystems.put("files-external", context.getExternalFilesDir(null).getAbsolutePath());
|
|
|
|
+ availableFileSystems.put("sdcard", Environment.getExternalStorageDirectory().getAbsolutePath());
|
|
|
|
+ availableFileSystems.put("cache-external", context.getExternalCacheDir().getAbsolutePath());
|
|
|
|
+ }
|
|
|
|
+ catch(NullPointerException e) {
|
|
|
|
+ Log.d(LOG_TAG, "External storage unavailable, check to see if USB Mass Storage Mode is on");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return availableFileSystems;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
|
|
|
+ super.initialize(cordova, webView);
|
|
|
|
+ this.filesystems = new ArrayList<Filesystem>();
|
|
|
|
+ this.pendingRequests = new PendingRequests();
|
|
|
|
+
|
|
|
|
+ String tempRoot = null;
|
|
|
|
+ String persistentRoot = null;
|
|
|
|
+
|
|
|
|
+ Activity activity = cordova.getActivity();
|
|
|
|
+ String packageName = activity.getPackageName();
|
|
|
|
+
|
|
|
|
+ String location = preferences.getString("androidpersistentfilelocation", "internal");
|
|
|
|
+
|
|
|
|
+ tempRoot = activity.getCacheDir().getAbsolutePath();
|
|
|
|
+ if ("internal".equalsIgnoreCase(location)) {
|
|
|
|
+ persistentRoot = activity.getFilesDir().getAbsolutePath() + "/files/";
|
|
|
|
+ this.configured = true;
|
|
|
|
+ } else if ("compatibility".equalsIgnoreCase(location)) {
|
|
|
|
+ /*
|
|
|
|
+ * Fall-back to compatibility mode -- this is the logic implemented in
|
|
|
|
+ * earlier versions of this plugin, and should be maintained here so
|
|
|
|
+ * that apps which were originally deployed with older versions of the
|
|
|
|
+ * plugin can continue to provide access to files stored under those
|
|
|
|
+ * versions.
|
|
|
|
+ */
|
|
|
|
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
|
|
+ persistentRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
|
|
|
|
+ tempRoot = Environment.getExternalStorageDirectory().getAbsolutePath() +
|
|
|
|
+ "/Android/data/" + packageName + "/cache/";
|
|
|
|
+ } else {
|
|
|
|
+ persistentRoot = "/data/data/" + packageName;
|
|
|
|
+ }
|
|
|
|
+ this.configured = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this.configured) {
|
|
|
|
+ // Create the directories if they don't exist.
|
|
|
|
+ File tmpRootFile = new File(tempRoot);
|
|
|
|
+ File persistentRootFile = new File(persistentRoot);
|
|
|
|
+ tmpRootFile.mkdirs();
|
|
|
|
+ persistentRootFile.mkdirs();
|
|
|
|
+
|
|
|
|
+ // Register initial filesystems
|
|
|
|
+ // Note: The temporary and persistent filesystems need to be the first two
|
|
|
|
+ // registered, so that they will match window.TEMPORARY and window.PERSISTENT,
|
|
|
|
+ // per spec.
|
|
|
|
+ this.registerFilesystem(new LocalFilesystem("temporary", webView.getContext(), webView.getResourceApi(), tmpRootFile));
|
|
|
|
+ this.registerFilesystem(new LocalFilesystem("persistent", webView.getContext(), webView.getResourceApi(), persistentRootFile));
|
|
|
|
+ this.registerFilesystem(new ContentFilesystem(webView.getContext(), webView.getResourceApi()));
|
|
|
|
+ this.registerFilesystem(new AssetFilesystem(webView.getContext().getAssets(), webView.getResourceApi()));
|
|
|
|
+
|
|
|
|
+ registerExtraFileSystems(getExtraFileSystemsPreference(activity), getAvailableFileSystems(activity));
|
|
|
|
+
|
|
|
|
+ // Initialize static plugin reference for deprecated getEntry method
|
|
|
|
+ if (filePlugin == null) {
|
|
|
|
+ FileUtils.filePlugin = this;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ Log.e(LOG_TAG, "File plugin configuration error: Please set AndroidPersistentFileLocation in config.xml to one of \"internal\" (for new applications) or \"compatibility\" (for compatibility with previous versions)");
|
|
|
|
+ activity.finish();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public static FileUtils getFilePlugin() {
|
|
|
|
+ return filePlugin;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Filesystem filesystemForURL(LocalFilesystemURL localURL) {
|
|
|
|
+ if (localURL == null) return null;
|
|
|
|
+ return filesystemForName(localURL.fsName);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Uri remapUri(Uri uri) {
|
|
|
|
+ // Remap only cdvfile: URLs (not content:).
|
|
|
|
+ if (!LocalFilesystemURL.FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ String path = fs.filesystemPathForURL(inputURL);
|
|
|
|
+ if (path != null) {
|
|
|
|
+ return Uri.parse("file://" + fs.filesystemPathForURL(inputURL));
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public boolean execute(String action, final String rawArgs, final CallbackContext callbackContext) {
|
|
|
|
+ if (!configured) {
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "File plugin is not configured. Please see the README.md file for details on how to update config.xml"));
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ if (action.equals("testSaveLocationExists")) {
|
|
|
|
+ threadhelper(new FileOp() {
|
|
|
|
+ public void run(JSONArray args) {
|
|
|
|
+ boolean b = DirectoryManager.testSaveLocationExists();
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("getFreeDiskSpace")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) {
|
|
|
|
+ // The getFreeDiskSpace plugin API is not documented, but some apps call it anyway via exec().
|
|
|
|
+ // For compatibility it always returns free space in the primary external storage, and
|
|
|
|
+ // does NOT fallback to internal store if external storage is unavailable.
|
|
|
|
+ long l = DirectoryManager.getFreeExternalStorageSpace();
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("testFileExists")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ boolean b = DirectoryManager.testFileExists(fname);
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("testDirectoryExists")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ boolean b = DirectoryManager.testFileExists(fname);
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("readAsText")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
|
|
|
|
+ String encoding = args.getString(1);
|
|
|
|
+ int start = args.getInt(2);
|
|
|
|
+ int end = args.getInt(3);
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("readAsDataURL")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
|
|
|
|
+ int start = args.getInt(1);
|
|
|
|
+ int end = args.getInt(2);
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ readFileAs(fname, start, end, callbackContext, null, -1);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("readAsArrayBuffer")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
|
|
|
|
+ int start = args.getInt(1);
|
|
|
|
+ int end = args.getInt(2);
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("readAsBinaryString")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
|
|
|
|
+ int start = args.getInt(1);
|
|
|
|
+ int end = args.getInt(2);
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("write")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ String nativeURL = resolveLocalFileSystemURI(fname).getString("nativeURL");
|
|
|
|
+ String data=args.getString(1);
|
|
|
|
+ int offset=args.getInt(2);
|
|
|
|
+ Boolean isBinary=args.getBoolean(3);
|
|
|
|
+
|
|
|
|
+ if(needPermission(nativeURL, WRITE)) {
|
|
|
|
+ getWritePermission(rawArgs, ACTION_WRITE, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ long fileSize = write(fname, data, offset, isBinary);
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("truncate")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ int offset=args.getInt(1);
|
|
|
|
+ long fileSize = truncateFile(fname, offset);
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("requestAllFileSystems")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws IOException, JSONException {
|
|
|
|
+ callbackContext.success(requestAllFileSystems());
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ } else if (action.equals("requestAllPaths")) {
|
|
|
|
+ cordova.getThreadPool().execute(
|
|
|
|
+ new Runnable() {
|
|
|
|
+ public void run() {
|
|
|
|
+ try {
|
|
|
|
+ callbackContext.success(requestAllPaths());
|
|
|
|
+ } catch (JSONException e) {
|
|
|
|
+ // TODO Auto-generated catch block
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+ } else if (action.equals("requestFileSystem")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException {
|
|
|
|
+ int fstype = args.getInt(0);
|
|
|
|
+ long requiredSize = args.optLong(1);
|
|
|
|
+ requestFileSystem(fstype, requiredSize, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("resolveLocalFileSystemURI")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws IOException, JSONException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ JSONObject obj = resolveLocalFileSystemURI(fname);
|
|
|
|
+ callbackContext.success(obj);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("getFileMetadata")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ JSONObject obj = getFileMetadata(fname);
|
|
|
|
+ callbackContext.success(obj);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("getParent")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, IOException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ JSONObject obj = getParent(fname);
|
|
|
|
+ callbackContext.success(obj);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("getDirectory")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
|
|
|
+ String dirname = args.getString(0);
|
|
|
|
+ String path = args.getString(1);
|
|
|
|
+ String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
|
|
|
|
+ boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
|
|
|
|
+
|
|
|
|
+ if(containsCreate && needPermission(nativeURL, WRITE)) {
|
|
|
|
+ getWritePermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if(!containsCreate && needPermission(nativeURL, READ)) {
|
|
|
|
+ getReadPermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
|
|
|
|
+ callbackContext.success(obj);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("getFile")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
|
|
|
+ String dirname = args.getString(0);
|
|
|
|
+ String path = args.getString(1);
|
|
|
|
+ String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
|
|
|
|
+ boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
|
|
|
|
+
|
|
|
|
+ if(containsCreate && needPermission(nativeURL, WRITE)) {
|
|
|
|
+ getWritePermission(rawArgs, ACTION_GET_FILE, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if(!containsCreate && needPermission(nativeURL, READ)) {
|
|
|
|
+ getReadPermission(rawArgs, ACTION_GET_FILE, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
|
|
|
|
+ callbackContext.success(obj);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("remove")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, NoModificationAllowedException, InvalidModificationException, MalformedURLException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ boolean success = remove(fname);
|
|
|
|
+ if (success) {
|
|
|
|
+ callbackContext.success();
|
|
|
|
+ } else {
|
|
|
|
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("removeRecursively")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, FileExistsException, MalformedURLException, NoModificationAllowedException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ boolean success = removeRecursively(fname);
|
|
|
|
+ if (success) {
|
|
|
|
+ callbackContext.success();
|
|
|
|
+ } else {
|
|
|
|
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("moveTo")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ String newParent=args.getString(1);
|
|
|
|
+ String newName=args.getString(2);
|
|
|
|
+ JSONObject entry = transferTo(fname, newParent, newName, true);
|
|
|
|
+ callbackContext.success(entry);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("copyTo")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ String newParent=args.getString(1);
|
|
|
|
+ String newName=args.getString(2);
|
|
|
|
+ JSONObject entry = transferTo(fname, newParent, newName, false);
|
|
|
|
+ callbackContext.success(entry);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("readEntries")) {
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ JSONArray entries = readEntries(fname);
|
|
|
|
+ callbackContext.success(entries);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else if (action.equals("_getLocalFilesystemPath")) {
|
|
|
|
+ // Internal method for testing: Get the on-disk location of a local filesystem url.
|
|
|
|
+ // [Currently used for testing file-transfer]
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
|
|
|
|
+ String localURLstr = args.getString(0);
|
|
|
|
+ String fname = filesystemPathForURL(localURLstr);
|
|
|
|
+ callbackContext.success(fname);
|
|
|
|
+ }
|
|
|
|
+ }, rawArgs, callbackContext);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void getReadPermission(String rawArgs, int action, CallbackContext callbackContext) {
|
|
|
|
+ int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
|
|
|
|
+ PermissionHelper.requestPermission(this, requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void getWritePermission(String rawArgs, int action, CallbackContext callbackContext) {
|
|
|
|
+ int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
|
|
|
|
+ PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean hasReadPermission() {
|
|
|
|
+ return PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean hasWritePermission() {
|
|
|
|
+ return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean needPermission(String nativeURL, int permissionType) throws JSONException {
|
|
|
|
+ JSONObject j = requestAllPaths();
|
|
|
|
+ String[] allowedStorageDirectories = {j.getString("applicationStorageDirectory"), j.getString("externalApplicationStorageDirectory")};
|
|
|
|
+
|
|
|
|
+ if(permissionType == READ && hasReadPermission()) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ else if(permissionType == WRITE && hasWritePermission()) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Permission required if the native url lies outside the allowed storage directories
|
|
|
|
+ for(String directory : allowedStorageDirectories) {
|
|
|
|
+ if(nativeURL.startsWith(directory)) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public LocalFilesystemURL resolveNativeUri(Uri nativeUri) {
|
|
|
|
+ LocalFilesystemURL localURL = null;
|
|
|
|
+
|
|
|
|
+ // Try all installed filesystems. Return the best matching URL
|
|
|
|
+ // (determined by the shortest resulting URL)
|
|
|
|
+ for (Filesystem fs : filesystems) {
|
|
|
|
+ LocalFilesystemURL url = fs.toLocalUri(nativeUri);
|
|
|
|
+ if (url != null) {
|
|
|
|
+ // A shorter fullPath implies that the filesystem is a better
|
|
|
|
+ // match for the local path than the previous best.
|
|
|
|
+ if (localURL == null || (url.uri.toString().length() < localURL.toString().length())) {
|
|
|
|
+ localURL = url;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return localURL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * These two native-only methods can be used by other plugins to translate between
|
|
|
|
+ * device file system paths and URLs. By design, there is no direct JavaScript
|
|
|
|
+ * interface to these methods.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ public String filesystemPathForURL(String localURLstr) throws MalformedURLException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(localURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.filesystemPathForURL(inputURL);
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public LocalFilesystemURL filesystemURLforLocalPath(String localPath) {
|
|
|
|
+ LocalFilesystemURL localURL = null;
|
|
|
|
+ int shortestFullPath = 0;
|
|
|
|
+
|
|
|
|
+ // Try all installed filesystems. Return the best matching URL
|
|
|
|
+ // (determined by the shortest resulting URL)
|
|
|
|
+ for (Filesystem fs: filesystems) {
|
|
|
|
+ LocalFilesystemURL url = fs.URLforFilesystemPath(localPath);
|
|
|
|
+ if (url != null) {
|
|
|
|
+ // A shorter fullPath implies that the filesystem is a better
|
|
|
|
+ // match for the local path than the previous best.
|
|
|
|
+ if (localURL == null || (url.path.length() < shortestFullPath)) {
|
|
|
|
+ localURL = url;
|
|
|
|
+ shortestFullPath = url.path.length();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return localURL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /* helper to execute functions async and handle the result codes
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+ private void threadhelper(final FileOp f, final String rawArgs, final CallbackContext callbackContext){
|
|
|
|
+ cordova.getThreadPool().execute(new Runnable() {
|
|
|
|
+ public void run() {
|
|
|
|
+ try {
|
|
|
|
+ JSONArray args = new JSONArray(rawArgs);
|
|
|
|
+ f.run(args);
|
|
|
|
+ } catch ( Exception e) {
|
|
|
|
+ if( e instanceof EncodingException){
|
|
|
|
+ callbackContext.error(FileUtils.ENCODING_ERR);
|
|
|
|
+ } else if(e instanceof FileNotFoundException) {
|
|
|
|
+ callbackContext.error(FileUtils.NOT_FOUND_ERR);
|
|
|
|
+ } else if(e instanceof FileExistsException) {
|
|
|
|
+ callbackContext.error(FileUtils.PATH_EXISTS_ERR);
|
|
|
|
+ } else if(e instanceof NoModificationAllowedException ) {
|
|
|
|
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
|
|
|
|
+ } else if(e instanceof InvalidModificationException ) {
|
|
|
|
+ callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
|
|
|
|
+ } else if(e instanceof MalformedURLException ) {
|
|
|
|
+ callbackContext.error(FileUtils.ENCODING_ERR);
|
|
|
|
+ } else if(e instanceof IOException ) {
|
|
|
|
+ callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
|
|
|
|
+ } else if(e instanceof EncodingException ) {
|
|
|
|
+ callbackContext.error(FileUtils.ENCODING_ERR);
|
|
|
|
+ } else if(e instanceof TypeMismatchException ) {
|
|
|
|
+ callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
|
|
|
|
+ } else if(e instanceof JSONException ) {
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
|
|
|
|
+ } else {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ callbackContext.error(FileUtils.UNKNOWN_ERR);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Allows the user to look up the Entry for a file or directory referred to by a local URI.
|
|
|
|
+ *
|
|
|
|
+ * @param uriString of the file/directory to look up
|
|
|
|
+ * @return a JSONObject representing a Entry from the filesystem
|
|
|
|
+ * @throws MalformedURLException if the url is not valid
|
|
|
|
+ * @throws FileNotFoundException if the file does not exist
|
|
|
|
+ * @throws IOException if the user can't read the file
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ */
|
|
|
|
+ private JSONObject resolveLocalFileSystemURI(String uriString) throws IOException, JSONException {
|
|
|
|
+ if (uriString == null) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ Uri uri = Uri.parse(uriString);
|
|
|
|
+ boolean isNativeUri = false;
|
|
|
|
+
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
|
|
|
|
+ if (inputURL == null) {
|
|
|
|
+ /* Check for file://, content:// urls */
|
|
|
|
+ inputURL = resolveNativeUri(uri);
|
|
|
|
+ isNativeUri = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ if (fs.exists(inputURL)) {
|
|
|
|
+ if (!isNativeUri) {
|
|
|
|
+ // If not already resolved as native URI, resolve to a native URI and back to
|
|
|
|
+ // fix the terminating slash based on whether the entry is a directory or file.
|
|
|
|
+ inputURL = fs.toLocalUri(fs.toNativeUri(inputURL));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return fs.getEntryForLocalURL(inputURL);
|
|
|
|
+ }
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ throw new FileNotFoundException();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Read the list of files from this directory.
|
|
|
|
+ *
|
|
|
|
+ * @return a JSONArray containing JSONObjects that represent Entry objects.
|
|
|
|
+ * @throws FileNotFoundException if the directory is not found.
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ * @throws MalformedURLException
|
|
|
|
+ */
|
|
|
|
+ private JSONArray readEntries(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.readEntriesAtLocalURL(inputURL);
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * A setup method that handles the move/copy of files/directories
|
|
|
|
+ *
|
|
|
|
+ * @param newName for the file directory to be called, if null use existing file name
|
|
|
|
+ * @param move if false do a copy, if true do a move
|
|
|
|
+ * @return a Entry object
|
|
|
|
+ * @throws NoModificationAllowedException
|
|
|
|
+ * @throws IOException
|
|
|
|
+ * @throws InvalidModificationException
|
|
|
|
+ * @throws EncodingException
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ * @throws FileExistsException
|
|
|
|
+ */
|
|
|
|
+ private JSONObject transferTo(String srcURLstr, String destURLstr, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
|
|
|
|
+ if (srcURLstr == null || destURLstr == null) {
|
|
|
|
+ // either no source or no destination provided
|
|
|
|
+ throw new FileNotFoundException();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LocalFilesystemURL srcURL = LocalFilesystemURL.parse(srcURLstr);
|
|
|
|
+ LocalFilesystemURL destURL = LocalFilesystemURL.parse(destURLstr);
|
|
|
|
+
|
|
|
|
+ Filesystem srcFs = this.filesystemForURL(srcURL);
|
|
|
|
+ Filesystem destFs = this.filesystemForURL(destURL);
|
|
|
|
+
|
|
|
|
+ // Check for invalid file name
|
|
|
|
+ if (newName != null && newName.contains(":")) {
|
|
|
|
+ throw new EncodingException("Bad file name");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return destFs.copyFileToURL(destURL, newName, srcFs, srcURL, move);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Deletes a directory and all of its contents, if any. In the event of an error
|
|
|
|
+ * [e.g. trying to delete a directory that contains a file that cannot be removed],
|
|
|
|
+ * some of the contents of the directory may be deleted.
|
|
|
|
+ * It is an error to attempt to delete the root directory of a filesystem.
|
|
|
|
+ *
|
|
|
|
+ * @return a boolean representing success of failure
|
|
|
|
+ * @throws FileExistsException
|
|
|
|
+ * @throws NoModificationAllowedException
|
|
|
|
+ * @throws MalformedURLException
|
|
|
|
+ */
|
|
|
|
+ private boolean removeRecursively(String baseURLstr) throws FileExistsException, NoModificationAllowedException, MalformedURLException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
|
|
|
|
+ // You can't delete the root directory.
|
|
|
|
+ if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
|
|
|
|
+ throw new NoModificationAllowedException("You can't delete the root directory");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.recursiveRemoveFileAtLocalURL(inputURL);
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty.
|
|
|
|
+ * It is an error to attempt to delete the root directory of a filesystem.
|
|
|
|
+ *
|
|
|
|
+ * @return a boolean representing success of failure
|
|
|
|
+ * @throws NoModificationAllowedException
|
|
|
|
+ * @throws InvalidModificationException
|
|
|
|
+ * @throws MalformedURLException
|
|
|
|
+ */
|
|
|
|
+ private boolean remove(String baseURLstr) throws NoModificationAllowedException, InvalidModificationException, MalformedURLException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
|
|
|
|
+ // You can't delete the root directory.
|
|
|
|
+ if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
|
|
|
|
+
|
|
|
|
+ throw new NoModificationAllowedException("You can't delete the root directory");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.removeFileAtLocalURL(inputURL);
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Creates or looks up a file.
|
|
|
|
+ *
|
|
|
|
+ * @param baseURLstr base directory
|
|
|
|
+ * @param path file/directory to lookup or create
|
|
|
|
+ * @param options specify whether to create or not
|
|
|
|
+ * @param directory if true look up directory, if false look up file
|
|
|
|
+ * @return a Entry object
|
|
|
|
+ * @throws FileExistsException
|
|
|
|
+ * @throws IOException
|
|
|
|
+ * @throws TypeMismatchException
|
|
|
|
+ * @throws EncodingException
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ */
|
|
|
|
+ private JSONObject getFile(String baseURLstr, String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.getFileForLocalURL(inputURL, path, options, directory);
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Look up the parent DirectoryEntry containing this Entry.
|
|
|
|
+ * If this Entry is the root of its filesystem, its parent is itself.
|
|
|
|
+ */
|
|
|
|
+ private JSONObject getParent(String baseURLstr) throws JSONException, IOException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.getParentForLocalURL(inputURL);
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Returns a File that represents the current state of the file that this FileEntry represents.
|
|
|
|
+ *
|
|
|
|
+ * @return returns a JSONObject represent a W3C File object
|
|
|
|
+ */
|
|
|
|
+ private JSONObject getFileMetadata(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+ return fs.getFileMetadataForLocalURL(inputURL);
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Requests a filesystem in which to store application data.
|
|
|
|
+ *
|
|
|
|
+ * @param type of file system requested
|
|
|
|
+ * @param requiredSize required free space in the file system in bytes
|
|
|
|
+ * @param callbackContext context for returning the result or error
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ */
|
|
|
|
+ private void requestFileSystem(int type, long requiredSize, final CallbackContext callbackContext) throws JSONException {
|
|
|
|
+ Filesystem rootFs = null;
|
|
|
|
+ try {
|
|
|
|
+ rootFs = this.filesystems.get(type);
|
|
|
|
+ } catch (ArrayIndexOutOfBoundsException e) {
|
|
|
|
+ // Pass null through
|
|
|
|
+ }
|
|
|
|
+ if (rootFs == null) {
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR));
|
|
|
|
+ } else {
|
|
|
|
+ // If a nonzero required size was specified, check that the retrieved filesystem has enough free space.
|
|
|
|
+ long availableSize = 0;
|
|
|
|
+ if (requiredSize > 0) {
|
|
|
|
+ availableSize = rootFs.getFreeSpaceInBytes();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (availableSize < requiredSize) {
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
|
|
|
|
+ } else {
|
|
|
|
+ JSONObject fs = new JSONObject();
|
|
|
|
+ fs.put("name", rootFs.name);
|
|
|
|
+ fs.put("root", rootFs.getRootEntry());
|
|
|
|
+ callbackContext.success(fs);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Requests a filesystem in which to store application data.
|
|
|
|
+ *
|
|
|
|
+ * @return a JSONObject representing the file system
|
|
|
|
+ */
|
|
|
|
+ private JSONArray requestAllFileSystems() throws IOException, JSONException {
|
|
|
|
+ JSONArray ret = new JSONArray();
|
|
|
|
+ for (Filesystem fs : filesystems) {
|
|
|
|
+ ret.put(fs.getRootEntry());
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static String toDirUrl(File f) {
|
|
|
|
+ return Uri.fromFile(f).toString() + '/';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private JSONObject requestAllPaths() throws JSONException {
|
|
|
|
+ Context context = cordova.getActivity();
|
|
|
|
+ JSONObject ret = new JSONObject();
|
|
|
|
+ ret.put("applicationDirectory", "file:///android_asset/");
|
|
|
|
+ ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
|
|
|
|
+ ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
|
|
|
|
+ ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
|
|
|
|
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
|
|
+ try {
|
|
|
|
+ ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
|
|
|
|
+ ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
|
|
|
|
+ ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
|
|
|
|
+ ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
|
|
|
|
+ }
|
|
|
|
+ catch(NullPointerException e) {
|
|
|
|
+ /* If external storage is unavailable, context.getExternal* returns null */
|
|
|
|
+ Log.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Returns a JSON object representing the given File. Internal APIs should be modified
|
|
|
|
+ * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
|
|
|
|
+ *
|
|
|
|
+ * @param file the File to convert
|
|
|
|
+ * @return a JSON representation of the given File
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ */
|
|
|
|
+ public JSONObject getEntryForFile(File file) throws JSONException {
|
|
|
|
+ JSONObject entry;
|
|
|
|
+
|
|
|
|
+ for (Filesystem fs : filesystems) {
|
|
|
|
+ entry = fs.makeEntryForFile(file);
|
|
|
|
+ if (entry != null) {
|
|
|
|
+ return entry;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Returns a JSON object representing the given File. Deprecated, as this is only used by
|
|
|
|
+ * FileTransfer, and because it is a static method that should really be an instance method,
|
|
|
|
+ * since it depends on the actual filesystem roots in use. Internal APIs should be modified
|
|
|
|
+ * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
|
|
|
|
+ *
|
|
|
|
+ * @param file the File to convert
|
|
|
|
+ * @return a JSON representation of the given File
|
|
|
|
+ * @throws JSONException
|
|
|
|
+ */
|
|
|
|
+ @Deprecated
|
|
|
|
+ public static JSONObject getEntry(File file) throws JSONException {
|
|
|
|
+ if (getFilePlugin() != null) {
|
|
|
|
+ return getFilePlugin().getEntryForFile(file);
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Read the contents of a file.
|
|
|
|
+ * This is done in a background thread; the result is sent to the callback.
|
|
|
|
+ *
|
|
|
|
+ * @param start Start position in the file.
|
|
|
|
+ * @param end End position to stop at (exclusive).
|
|
|
|
+ * @param callbackContext The context through which to send the result.
|
|
|
|
+ * @param encoding The encoding to return contents as. Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
|
|
|
|
+ * @param resultType The desired type of data to send to the callback.
|
|
|
|
+ * @return Contents of file.
|
|
|
|
+ */
|
|
|
|
+ public void readFileAs(final String srcURLstr, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) throws MalformedURLException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fs.readFileAtURL(inputURL, start, end, new Filesystem.ReadFileCallback() {
|
|
|
|
+ public void handleData(InputStream inputStream, String contentType) {
|
|
|
|
+ try {
|
|
|
|
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
|
|
+ final int BUFFER_SIZE = 8192;
|
|
|
|
+ byte[] buffer = new byte[BUFFER_SIZE];
|
|
|
|
+
|
|
|
|
+ for (;;) {
|
|
|
|
+ int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
|
|
|
|
+
|
|
|
|
+ if (bytesRead <= 0) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ os.write(buffer, 0, bytesRead);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ PluginResult result;
|
|
|
|
+ switch (resultType) {
|
|
|
|
+ case PluginResult.MESSAGE_TYPE_STRING:
|
|
|
|
+ result = new PluginResult(PluginResult.Status.OK, os.toString(encoding));
|
|
|
|
+ break;
|
|
|
|
+ case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
|
|
|
+ result = new PluginResult(PluginResult.Status.OK, os.toByteArray());
|
|
|
|
+ break;
|
|
|
|
+ case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
|
|
|
+ result = new PluginResult(PluginResult.Status.OK, os.toByteArray(), true);
|
|
|
|
+ break;
|
|
|
|
+ default: // Base64.
|
|
|
|
+ byte[] base64 = Base64.encode(os.toByteArray(), Base64.NO_WRAP);
|
|
|
|
+ String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
|
|
|
|
+ result = new PluginResult(PluginResult.Status.OK, s);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ callbackContext.sendPluginResult(result);
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ Log.d(LOG_TAG, e.getLocalizedMessage());
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ Log.d(LOG_TAG, e.getLocalizedMessage());
|
|
|
|
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Write contents of file.
|
|
|
|
+ *
|
|
|
|
+ * @param data The contents of the file.
|
|
|
|
+ * @param offset The position to begin writing the file.
|
|
|
|
+ * @param isBinary True if the file contents are base64-encoded binary data
|
|
|
|
+ */
|
|
|
|
+ /**/
|
|
|
|
+ public long write(String srcURLstr, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ long x = fs.writeToFileAtURL(inputURL, data, offset, isBinary); Log.d("TEST",srcURLstr + ": "+x); return x;
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Truncate the file to size
|
|
|
|
+ */
|
|
|
|
+ private long truncateFile(String srcURLstr, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
|
|
|
|
+ try {
|
|
|
|
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
|
|
|
|
+ Filesystem fs = this.filesystemForURL(inputURL);
|
|
|
|
+ if (fs == null) {
|
|
|
|
+ throw new MalformedURLException("No installed handlers for this URL");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return fs.truncateFileAtURL(inputURL, size);
|
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
|
+ throw new MalformedURLException("Unrecognized filesystem URL");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Handle the response
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ public void onRequestPermissionResult(int requestCode, String[] permissions,
|
|
|
|
+ int[] grantResults) throws JSONException {
|
|
|
|
+
|
|
|
|
+ final PendingRequests.Request req = pendingRequests.getAndRemove(requestCode);
|
|
|
|
+ if (req != null) {
|
|
|
|
+ for(int r:grantResults)
|
|
|
|
+ {
|
|
|
|
+ if(r == PackageManager.PERMISSION_DENIED)
|
|
|
|
+ {
|
|
|
|
+ req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR));
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ switch(req.getAction())
|
|
|
|
+ {
|
|
|
|
+ case ACTION_GET_FILE:
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
|
|
|
+ String dirname = args.getString(0);
|
|
|
|
+
|
|
|
|
+ String path = args.getString(1);
|
|
|
|
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
|
|
|
|
+ req.getCallbackContext().success(obj);
|
|
|
|
+ }
|
|
|
|
+ }, req.getRawArgs(), req.getCallbackContext());
|
|
|
|
+ break;
|
|
|
|
+ case ACTION_GET_DIRECTORY:
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
|
|
|
|
+ String dirname = args.getString(0);
|
|
|
|
+
|
|
|
|
+ String path = args.getString(1);
|
|
|
|
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
|
|
|
|
+ req.getCallbackContext().success(obj);
|
|
|
|
+ }
|
|
|
|
+ }, req.getRawArgs(), req.getCallbackContext());
|
|
|
|
+ break;
|
|
|
|
+ case ACTION_WRITE:
|
|
|
|
+ threadhelper( new FileOp( ){
|
|
|
|
+ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
|
|
|
|
+ String fname=args.getString(0);
|
|
|
|
+ String data=args.getString(1);
|
|
|
|
+ int offset=args.getInt(2);
|
|
|
|
+ Boolean isBinary=args.getBoolean(3);
|
|
|
|
+ long fileSize = write(fname, data, offset, isBinary);
|
|
|
|
+ req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
|
|
|
|
+ }
|
|
|
|
+ }, req.getRawArgs(), req.getCallbackContext());
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ Log.d(LOG_TAG, "Received permission callback for unknown request code");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|