summaryrefslogtreecommitdiff
path: root/plugins/org.apache.cordova.file/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/org.apache.cordova.file/src')
-rw-r--r--plugins/org.apache.cordova.file/src/android/DirectoryManager.java133
-rw-r--r--plugins/org.apache.cordova.file/src/android/EncodingException.java29
-rw-r--r--plugins/org.apache.cordova.file/src/android/FileExistsException.java29
-rw-r--r--plugins/org.apache.cordova.file/src/android/FileHelper.java158
-rwxr-xr-xplugins/org.apache.cordova.file/src/android/FileUtils.java1191
-rw-r--r--plugins/org.apache.cordova.file/src/android/InvalidModificationException.java30
-rw-r--r--plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java29
-rw-r--r--plugins/org.apache.cordova.file/src/android/TypeMismatchException.java30
-rw-r--r--plugins/org.apache.cordova.file/src/blackberry10/index.js10
-rw-r--r--plugins/org.apache.cordova.file/src/ios/CDVFile.h107
-rw-r--r--plugins/org.apache.cordova.file/src/ios/CDVFile.m1417
-rw-r--r--plugins/org.apache.cordova.file/src/windows8/FileProxy.js845
-rw-r--r--plugins/org.apache.cordova.file/src/wp/File.cs1676
13 files changed, 5684 insertions, 0 deletions
diff --git a/plugins/org.apache.cordova.file/src/android/DirectoryManager.java b/plugins/org.apache.cordova.file/src/android/DirectoryManager.java
new file mode 100644
index 00000000..bcc005b2
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/DirectoryManager.java
@@ -0,0 +1,133 @@
+/*
+ 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.os.Environment;
+import android.os.StatFs;
+
+import java.io.File;
+
+/**
+ * This class provides file directory utilities.
+ * All file operations are performed on the SD card.
+ *
+ * It is used by the FileUtils class.
+ */
+public class DirectoryManager {
+
+ @SuppressWarnings("unused")
+ private static final String LOG_TAG = "DirectoryManager";
+
+ /**
+ * Determine if a file or directory exists.
+ * @param name The name of the file to check.
+ * @return T=exists, F=not found
+ */
+ public static boolean testFileExists(String name) {
+ boolean status;
+
+ // If SD card exists
+ if ((testSaveLocationExists()) && (!name.equals(""))) {
+ File path = Environment.getExternalStorageDirectory();
+ File newPath = constructFilePaths(path.toString(), name);
+ status = newPath.exists();
+ }
+ // If no SD card
+ else {
+ status = false;
+ }
+ return status;
+ }
+
+ /**
+ * Get the free disk space
+ *
+ * @return Size in KB or -1 if not available
+ */
+ public static long getFreeDiskSpace(boolean checkInternal) {
+ String status = Environment.getExternalStorageState();
+ long freeSpace = 0;
+
+ // If SD card exists
+ if (status.equals(Environment.MEDIA_MOUNTED)) {
+ freeSpace = freeSpaceCalculation(Environment.getExternalStorageDirectory().getPath());
+ }
+ else if (checkInternal) {
+ freeSpace = freeSpaceCalculation("/");
+ }
+ // If no SD card and we haven't been asked to check the internal directory then return -1
+ else {
+ return -1;
+ }
+
+ return freeSpace;
+ }
+
+ /**
+ * Given a path return the number of free KB
+ *
+ * @param path to the file system
+ * @return free space in KB
+ */
+ private static long freeSpaceCalculation(String path) {
+ StatFs stat = new StatFs(path);
+ long blockSize = stat.getBlockSize();
+ long availableBlocks = stat.getAvailableBlocks();
+ return availableBlocks * blockSize / 1024;
+ }
+
+ /**
+ * Determine if SD card exists.
+ *
+ * @return T=exists, F=not found
+ */
+ public static boolean testSaveLocationExists() {
+ String sDCardStatus = Environment.getExternalStorageState();
+ boolean status;
+
+ // If SD card is mounted
+ if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
+ status = true;
+ }
+
+ // If no SD card
+ else {
+ status = false;
+ }
+ return status;
+ }
+
+ /**
+ * Create a new file object from two file paths.
+ *
+ * @param file1 Base file path
+ * @param file2 Remaining file path
+ * @return File object
+ */
+ private static File constructFilePaths (String file1, String file2) {
+ File newPath;
+ if (file2.startsWith(file1)) {
+ newPath = new File(file2);
+ }
+ else {
+ newPath = new File(file1 + "/" + file2);
+ }
+ return newPath;
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/EncodingException.java b/plugins/org.apache.cordova.file/src/android/EncodingException.java
new file mode 100644
index 00000000..e9e1653b
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/EncodingException.java
@@ -0,0 +1,29 @@
+/*
+ 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;
+
+@SuppressWarnings("serial")
+public class EncodingException extends Exception {
+
+ public EncodingException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/FileExistsException.java b/plugins/org.apache.cordova.file/src/android/FileExistsException.java
new file mode 100644
index 00000000..5c4d83dc
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/FileExistsException.java
@@ -0,0 +1,29 @@
+/*
+ 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;
+
+@SuppressWarnings("serial")
+public class FileExistsException extends Exception {
+
+ public FileExistsException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/FileHelper.java b/plugins/org.apache.cordova.file/src/android/FileHelper.java
new file mode 100644
index 00000000..867f128c
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/FileHelper.java
@@ -0,0 +1,158 @@
+/*
+ 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.database.Cursor;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.LOG;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+public class FileHelper {
+ private static final String LOG_TAG = "FileUtils";
+ private static final String _DATA = "_data";
+
+ /**
+ * Returns the real path of the given URI string.
+ * If the given URI string represents a content:// URI, the real path is retrieved from the media store.
+ *
+ * @param uriString the URI string of the audio/image/video
+ * @param cordova the current application context
+ * @return the full path to the file
+ */
+ @SuppressWarnings("deprecation")
+ public static String getRealPath(String uriString, CordovaInterface cordova) {
+ String realPath = null;
+
+ if (uriString.startsWith("content://")) {
+ String[] proj = { _DATA };
+ Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
+ int column_index = cursor.getColumnIndexOrThrow(_DATA);
+ cursor.moveToFirst();
+ realPath = cursor.getString(column_index);
+ if (realPath == null) {
+ LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
+ }
+ } else if (uriString.startsWith("file://")) {
+ realPath = uriString.substring(7);
+ if (realPath.startsWith("/android_asset/")) {
+ LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
+ realPath = null;
+ }
+ } else {
+ realPath = uriString;
+ }
+
+ return realPath;
+ }
+
+ /**
+ * Returns the real path of the given URI.
+ * If the given URI is a content:// URI, the real path is retrieved from the media store.
+ *
+ * @param uri the URI of the audio/image/video
+ * @param cordova the current application context
+ * @return the full path to the file
+ */
+ public static String getRealPath(Uri uri, CordovaInterface cordova) {
+ return FileHelper.getRealPath(uri.toString(), cordova);
+ }
+
+ /**
+ * Returns an input stream based on given URI string.
+ *
+ * @param uriString the URI string from which to obtain the input stream
+ * @param cordova the current application context
+ * @return an input stream into the data at the given URI or null if given an invalid URI string
+ * @throws IOException
+ */
+ public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
+ if (uriString.startsWith("content")) {
+ Uri uri = Uri.parse(uriString);
+ return cordova.getActivity().getContentResolver().openInputStream(uri);
+ } else if (uriString.startsWith("file://")) {
+ int question = uriString.indexOf("?");
+ if (question > -1) {
+ uriString = uriString.substring(0,question);
+ }
+ if (uriString.startsWith("file:///android_asset/")) {
+ Uri uri = Uri.parse(uriString);
+ String relativePath = uri.getPath().substring(15);
+ return cordova.getActivity().getAssets().open(relativePath);
+ } else {
+ return new FileInputStream(getRealPath(uriString, cordova));
+ }
+ } else {
+ return new FileInputStream(getRealPath(uriString, cordova));
+ }
+ }
+
+ /**
+ * Removes the "file://" prefix from the given URI string, if applicable.
+ * If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
+ *
+ * @param uriString the URI string to operate on
+ * @return a path without the "file://" prefix
+ */
+ public static String stripFileProtocol(String uriString) {
+ if (uriString.startsWith("file://")) {
+ uriString = uriString.substring(7);
+ }
+ return uriString;
+ }
+
+ public static String getMimeTypeForExtension(String path) {
+ String extension = path;
+ int lastDot = extension.lastIndexOf('.');
+ if (lastDot != -1) {
+ extension = extension.substring(lastDot + 1);
+ }
+ // Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
+ extension = extension.toLowerCase(Locale.getDefault());
+ if (extension.equals("3ga")) {
+ return "audio/3gpp";
+ }
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+
+ /**
+ * Returns the mime type of the data specified by the given URI string.
+ *
+ * @param uriString the URI string of the data
+ * @return the mime type of the specified data
+ */
+ public static String getMimeType(String uriString, CordovaInterface cordova) {
+ String mimeType = null;
+
+ Uri uri = Uri.parse(uriString);
+ if (uriString.startsWith("content://")) {
+ mimeType = cordova.getActivity().getContentResolver().getType(uri);
+ } else {
+ mimeType = getMimeTypeForExtension(uri.getPath());
+ }
+
+ return mimeType;
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/FileUtils.java b/plugins/org.apache.cordova.file/src/android/FileUtils.java
new file mode 100755
index 00000000..9a9452fb
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/FileUtils.java
@@ -0,0 +1,1191 @@
+/*
+ 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.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.util.Base64;
+import android.util.Log;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.PluginResult;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayInputStream;
+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.RandomAccessFile;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.channels.FileChannel;
+
+/**
+ * This class provides SD card file and directory services to JavaScript.
+ * Only files on the SD card can be accessed.
+ */
+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;
+
+ public static int TEMPORARY = 0;
+ public static int PERSISTENT = 1;
+ public static int RESOURCE = 2;
+ public static int APPLICATION = 3;
+
+ private interface FileOp {
+ void run( ) throws Exception;
+ }
+
+ /**
+ * Executes the request and returns whether the action was valid.
+ *
+ * @param action The action to execute.
+ * @param args JSONArray of arguments for the plugin.
+ * @param callbackContext The callback context used when calling back into JavaScript.
+ * @return True if the action was valid, false otherwise.
+ */
+ public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ if (action.equals("testSaveLocationExists")) {
+ threadhelper( new FileOp( ){
+ public void run() {
+ boolean b = DirectoryManager.testSaveLocationExists();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getFreeDiskSpace")) {
+ threadhelper( new FileOp( ){
+ public void run() {
+ long l = DirectoryManager.getFreeDiskSpace(false);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
+ }
+ },callbackContext);
+ }
+ else if (action.equals("testFileExists")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ boolean b = DirectoryManager.testFileExists(fname);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("testDirectoryExists")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ boolean b = DirectoryManager.testFileExists(fname);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("readAsText")) {
+ final String encoding = args.getString(1);
+ final int start = args.getInt(2);
+ final int end = args.getInt(3);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("readAsDataURL")) {
+ final int start = args.getInt(1);
+ final int end = args.getInt(2);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ readFileAs(fname, start, end, callbackContext, null, -1);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("readAsArrayBuffer")) {
+ final int start = args.getInt(1);
+ final int end = args.getInt(2);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("readAsBinaryString")) {
+ final int start = args.getInt(1);
+ final int end = args.getInt(2);
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() {
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("write")) {
+ final String fname=args.getString(0);
+ final String data=args.getString(1);
+ final int offset=args.getInt(2);
+ final Boolean isBinary=args.getBoolean(3);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, IOException, NoModificationAllowedException {
+ long fileSize = write(fname, data, offset, isBinary);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("truncate")) {
+ final String fname=args.getString(0);
+ final int offset=args.getInt(1);
+ threadhelper( new FileOp( ){
+ public void run( ) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ long fileSize = truncateFile(fname, offset);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("requestFileSystem")) {
+ final int fstype=args.getInt(0);
+ final long size = args.optLong(1);
+ threadhelper( new FileOp( ){
+ public void run() throws IOException, JSONException {
+ if (size != 0 && size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
+ } else {
+ JSONObject obj = requestFileSystem(fstype);
+ callbackContext.success(obj);
+ }
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("resolveLocalFileSystemURI")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws IOException, JSONException {
+ JSONObject obj = resolveLocalFileSystemURI(fname);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getMetadata")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, JSONException {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getMetadata(fname)));
+ }
+ }, callbackContext);
+ }
+ else if (action.equals("getFileMetadata")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, JSONException {
+ JSONObject obj = getFileMetadata(fname);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getParent")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws JSONException {
+ JSONObject obj = getParent(fname);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getDirectory")) {
+ final String dirname=args.getString(0);
+ final String fname=args.getString(1);
+ threadhelper( new FileOp( ){
+ public void run() throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ JSONObject obj = getFile(dirname, fname, args.optJSONObject(2), true);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("getFile")) {
+ final String dirname=args.getString(0);
+ final String fname=args.getString(1);
+ threadhelper( new FileOp( ){
+ public void run() throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ JSONObject obj = getFile(dirname, fname, args.optJSONObject(2), false);
+ callbackContext.success(obj);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("remove")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws NoModificationAllowedException, InvalidModificationException {
+ boolean success= remove(fname);
+ if (success) {
+ notifyDelete(fname);
+ callbackContext.success();
+ } else {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ }
+ }
+ },callbackContext);
+ }
+ else if (action.equals("removeRecursively")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileExistsException {
+ boolean success = removeRecursively(fname);
+ if (success) {
+ callbackContext.success();
+ } else {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ }
+ }
+ },callbackContext);
+ }
+ else if (action.equals("moveTo")) {
+ final String fname=args.getString(0);
+ final String newParent=args.getString(1);
+ final String newName=args.getString(2);
+ threadhelper( new FileOp( ){
+ public void run() throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ JSONObject entry = transferTo(fname, newParent, newName, true);
+ callbackContext.success(entry);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("copyTo")) {
+ final String fname=args.getString(0);
+ final String newParent=args.getString(1);
+ final String newName=args.getString(2);
+ threadhelper( new FileOp( ){
+ public void run() throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ JSONObject entry = transferTo(fname, newParent, newName, false);
+ callbackContext.success(entry);
+ }
+ },callbackContext);
+ }
+ else if (action.equals("readEntries")) {
+ final String fname=args.getString(0);
+ threadhelper( new FileOp( ){
+ public void run() throws FileNotFoundException, JSONException {
+ JSONArray entries = readEntries(fname);
+ callbackContext.success(entries);
+ }
+ },callbackContext);
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ /* helper to execute functions async and handle the result codes
+ *
+ */
+ private void threadhelper(final FileOp f, final CallbackContext callbackContext){
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ f.run();
+ } catch ( Exception e) {
+ e.printStackTrace();
+ 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);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Need to check to see if we need to clean up the content store
+ *
+ * @param filePath the path to check
+ */
+ private void notifyDelete(String filePath) {
+ String newFilePath = FileHelper.getRealPath(filePath, cordova);
+ try {
+ this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ MediaStore.Images.Media.DATA + " = ?",
+ new String[] { newFilePath });
+ } catch (UnsupportedOperationException t) {
+ // Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
+ // The ContentResolver applies only when the file was registered in the
+ // first case, which is generally only the case with images.
+ }
+ }
+
+ /**
+ * Allows the user to look up the Entry for a file or directory referred to by a local URI.
+ *
+ * @param url 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
+ */
+ @SuppressWarnings("deprecation")
+ private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException {
+ String decoded = URLDecoder.decode(url, "UTF-8");
+
+ File fp = null;
+
+ // Handle the special case where you get an Android content:// uri.
+ if (decoded.startsWith("content:")) {
+ Cursor cursor = this.cordova.getActivity().managedQuery(Uri.parse(decoded), new String[] { MediaStore.Images.Media.DATA }, null, null, null);
+ // Note: MediaStore.Images/Audio/Video.Media.DATA is always "_data"
+ int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+ cursor.moveToFirst();
+ fp = new File(cursor.getString(column_index));
+ } else {
+ // Test to see if this is a valid URL first
+ @SuppressWarnings("unused")
+ URL testUrl = new URL(decoded);
+
+ if (decoded.startsWith("file://")) {
+ int questionMark = decoded.indexOf("?");
+ if (questionMark < 0) {
+ fp = new File(decoded.substring(7, decoded.length()));
+ } else {
+ fp = new File(decoded.substring(7, questionMark));
+ }
+ } else {
+ fp = new File(decoded);
+ }
+ }
+
+ if (!fp.exists()) {
+ throw new FileNotFoundException();
+ }
+ if (!fp.canRead()) {
+ throw new IOException();
+ }
+ return getEntry(fp);
+ }
+
+ /**
+ * Read the list of files from this directory.
+ *
+ * @param fileName the directory to read from
+ * @return a JSONArray containing JSONObjects that represent Entry objects.
+ * @throws FileNotFoundException if the directory is not found.
+ * @throws JSONException
+ */
+ private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException {
+ File fp = createFileObject(fileName);
+
+ if (!fp.exists()) {
+ // The directory we are listing doesn't exist so we should fail.
+ throw new FileNotFoundException();
+ }
+
+ JSONArray entries = new JSONArray();
+
+ if (fp.isDirectory()) {
+ File[] files = fp.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].canRead()) {
+ entries.put(getEntry(files[i]));
+ }
+ }
+ }
+
+ return entries;
+ }
+
+ /**
+ * A setup method that handles the move/copy of files/directories
+ *
+ * @param fileName to be copied/moved
+ * @param newParent is the location where the file will be copied/moved to
+ * @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 fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ String newFileName = FileHelper.getRealPath(fileName, cordova);
+ newParent = FileHelper.getRealPath(newParent, cordova);
+
+ // Check for invalid file name
+ if (newName != null && newName.contains(":")) {
+ throw new EncodingException("Bad file name");
+ }
+
+ File source = new File(newFileName);
+
+ if (!source.exists()) {
+ // The file/directory we are copying doesn't exist so we should fail.
+ throw new FileNotFoundException("The source does not exist");
+ }
+
+ File destinationDir = new File(newParent);
+ if (!destinationDir.exists()) {
+ // The destination does not exist so we should fail.
+ throw new FileNotFoundException("The source does not exist");
+ }
+
+ // Figure out where we should be copying to
+ File destination = createDestination(newName, source, destinationDir);
+
+ //Log.d(LOG_TAG, "Source: " + source.getAbsolutePath());
+ //Log.d(LOG_TAG, "Destin: " + destination.getAbsolutePath());
+
+ // Check to see if source and destination are the same file
+ if (source.getAbsolutePath().equals(destination.getAbsolutePath())) {
+ throw new InvalidModificationException("Can't copy a file onto itself");
+ }
+
+ if (source.isDirectory()) {
+ if (move) {
+ return moveDirectory(source, destination);
+ } else {
+ return copyDirectory(source, destination);
+ }
+ } else {
+ if (move) {
+ JSONObject newFileEntry = moveFile(source, destination);
+
+ // If we've moved a file given its content URI, we need to clean up.
+ if (fileName.startsWith("content://")) {
+ notifyDelete(fileName);
+ }
+
+ return newFileEntry;
+ } else {
+ return copyFile(source, destination);
+ }
+ }
+ }
+
+ /**
+ * Creates the destination File object based on name passed in
+ *
+ * @param newName for the file directory to be called, if null use existing file name
+ * @param fp represents the source file
+ * @param destination represents the destination file
+ * @return a File object that represents the destination
+ */
+ private File createDestination(String newName, File fp, File destination) {
+ File destFile = null;
+
+ // I know this looks weird but it is to work around a JSON bug.
+ if ("null".equals(newName) || "".equals(newName)) {
+ newName = null;
+ }
+
+ if (newName != null) {
+ destFile = new File(destination.getAbsolutePath() + File.separator + newName);
+ } else {
+ destFile = new File(destination.getAbsolutePath() + File.separator + fp.getName());
+ }
+ return destFile;
+ }
+
+ /**
+ * Copy a file
+ *
+ * @param srcFile file to be copied
+ * @param destFile destination to be copied to
+ * @return a FileEntry object
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws JSONException
+ */
+ private JSONObject copyFile(File srcFile, File destFile) throws IOException, InvalidModificationException, JSONException {
+ // Renaming a file to an existing directory should fail
+ if (destFile.exists() && destFile.isDirectory()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ copyAction(srcFile, destFile);
+
+ return getEntry(destFile);
+ }
+
+ /**
+ * Moved this code into it's own method so moveTo could use it when the move is across file systems
+ */
+ private void copyAction(File srcFile, File destFile)
+ throws FileNotFoundException, IOException {
+ FileInputStream istream = new FileInputStream(srcFile);
+ FileOutputStream ostream = new FileOutputStream(destFile);
+ FileChannel input = istream.getChannel();
+ FileChannel output = ostream.getChannel();
+
+ try {
+ input.transferTo(0, input.size(), output);
+ } finally {
+ istream.close();
+ ostream.close();
+ input.close();
+ output.close();
+ }
+ }
+
+ /**
+ * Copy a directory
+ *
+ * @param srcDir directory to be copied
+ * @param destinationDir destination to be copied to
+ * @return a DirectoryEntry object
+ * @throws JSONException
+ * @throws IOException
+ * @throws NoModificationAllowedException
+ * @throws InvalidModificationException
+ */
+ private JSONObject copyDirectory(File srcDir, File destinationDir) throws JSONException, IOException, NoModificationAllowedException, InvalidModificationException {
+ // Renaming a file to an existing directory should fail
+ if (destinationDir.exists() && destinationDir.isFile()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ // Check to make sure we are not copying the directory into itself
+ if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) {
+ throw new InvalidModificationException("Can't copy itself into itself");
+ }
+
+ // See if the destination directory exists. If not create it.
+ if (!destinationDir.exists()) {
+ if (!destinationDir.mkdir()) {
+ // If we can't create the directory then fail
+ throw new NoModificationAllowedException("Couldn't create the destination directory");
+ }
+ }
+
+
+ for (File file : srcDir.listFiles()) {
+ File destination = new File(destinationDir.getAbsoluteFile() + File.separator + file.getName());
+ if (file.isDirectory()) {
+ copyDirectory(file, destination);
+ } else {
+ copyFile(file, destination);
+ }
+ }
+
+ return getEntry(destinationDir);
+ }
+
+ /**
+ * Check to see if the user attempted to copy an entry into its parent without changing its name,
+ * or attempted to copy a directory into a directory that it contains directly or indirectly.
+ *
+ * @param srcDir
+ * @param destinationDir
+ * @return
+ */
+ private boolean isCopyOnItself(String src, String dest) {
+
+ // This weird test is to determine if we are copying or moving a directory into itself.
+ // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but
+ // Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR
+ if (dest.startsWith(src) && dest.indexOf(File.separator, src.length() - 1) != -1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Move a file
+ *
+ * @param srcFile file to be copied
+ * @param destFile destination to be copied to
+ * @return a FileEntry object
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws JSONException
+ */
+ private JSONObject moveFile(File srcFile, File destFile) throws IOException, JSONException, InvalidModificationException {
+ // Renaming a file to an existing directory should fail
+ if (destFile.exists() && destFile.isDirectory()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ // Try to rename the file
+ if (!srcFile.renameTo(destFile)) {
+ // Trying to rename the file failed. Possibly because we moved across file system on the device.
+ // Now we have to do things the hard way
+ // 1) Copy all the old file
+ // 2) delete the src file
+ copyAction(srcFile, destFile);
+ if (destFile.exists()) {
+ srcFile.delete();
+ } else {
+ throw new IOException("moved failed");
+ }
+ }
+
+ return getEntry(destFile);
+ }
+
+ /**
+ * Move a directory
+ *
+ * @param srcDir directory to be copied
+ * @param destinationDir destination to be copied to
+ * @return a DirectoryEntry object
+ * @throws JSONException
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws NoModificationAllowedException
+ * @throws FileExistsException
+ */
+ private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException {
+ // Renaming a file to an existing directory should fail
+ if (destinationDir.exists() && destinationDir.isFile()) {
+ throw new InvalidModificationException("Can't rename a file to a directory");
+ }
+
+ // Check to make sure we are not copying the directory into itself
+ if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) {
+ throw new InvalidModificationException("Can't move itself into itself");
+ }
+
+ // If the destination directory already exists and is empty then delete it. This is according to spec.
+ if (destinationDir.exists()) {
+ if (destinationDir.list().length > 0) {
+ throw new InvalidModificationException("directory is not empty");
+ }
+ }
+
+ // Try to rename the directory
+ if (!srcDir.renameTo(destinationDir)) {
+ // Trying to rename the directory failed. Possibly because we moved across file system on the device.
+ // Now we have to do things the hard way
+ // 1) Copy all the old files
+ // 2) delete the src directory
+ copyDirectory(srcDir, destinationDir);
+ if (destinationDir.exists()) {
+ removeDirRecursively(srcDir);
+ } else {
+ throw new IOException("moved failed");
+ }
+ }
+
+ return getEntry(destinationDir);
+ }
+
+ /**
+ * 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.
+ *
+ * @param filePath the directory to be removed
+ * @return a boolean representing success of failure
+ * @throws FileExistsException
+ */
+ private boolean removeRecursively(String filePath) throws FileExistsException {
+ File fp = createFileObject(filePath);
+
+ // You can't delete the root directory.
+ if (atRootDirectory(filePath)) {
+ return false;
+ }
+
+ return removeDirRecursively(fp);
+ }
+
+ /**
+ * Loops through a directory deleting all the files.
+ *
+ * @param directory to be removed
+ * @return a boolean representing success of failure
+ * @throws FileExistsException
+ */
+ private boolean removeDirRecursively(File directory) throws FileExistsException {
+ if (directory.isDirectory()) {
+ for (File file : directory.listFiles()) {
+ removeDirRecursively(file);
+ }
+ }
+
+ if (!directory.delete()) {
+ throw new FileExistsException("could not delete: " + directory.getName());
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param filePath file or directory to be removed
+ * @return a boolean representing success of failure
+ * @throws NoModificationAllowedException
+ * @throws InvalidModificationException
+ */
+ private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException {
+ File fp = createFileObject(filePath);
+
+ // You can't delete the root directory.
+ if (atRootDirectory(filePath)) {
+ throw new NoModificationAllowedException("You can't delete the root directory");
+ }
+
+ // You can't delete a directory that is not empty
+ if (fp.isDirectory() && fp.list().length > 0) {
+ throw new InvalidModificationException("You can't delete a directory that is not empty.");
+ }
+
+ return fp.delete();
+ }
+
+ /**
+ * Creates or looks up a file.
+ *
+ * @param dirPath base directory
+ * @param fileName 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 dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ boolean create = false;
+ boolean exclusive = false;
+ if (options != null) {
+ create = options.optBoolean("create");
+ if (create) {
+ exclusive = options.optBoolean("exclusive");
+ }
+ }
+
+ // Check for a ":" character in the file to line up with BB and iOS
+ if (fileName.contains(":")) {
+ throw new EncodingException("This file has a : in it's name");
+ }
+
+ File fp = createFileObject(dirPath, fileName);
+
+ if (create) {
+ if (exclusive && fp.exists()) {
+ throw new FileExistsException("create/exclusive fails");
+ }
+ if (directory) {
+ fp.mkdir();
+ } else {
+ fp.createNewFile();
+ }
+ if (!fp.exists()) {
+ throw new FileExistsException("create fails");
+ }
+ }
+ else {
+ if (!fp.exists()) {
+ throw new FileNotFoundException("path does not exist");
+ }
+ if (directory) {
+ if (fp.isFile()) {
+ throw new TypeMismatchException("path doesn't exist or is file");
+ }
+ } else {
+ if (fp.isDirectory()) {
+ throw new TypeMismatchException("path doesn't exist or is directory");
+ }
+ }
+ }
+
+ // Return the directory
+ return getEntry(fp);
+ }
+
+ /**
+ * If the path starts with a '/' just return that file object. If not construct the file
+ * object from the path passed in and the file name.
+ *
+ * @param dirPath root directory
+ * @param fileName new file name
+ * @return
+ */
+ private File createFileObject(String dirPath, String fileName) {
+ File fp = null;
+ if (fileName.startsWith("/")) {
+ fp = new File(fileName);
+ } else {
+ dirPath = FileHelper.getRealPath(dirPath, cordova);
+ fp = new File(dirPath + File.separator + fileName);
+ }
+ return fp;
+ }
+
+ /**
+ * Look up the parent DirectoryEntry containing this Entry.
+ * If this Entry is the root of its filesystem, its parent is itself.
+ *
+ * @param filePath
+ * @return
+ * @throws JSONException
+ */
+ private JSONObject getParent(String filePath) throws JSONException {
+ filePath = FileHelper.getRealPath(filePath, cordova);
+
+ if (atRootDirectory(filePath)) {
+ return getEntry(filePath);
+ }
+ return getEntry(new File(filePath).getParent());
+ }
+
+ /**
+ * Checks to see if we are at the root directory. Useful since we are
+ * not allow to delete this directory.
+ *
+ * @param filePath to directory
+ * @return true if we are at the root, false otherwise.
+ */
+ private boolean atRootDirectory(String filePath) {
+ filePath = FileHelper.getRealPath(filePath, cordova);
+
+ if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") ||
+ filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) ||
+ filePath.equals("/data/data/" + cordova.getActivity().getPackageName())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Create a File object from the passed in path
+ *
+ * @param filePath
+ * @return
+ */
+ private File createFileObject(String filePath) {
+ filePath = FileHelper.getRealPath(filePath, cordova);
+
+ File file = new File(filePath);
+ return file;
+ }
+
+ /**
+ * Look up metadata about this entry.
+ *
+ * @param filePath to entry
+ * @return a long
+ * @throws FileNotFoundException
+ */
+ private long getMetadata(String filePath) throws FileNotFoundException {
+ File file = createFileObject(filePath);
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("Failed to find file in getMetadata");
+ }
+
+ return file.lastModified();
+ }
+
+ /**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ *
+ * @param filePath to entry
+ * @return returns a JSONObject represent a W3C File object
+ * @throws FileNotFoundException
+ * @throws JSONException
+ */
+ private JSONObject getFileMetadata(String filePath) throws FileNotFoundException, JSONException {
+ File file = createFileObject(filePath);
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("File: " + filePath + " does not exist.");
+ }
+
+ JSONObject metadata = new JSONObject();
+ metadata.put("size", file.length());
+ metadata.put("type", FileHelper.getMimeType(filePath, cordova));
+ metadata.put("name", file.getName());
+ metadata.put("fullPath", filePath);
+ metadata.put("lastModifiedDate", file.lastModified());
+
+ return metadata;
+ }
+
+ /**
+ * Requests a filesystem in which to store application data.
+ *
+ * @param type of file system requested
+ * @return a JSONObject representing the file system
+ * @throws IOException
+ * @throws JSONException
+ */
+ private JSONObject requestFileSystem(int type) throws IOException, JSONException {
+ JSONObject fs = new JSONObject();
+ if (type == TEMPORARY) {
+ File fp;
+ fs.put("name", "temporary");
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
+ "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/");
+ // Create the cache dir if it doesn't exist.
+ fp.mkdirs();
+ fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() +
+ "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/"));
+ } else {
+ fp = new File("/data/data/" + cordova.getActivity().getPackageName() + "/cache/");
+ // Create the cache dir if it doesn't exist.
+ fp.mkdirs();
+ fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName() + "/cache/"));
+ }
+ }
+ else if (type == PERSISTENT) {
+ fs.put("name", "persistent");
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ fs.put("root", getEntry(Environment.getExternalStorageDirectory()));
+ } else {
+ fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName()));
+ }
+ }
+ else {
+ throw new IOException("No filesystem of type requested");
+ }
+
+ return fs;
+ }
+
+ /**
+ * Returns a JSON object representing the given File.
+ *
+ * @param file the File to convert
+ * @return a JSON representation of the given File
+ * @throws JSONException
+ */
+ public static JSONObject getEntry(File file) throws JSONException {
+ JSONObject entry = new JSONObject();
+
+ entry.put("isFile", file.isFile());
+ entry.put("isDirectory", file.isDirectory());
+ entry.put("name", file.getName());
+ entry.put("fullPath", "file://" + file.getAbsolutePath());
+ // The file system can't be specified, as it would lead to an infinite loop.
+ // entry.put("filesystem", null);
+
+ return entry;
+ }
+
+ /**
+ * Returns a JSON Object representing a directory on the device's file system
+ *
+ * @param path to the directory
+ * @return
+ * @throws JSONException
+ */
+ private JSONObject getEntry(String path) throws JSONException {
+ return getEntry(new File(path));
+ }
+
+ /**
+ * Read the contents of a file.
+ * This is done in a background thread; the result is sent to the callback.
+ *
+ * @param filename The name of the file.
+ * @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 filename, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) {
+ this.cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ byte[] bytes = readAsBinaryHelper(filename, start, end);
+
+ PluginResult result;
+ switch (resultType) {
+ case PluginResult.MESSAGE_TYPE_STRING:
+ result = new PluginResult(PluginResult.Status.OK, new String(bytes, encoding));
+ break;
+ case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+ result = new PluginResult(PluginResult.Status.OK, bytes);
+ break;
+ case PluginResult.MESSAGE_TYPE_BINARYSTRING:
+ result = new PluginResult(PluginResult.Status.OK, bytes, true);
+ break;
+ default: // Base64.
+ String contentType = FileHelper.getMimeType(filename, cordova);
+ byte[] base64 = Base64.encode(bytes, Base64.NO_WRAP);
+ String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
+ result = new PluginResult(PluginResult.Status.OK, s);
+ }
+
+ callbackContext.sendPluginResult(result);
+ } 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));
+ }
+ }
+ });
+ }
+
+ /**
+ * Read the contents of a file as binary.
+ * This is done synchronously; the result is returned.
+ *
+ * @param filename The name of the file.
+ * @param start Start position in the file.
+ * @param end End position to stop at (exclusive).
+ * @return Contents of the file as a byte[].
+ * @throws IOException
+ */
+ private byte[] readAsBinaryHelper(String filename, int start, int end) throws IOException {
+ int numBytesToRead = end - start;
+ byte[] bytes = new byte[numBytesToRead];
+ InputStream inputStream = FileHelper.getInputStreamFromUriString(filename, cordova);
+ int numBytesRead = 0;
+
+ if (start > 0) {
+ inputStream.skip(start);
+ }
+
+ while (numBytesToRead > 0 && (numBytesRead = inputStream.read(bytes, numBytesRead, numBytesToRead)) >= 0) {
+ numBytesToRead -= numBytesRead;
+ }
+
+ return bytes;
+ }
+
+ /**
+ * Write contents of file.
+ *
+ * @param filename The name of the 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
+ * @throws FileNotFoundException, IOException
+ * @throws NoModificationAllowedException
+ */
+ /**/
+ public long write(String filename, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ if (filename.startsWith("content://")) {
+ throw new NoModificationAllowedException("Couldn't write to file given its content URI");
+ }
+
+ filename = FileHelper.getRealPath(filename, cordova);
+
+ boolean append = false;
+ if (offset > 0) {
+ this.truncateFile(filename, offset);
+ append = true;
+ }
+
+ byte[] rawData;
+ if (isBinary) {
+ rawData = Base64.decode(data, Base64.DEFAULT);
+ } else {
+ rawData = data.getBytes();
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(rawData);
+ try
+ {
+ FileOutputStream out = new FileOutputStream(filename, append);
+ byte buff[] = new byte[rawData.length];
+ in.read(buff, 0, buff.length);
+ out.write(buff, 0, rawData.length);
+ out.flush();
+ out.close();
+ }
+ catch (NullPointerException e)
+ {
+ // This is a bug in the Android implementation of the Java Stack
+ NoModificationAllowedException realException = new NoModificationAllowedException(filename);
+ throw realException;
+ }
+
+ return rawData.length;
+ }
+
+ /**
+ * Truncate the file to size
+ *
+ * @param filename
+ * @param size
+ * @throws FileNotFoundException, IOException
+ * @throws NoModificationAllowedException
+ */
+ private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ if (filename.startsWith("content://")) {
+ throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
+ }
+
+ filename = FileHelper.getRealPath(filename, cordova);
+
+ RandomAccessFile raf = new RandomAccessFile(filename, "rw");
+ try {
+ if (raf.length() >= size) {
+ FileChannel channel = raf.getChannel();
+ channel.truncate(size);
+ return size;
+ }
+
+ return raf.length();
+ } finally {
+ raf.close();
+ }
+ }
+}
diff --git a/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java b/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java
new file mode 100644
index 00000000..8f6bec59
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/InvalidModificationException.java
@@ -0,0 +1,30 @@
+/*
+ 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;
+
+@SuppressWarnings("serial")
+public class InvalidModificationException extends Exception {
+
+ public InvalidModificationException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java b/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java
new file mode 100644
index 00000000..627eafb5
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/NoModificationAllowedException.java
@@ -0,0 +1,29 @@
+/*
+ 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;
+
+@SuppressWarnings("serial")
+public class NoModificationAllowedException extends Exception {
+
+ public NoModificationAllowedException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java b/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java
new file mode 100644
index 00000000..1315f9a9
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/android/TypeMismatchException.java
@@ -0,0 +1,30 @@
+/*
+ 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;
+
+@SuppressWarnings("serial")
+public class TypeMismatchException extends Exception {
+
+ public TypeMismatchException(String message) {
+ super(message);
+ }
+
+}
diff --git a/plugins/org.apache.cordova.file/src/blackberry10/index.js b/plugins/org.apache.cordova.file/src/blackberry10/index.js
new file mode 100644
index 00000000..914d9663
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/blackberry10/index.js
@@ -0,0 +1,10 @@
+module.exports = {
+ setSandbox : function (success, fail, args, env) {
+ require("lib/webview").setSandbox(JSON.parse(decodeURIComponent(args[0])));
+ new PluginResult(args, env).noResult(false);
+ },
+
+ isSandboxed : function (success, fail, args, env) {
+ new PluginResult(args, env).ok(require("lib/webview").getSandbox() === "1");
+ }
+};
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVFile.h b/plugins/org.apache.cordova.file/src/ios/CDVFile.h
new file mode 100644
index 00000000..54d25f08
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVFile.h
@@ -0,0 +1,107 @@
+/*
+ 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import <Cordova/CDVPlugin.h>
+
+enum CDVFileError {
+ NO_ERROR = 0,
+ NOT_FOUND_ERR = 1,
+ SECURITY_ERR = 2,
+ ABORT_ERR = 3,
+ NOT_READABLE_ERR = 4,
+ ENCODING_ERR = 5,
+ NO_MODIFICATION_ALLOWED_ERR = 6,
+ INVALID_STATE_ERR = 7,
+ SYNTAX_ERR = 8,
+ INVALID_MODIFICATION_ERR = 9,
+ QUOTA_EXCEEDED_ERR = 10,
+ TYPE_MISMATCH_ERR = 11,
+ PATH_EXISTS_ERR = 12
+};
+typedef int CDVFileError;
+
+enum CDVFileSystemType {
+ TEMPORARY = 0,
+ PERSISTENT = 1
+};
+typedef int CDVFileSystemType;
+
+extern NSString* const kCDVAssetsLibraryPrefix;
+
+@interface CDVFile : CDVPlugin {
+ NSString* appDocsPath;
+ NSString* appLibraryPath;
+ NSString* appTempPath;
+ NSString* persistentPath;
+ NSString* temporaryPath;
+
+ BOOL userHasAllowed;
+}
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
+- (NSString*)getAppPath:(NSString*)pathFragment;
+// -(NSString*) getFullPath: (NSString*)pathFragment;
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
+- (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir;
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
+- (void)getDirectory:(CDVInvokedUrlCommand*)command;
+- (void)getFile:(CDVInvokedUrlCommand*)command;
+- (void)getParent:(CDVInvokedUrlCommand*)command;
+- (void)getMetadata:(CDVInvokedUrlCommand*)command;
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
+- (void)remove:(CDVInvokedUrlCommand*)command;
+- (CDVPluginResult*)doRemove:(NSString*)fullPath;
+- (void)copyTo:(CDVInvokedUrlCommand*)command;
+- (void)moveTo:(CDVInvokedUrlCommand*)command;
+- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest;
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
+// - (void) toURI:(CDVInvokedUrlCommand*)command;
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
+- (void)readEntries:(CDVInvokedUrlCommand*)command;
+
+- (void)readAsText:(CDVInvokedUrlCommand*)command;
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+- (void)write:(CDVInvokedUrlCommand*)command;
+- (void)testFileExists:(CDVInvokedUrlCommand*)command;
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
+// - (void) createDirectory:(CDVInvokedUrlCommand*)command;
+// - (void) deleteDirectory:(CDVInvokedUrlCommand*)command;
+// - (void) deleteFile:(CDVInvokedUrlCommand*)command;
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
+- (void)truncate:(CDVInvokedUrlCommand*)command;
+
+// - (BOOL) fileExists:(NSString*)fileName;
+// - (BOOL) directoryExists:(NSString*)dirName;
+- (void)writeToFile:(NSString*)fileName withData:(NSData*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId;
+- (void)writeToFile:(NSString*)fileName withString:(NSString*)data encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId;
+- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos;
+
+@property (nonatomic, strong) NSString* appDocsPath;
+@property (nonatomic, strong) NSString* appLibraryPath;
+@property (nonatomic, strong) NSString* appTempPath;
+@property (nonatomic, strong) NSString* persistentPath;
+@property (nonatomic, strong) NSString* temporaryPath;
+@property BOOL userHasAllowed;
+
+@end
+
+#define kW3FileTemporary @"temporary"
+#define kW3FilePersistent @"persistent"
diff --git a/plugins/org.apache.cordova.file/src/ios/CDVFile.m b/plugins/org.apache.cordova.file/src/ios/CDVFile.m
new file mode 100644
index 00000000..6b2602d3
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/ios/CDVFile.m
@@ -0,0 +1,1417 @@
+/*
+ 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.
+ */
+
+#import "CDVFile.h"
+#import <Cordova/CDV.h>
+#import <AssetsLibrary/ALAsset.h>
+#import <AssetsLibrary/ALAssetRepresentation.h>
+#import <AssetsLibrary/ALAssetsLibrary.h>
+#import <MobileCoreServices/MobileCoreServices.h>
+#import <sys/xattr.h>
+
+extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import));
+
+#ifndef __IPHONE_5_1
+ NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey";
+#endif
+
+NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
+
+@implementation CDVFile
+
+@synthesize appDocsPath, appLibraryPath, appTempPath, persistentPath, temporaryPath, userHasAllowed;
+
+- (id)initWithWebView:(UIWebView*)theWebView
+{
+ self = (CDVFile*)[super initWithWebView:theWebView];
+ if (self) {
+ // get the documents directory path
+ NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ self.appDocsPath = [paths objectAtIndex:0];
+
+ paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ self.appLibraryPath = [paths objectAtIndex:0];
+
+ self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory()
+
+ self.persistentPath = [NSString stringWithFormat:@"/%@", [self.appDocsPath lastPathComponent]];
+ self.temporaryPath = [NSString stringWithFormat:@"/%@", [self.appTempPath lastPathComponent]];
+ // NSLog(@"docs: %@ - temp: %@", self.appDocsPath, self.appTempPath);
+ }
+
+ return self;
+}
+
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath
+{
+ NSFileManager* fMgr = [[NSFileManager alloc] init];
+
+ NSError* __autoreleasing pError = nil;
+
+ NSDictionary* pDict = [fMgr attributesOfFileSystemForPath:appPath error:&pError];
+ NSNumber* pNumAvail = (NSNumber*)[pDict objectForKey:NSFileSystemFreeSize];
+
+ return pNumAvail;
+}
+
+// figure out if the pathFragment represents a persistent of temporary directory and return the full application path.
+// returns nil if path is not persistent or temporary
+- (NSString*)getAppPath:(NSString*)pathFragment
+{
+ NSString* appPath = nil;
+ NSRange rangeP = [pathFragment rangeOfString:self.persistentPath];
+ NSRange rangeT = [pathFragment rangeOfString:self.temporaryPath];
+
+ if ((rangeP.location != NSNotFound) && (rangeT.location != NSNotFound)) {
+ // we found both in the path, return whichever one is first
+ if (rangeP.length < rangeT.length) {
+ appPath = self.appDocsPath;
+ } else {
+ appPath = self.appTempPath;
+ }
+ } else if (rangeP.location != NSNotFound) {
+ appPath = self.appDocsPath;
+ } else if (rangeT.location != NSNotFound) {
+ appPath = self.appTempPath;
+ }
+ return appPath;
+}
+
+/* get the full path to this resource
+ * IN
+ * NSString* pathFragment - full Path from File or Entry object (includes system path info)
+ * OUT
+ * NSString* fullPath - full iOS path to this resource, nil if not found
+ */
+
+/* Was here in order to NOT have to return full path, but W3C synchronous DirectoryEntry.toURI() killed that idea since I can't call into iOS to
+ * resolve full URI. Leaving this code here in case W3C spec changes.
+-(NSString*) getFullPath: (NSString*)pathFragment
+{
+ return pathFragment;
+ NSString* fullPath = nil;
+ NSString *appPath = [ self getAppPath: pathFragment];
+ if (appPath){
+
+ // remove last component from appPath
+ NSRange range = [appPath rangeOfString:@"/" options: NSBackwardsSearch];
+ NSString* newPath = [appPath substringToIndex:range.location];
+ // add pathFragment to get test Path
+ fullPath = [newPath stringByAppendingPathComponent:pathFragment];
+ }
+ return fullPath;
+} */
+
+/* Request the File System info
+ *
+ * IN:
+ * arguments[0] - type (number as string)
+ * TEMPORARY = 0, PERSISTENT = 1;
+ * arguments[1] - size
+ *
+ * OUT:
+ * Dictionary representing FileSystem object
+ * name - the human readable directory name
+ * root = DirectoryEntry object
+ * bool isDirectory
+ * bool isFile
+ * string name
+ * string fullPath
+ * fileSystem = FileSystem object - !! ignored because creates circular reference !!
+ */
+
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command
+{
+ NSArray* arguments = command.arguments;
+
+ // arguments
+ NSString* strType = [arguments objectAtIndex:0];
+ unsigned long long size = [[arguments objectAtIndex:1] longLongValue];
+
+ int type = [strType intValue];
+ CDVPluginResult* result = nil;
+
+ if (type > 1) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"iOS only supports TEMPORARY and PERSISTENT file systems");
+ } else {
+ // NSString* fullPath = [NSString stringWithFormat:@"/%@", (type == 0 ? [self.appTempPath lastPathComponent] : [self.appDocsPath lastPathComponent])];
+ NSString* fullPath = (type == 0 ? self.appTempPath : self.appDocsPath);
+ // check for avail space for size request
+ NSNumber* pNumAvail = [self checkFreeDiskSpace:fullPath];
+ // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]);
+ if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR];
+ } else {
+ NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2];
+ [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary : kW3FilePersistent) forKey:@"name"];
+ NSDictionary* dirEntry = [self getDirectoryEntry:fullPath isDirectory:YES];
+ [fileSystem setObject:dirEntry forKey:@"root"];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* Creates a dictionary representing an Entry Object
+ *
+ * IN:
+ * NSString* fullPath of the entry
+ * FileSystem type
+ * BOOL isDirectory - YES if this is a directory, NO if is a file
+ * OUT:
+ * NSDictionary*
+ Entry object
+ * bool as NSNumber isDirectory
+ * bool as NSNumber isFile
+ * NSString* name - last part of path
+ * NSString* fullPath
+ * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!!
+ */
+- (NSDictionary*)getDirectoryEntry:(NSString*)fullPath isDirectory:(BOOL)isDir
+{
+ NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:4];
+ NSString* lastPart = [fullPath lastPathComponent];
+
+ [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
+ [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
+ // NSURL* fileUrl = [NSURL fileURLWithPath:fullPath];
+ // [dirEntry setObject: [fileUrl absoluteString] forKey: @"fullPath"];
+ [dirEntry setObject:fullPath forKey:@"fullPath"];
+ [dirEntry setObject:lastPart forKey:@"name"];
+
+ return dirEntry;
+}
+
+/*
+ * Given a URI determine the File System information associated with it and return an appropriate W3C entry object
+ * IN
+ * NSString* fileURI - currently requires full file URI
+ * OUT
+ * Entry object
+ * bool isDirectory
+ * bool isFile
+ * string name
+ * string fullPath
+ * fileSystem = FileSystem object - !! ignored because creates circular reference FileSystem contains DirectoryEntry which contains FileSystem.....!!
+ */
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* inputUri = [command.arguments objectAtIndex:0];
+
+ // don't know if string is encoded or not so unescape
+ NSString* cleanUri = [inputUri stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ // now escape in order to create URL
+ NSString* strUri = [cleanUri stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ NSURL* testUri = [NSURL URLWithString:strUri];
+ CDVPluginResult* result = nil;
+
+ if (!testUri) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else if ([testUri isFileURL]) {
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSString* path = [testUri path];
+ // NSLog(@"url path: %@", path);
+ BOOL isDir = NO;
+ // see if exists and is file or dir
+ BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
+ if (bExists) {
+ // see if it contains docs path or temp path
+ NSString* foundFullPath = nil;
+ if ([path hasPrefix:self.appDocsPath]) {
+ foundFullPath = self.appDocsPath;
+ } else if ([path hasPrefix:self.appTempPath]) {
+ foundFullPath = self.appTempPath;
+ }
+
+ if (foundFullPath == nil) {
+ // error SECURITY_ERR - not one of the two paths types supported
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SECURITY_ERR];
+ } else {
+ NSDictionary* fileSystem = [self getDirectoryEntry:path isDirectory:isDir];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
+ }
+ } else {
+ // return NOT_FOUND_ERR
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ } else if ([strUri hasPrefix:@"assets-library://"]) {
+ NSDictionary* fileSystem = [self getDirectoryEntry:strUri isDirectory:NO];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ }
+
+ if (result != nil) {
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }
+}
+
+/* Part of DirectoryEntry interface, creates or returns the specified directory
+ * IN:
+ * NSString* fullPath - full path for this directory
+ * NSString* path - directory to be created/returned; may be full path or relative path
+ * NSDictionary* - Flags object
+ * boolean as NSNumber create -
+ * if create is true and directory does not exist, create dir and return directory entry
+ * if create is true and exclusive is true and directory does exist, return error
+ * if create is false and directory does not exist, return error
+ * if create is false and the path represents a file, return error
+ * boolean as NSNumber exclusive - used in conjunction with create
+ * if exclusive is true and create is true - specifies failure if directory already exists
+ *
+ *
+ */
+- (void)getDirectory:(CDVInvokedUrlCommand*)command
+{
+ NSMutableArray* arguments = [NSMutableArray arrayWithArray:command.arguments];
+ NSMutableDictionary* options = nil;
+
+ if ([arguments count] >= 3) {
+ options = [arguments objectAtIndex:2 withDefault:nil];
+ }
+ // add getDir to options and call getFile()
+ if (options != nil) {
+ options = [NSMutableDictionary dictionaryWithDictionary:options];
+ } else {
+ options = [NSMutableDictionary dictionaryWithCapacity:1];
+ }
+ [options setObject:[NSNumber numberWithInt:1] forKey:@"getDir"];
+ if ([arguments count] >= 3) {
+ [arguments replaceObjectAtIndex:2 withObject:options];
+ } else {
+ [arguments addObject:options];
+ }
+ CDVInvokedUrlCommand* subCommand =
+ [[CDVInvokedUrlCommand alloc] initWithArguments:arguments
+ callbackId:command.callbackId
+ className:command.className
+ methodName:command.methodName];
+
+ [self getFile:subCommand];
+}
+
+/* Part of DirectoryEntry interface, creates or returns the specified file
+ * IN:
+ * NSString* fullPath - full path for this file
+ * NSString* path - file to be created/returned; may be full path or relative path
+ * NSDictionary* - Flags object
+ * boolean as NSNumber create -
+ * if create is true and file does not exist, create file and return File entry
+ * if create is true and exclusive is true and file does exist, return error
+ * if create is false and file does not exist, return error
+ * if create is false and the path represents a directory, return error
+ * boolean as NSNumber exclusive - used in conjunction with create
+ * if exclusive is true and create is true - specifies failure if file already exists
+ *
+ *
+ */
+- (void)getFile:(CDVInvokedUrlCommand*)command
+{
+ // arguments are URL encoded
+ NSString* fullPath = [command.arguments objectAtIndex:0];
+ NSString* requestedPath = [command.arguments objectAtIndex:1];
+ NSDictionary* options = [command.arguments objectAtIndex:2 withDefault:nil];
+
+ // return unsupported result for assets-library URLs
+ if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVPluginResult* result = nil;
+ BOOL bDirRequest = NO;
+ BOOL create = NO;
+ BOOL exclusive = NO;
+ int errorCode = 0; // !!! risky - no error code currently defined for 0
+
+ if ([options valueForKeyIsNumber:@"create"]) {
+ create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
+ }
+ if ([options valueForKeyIsNumber:@"exclusive"]) {
+ exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
+ }
+
+ if ([options valueForKeyIsNumber:@"getDir"]) {
+ // this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
+ bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
+ }
+ // see if the requested path has invalid characters - should we be checking for more than just ":"?
+ if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
+ errorCode = ENCODING_ERR;
+ } else {
+ // was full or relative path provided?
+ NSRange range = [requestedPath rangeOfString:fullPath];
+ BOOL bIsFullPath = range.location != NSNotFound;
+
+ NSString* reqFullPath = nil;
+
+ if (!bIsFullPath) {
+ reqFullPath = [fullPath stringByAppendingPathComponent:requestedPath];
+ } else {
+ reqFullPath = requestedPath;
+ }
+
+ // NSLog(@"reqFullPath = %@", reqFullPath);
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:reqFullPath isDirectory:&bIsDir];
+ if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
+ // path exists and is of requested type - return TYPE_MISMATCH_ERR
+ errorCode = TYPE_MISMATCH_ERR;
+ } else if (!bExists && (create == NO)) {
+ // path does not exist and create is false - return NOT_FOUND_ERR
+ errorCode = NOT_FOUND_ERR;
+ } else if (bExists && (create == YES) && (exclusive == YES)) {
+ // file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
+ errorCode = PATH_EXISTS_ERR;
+ } else {
+ // if bExists and create == YES - just return data
+ // if bExists and create == NO - just return data
+ // if !bExists and create == YES - create and return data
+ BOOL bSuccess = YES;
+ NSError __autoreleasing* pError = nil;
+ if (!bExists && (create == YES)) {
+ if (bDirRequest) {
+ // create the dir
+ bSuccess = [fileMgr createDirectoryAtPath:reqFullPath withIntermediateDirectories:NO attributes:nil error:&pError];
+ } else {
+ // create the empty file
+ bSuccess = [fileMgr createFileAtPath:reqFullPath contents:nil attributes:nil];
+ }
+ }
+ if (!bSuccess) {
+ errorCode = ABORT_ERR;
+ if (pError) {
+ NSLog(@"error creating directory: %@", [pError localizedDescription]);
+ }
+ } else {
+ // NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
+ // file existed or was created
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:reqFullPath isDirectory:bDirRequest]];
+ }
+ } // are all possible conditions met?
+ }
+
+ if (errorCode > 0) {
+ // create error callback
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * Look up the parent Entry containing this Entry.
+ * If this Entry is the root of its filesystem, its parent is itself.
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * NSMutableDictionary* options
+ * empty
+ */
+- (void)getParent:(CDVInvokedUrlCommand*)command
+{
+ // arguments are URL encoded
+ NSString* fullPath = [command.arguments objectAtIndex:0];
+
+ // we don't (yet?) support getting the parent of an asset
+ if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVPluginResult* result = nil;
+ NSString* newPath = nil;
+
+ if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) {
+ // return self
+ newPath = fullPath;
+ } else {
+ // since this call is made from an existing Entry object - the parent should already exist so no additional error checking
+ // remove last component and return Entry
+ NSRange range = [fullPath rangeOfString:@"/" options:NSBackwardsSearch];
+ newPath = [fullPath substringToIndex:range.location];
+ }
+
+ if (newPath) {
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:newPath isDirectory:&bIsDir];
+ if (bExists) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newPath isDirectory:bIsDir]];
+ }
+ }
+ if (!result) {
+ // invalid path or file does not exist
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * get MetaData of entry
+ * Currently MetaData only includes modificationTime.
+ */
+- (void)getMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command.arguments objectAtIndex:0];
+ __block CDVPluginResult* result = nil;
+
+ if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ // In this case, we need to use an asynchronous method to retrieve the file.
+ // Because of this, we can't just assign to `result` and send it at the end of the method.
+ // Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Retrieve the metadata and send it off.
+ NSDate* date = [asset valueForProperty:ALAssetPropertyDate];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[date timeIntervalSince1970] * 1000];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ } else {
+ // We couldn't find the asset. Send the appropriate error.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }
+ };
+ // TODO(maxw): Consider making this a class variable since it's the same every time.
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock];
+ return;
+ }
+
+ NSString* testPath = argPath; // [self getFullPath: argPath];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* __autoreleasing error = nil;
+
+ NSDictionary* fileAttribs = [fileMgr attributesOfItemAtPath:testPath error:&error];
+
+ if (fileAttribs) {
+ NSDate* modDate = [fileAttribs fileModificationDate];
+ if (modDate) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[modDate timeIntervalSince1970] * 1000];
+ }
+ } else {
+ // didn't get fileAttribs
+ CDVFileError errorCode = ABORT_ERR;
+ NSLog(@"error getting metadata: %@", [error localizedDescription]);
+ if ([error code] == NSFileNoSuchFileError) {
+ errorCode = NOT_FOUND_ERR;
+ }
+ // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
+ }
+ if (!result) {
+ // invalid path or file does not exist
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * set MetaData of entry
+ * Currently we only support "com.apple.MobileBackup" (boolean)
+ */
+- (void)setMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* filePath = [command.arguments objectAtIndex:0];
+ NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:nil];
+ CDVPluginResult* result = nil;
+ BOOL ok = NO;
+
+ // setMetadata doesn't make sense for asset library files
+ if (![filePath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ // we only care about this iCloud key for now.
+ // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
+ NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
+ id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
+
+ if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
+ if (IsAtLeastiOSVersion(@"5.1")) {
+ NSURL* url = [NSURL fileURLWithPath:filePath];
+ NSError* __autoreleasing error = nil;
+
+ ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
+ } else { // below 5.1 (deprecated - only really supported in 5.01)
+ u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
+ if (value == 0) { // remove the attribute (allow backup, the default)
+ ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
+ } else { // set the attribute (skip backup)
+ ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
+ }
+ }
+ }
+ }
+
+ if (ok) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* removes the directory or file entry
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ *
+ * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir
+ * returns INVALID_MODIFICATION_ERR if is non-empty dir or asset library file
+ * returns NOT_FOUND_ERR if file or dir is not found
+*/
+- (void)remove:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* fullPath = [command.arguments objectAtIndex:0];
+ CDVPluginResult* result = nil;
+ CDVFileError errorCode = 0; // !! 0 not currently defined
+
+ // return error for assets-library URLs
+ if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ errorCode = INVALID_MODIFICATION_ERR;
+ } else if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) {
+ // error if try to remove top level (documents or tmp) dir
+ errorCode = NO_MODIFICATION_ALLOWED_ERR;
+ } else {
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir = NO;
+ BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir];
+ if (!bExists) {
+ errorCode = NOT_FOUND_ERR;
+ }
+ if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fullPath error:nil] count] != 0)) {
+ // dir is not empty
+ errorCode = INVALID_MODIFICATION_ERR;
+ }
+ }
+ if (errorCode > 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ } else {
+ // perform actual remove
+ result = [self doRemove:fullPath];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* recursively removes the directory
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ *
+ * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir
+ * returns NOT_FOUND_ERR if file or dir is not found
+ */
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* fullPath = [command.arguments objectAtIndex:0];
+
+ // return unsupported result for assets-library URLs
+ if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVPluginResult* result = nil;
+
+ // error if try to remove top level (documents or tmp) dir
+ if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ result = [self doRemove:fullPath];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* remove the file or directory (recursively)
+ * IN:
+ * NSString* fullPath - the full path to the file or directory to be removed
+ * NSString* callbackId
+ * called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
+ */
+
+- (CDVPluginResult*)doRemove:(NSString*)fullPath
+{
+ CDVPluginResult* result = nil;
+ BOOL bSuccess = NO;
+ NSError* __autoreleasing pError = nil;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ @try {
+ bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
+ if (bSuccess) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ // see if we can give a useful error
+ CDVFileError errorCode = ABORT_ERR;
+ NSLog(@"error getting metadata: %@", [pError localizedDescription]);
+ if ([pError code] == NSFileNoSuchFileError) {
+ errorCode = NOT_FOUND_ERR;
+ } else if ([pError code] == NSFileWriteNoPermissionError) {
+ errorCode = NO_MODIFICATION_ALLOWED_ERR;
+ }
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ } @catch(NSException* e) { // NSInvalidArgumentException if path is . or ..
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
+ }
+
+ return result;
+}
+
+- (void)copyTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:YES];
+}
+
+- (void)moveTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:NO];
+}
+
+/**
+ * Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
+ * or attempted to copy a directory into a directory that it contains directly or indirectly.
+ *
+ * IN:
+ * NSString* srcDir
+ * NSString* destinationDir
+ * OUT:
+ * YES copy/ move is allows
+ * NO move is onto itself
+ */
+- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
+{
+ // This weird test is to determine if we are copying or moving a directory into itself.
+ // Copy /Documents/myDir to /Documents/myDir-backup is okay but
+ // Copy /Documents/myDir to /Documents/myDir/backup not okay
+ BOOL copyOK = YES;
+ NSRange range = [dest rangeOfString:src];
+
+ if (range.location != NSNotFound) {
+ NSRange testRange = {range.length - 1, ([dest length] - range.length)};
+ NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
+ if (resultRange.location != NSNotFound) {
+ copyOK = NO;
+ }
+ }
+ return copyOK;
+}
+
+/* Copy/move a file or directory to a new location
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath of entry
+ * 1 - NSString* newName the new name of the entry, defaults to the current name
+ * NSMutableDictionary* options - DirectoryEntry to which to copy the entry
+ * BOOL - bCopy YES if copy, NO if move
+ *
+ */
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy
+{
+ NSArray* arguments = command.arguments;
+
+ // arguments
+ NSString* srcFullPath = [arguments objectAtIndex:0];
+ NSString* destRootPath = [arguments objectAtIndex:1];
+ // optional argument
+ NSString* newName = ([arguments count] > 2) ? [arguments objectAtIndex:2] : [srcFullPath lastPathComponent]; // use last component from appPath if new name not provided
+
+ __block CDVPluginResult* result = nil;
+ CDVFileError errCode = 0; // !! Currently 0 is not defined, use this to signal error !!
+
+ /*NSString* destRootPath = nil;
+ NSString* key = @"fullPath";
+ if([options valueForKeyIsString:key]){
+ destRootPath = [options objectForKey:@"fullPath"];
+ }*/
+
+ if (!destRootPath) {
+ // no destination provided
+ errCode = NOT_FOUND_ERR;
+ } else if ([newName rangeOfString:@":"].location != NSNotFound) {
+ // invalid chars in new name
+ errCode = ENCODING_ERR;
+ } else {
+ NSString* newFullPath = [destRootPath stringByAppendingPathComponent:newName];
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ if ([newFullPath isEqualToString:srcFullPath]) {
+ // source and destination can not be the same
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if ([srcFullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ if (bCopy) {
+ // Copying (as opposed to moving) an assets library file is okay.
+ // In this case, we need to use an asynchronous method to retrieve the file.
+ // Because of this, we can't just assign to `result` and send it at the end of the method.
+ // Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Get the data and try to copy it over.
+ if (![fileMgr fileExistsAtPath:destRootPath]) {
+ // The destination directory doesn't exist.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ } else if ([fileMgr fileExistsAtPath:newFullPath]) {
+ // A file already exists at the destination path.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:PATH_EXISTS_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ // We're good to go! Write the file to the new destination.
+ ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
+ Byte* buffer = (Byte*)malloc([assetRepresentation size]);
+ NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil];
+ NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
+ [data writeToFile:newFullPath atomically:YES];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newFullPath isDirectory:NO]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ } else {
+ // We couldn't find the asset. Send the appropriate error.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }
+ };
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[NSURL URLWithString:srcFullPath] resultBlock:resultBlock failureBlock:failureBlock];
+ return;
+ } else {
+ // Moving an assets library file is not doable, since we can't remove it.
+ errCode = INVALID_MODIFICATION_ERR;
+ }
+ } else {
+ BOOL bSrcIsDir = NO;
+ BOOL bDestIsDir = NO;
+ BOOL bNewIsDir = NO;
+ BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
+ BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
+ BOOL bNewExists = [fileMgr fileExistsAtPath:newFullPath isDirectory:&bNewIsDir];
+ if (!bSrcExists || !bDestExists) {
+ // the source or the destination root does not exist
+ errCode = NOT_FOUND_ERR;
+ } else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
+ // can't copy/move dir to file
+ errCode = INVALID_MODIFICATION_ERR;
+ } else { // no errors yet
+ NSError* __autoreleasing error = nil;
+ BOOL bSuccess = NO;
+ if (bCopy) {
+ if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath] /*[newFullPath hasPrefix:srcFullPath]*/) {
+ // can't copy dir into self
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bNewExists) {
+ // the full destination should NOT already exist if a copy
+ errCode = PATH_EXISTS_ERR;
+ } else {
+ bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFullPath error:&error];
+ }
+ } else { // move
+ // iOS requires that destination must not exist before calling moveTo
+ // is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
+ //
+ if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
+ // can't move a file to directory
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFullPath]) { // [newFullPath hasPrefix:srcFullPath]){
+ // can't move a dir into itself
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bNewExists) {
+ if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFullPath error:NULL] count] != 0)) {
+ // can't move dir to a dir that is not empty
+ errCode = INVALID_MODIFICATION_ERR;
+ newFullPath = nil; // so we won't try to move
+ } else {
+ // remove destination so can perform the moveItemAtPath
+ bSuccess = [fileMgr removeItemAtPath:newFullPath error:NULL];
+ if (!bSuccess) {
+ errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
+ newFullPath = nil;
+ }
+ }
+ } else if (bNewIsDir && [newFullPath hasPrefix:srcFullPath]) {
+ // can't move a directory inside itself or to any child at any depth;
+ errCode = INVALID_MODIFICATION_ERR;
+ newFullPath = nil;
+ }
+
+ if (newFullPath != nil) {
+ bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFullPath error:&error];
+ }
+ }
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self getDirectoryEntry:newFullPath isDirectory:bSrcIsDir]; // should be the same type as source
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ errCode = INVALID_MODIFICATION_ERR; // catch all
+ if (error) {
+ if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
+ errCode = NOT_READABLE_ERR;
+ } else if ([error code] == NSFileWriteOutOfSpaceError) {
+ errCode = QUOTA_EXCEEDED_ERR;
+ } else if ([error code] == NSFileWriteNoPermissionError) {
+ errCode = NO_MODIFICATION_ALLOWED_ERR;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (errCode > 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* return the URI to the entry
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath of entry
+ * 1 - desired mime type of entry - ignored - always returns file://
+ */
+
+/* Not needed since W3C toURI is synchronous. Leaving code here for now in case W3C spec changes.....
+- (void) toURI:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSString* argPath = [command.arguments objectAtIndex:0];
+ PluginResult* result = nil;
+ NSString* jsString = nil;
+
+ NSString* fullPath = [self getFullPath: argPath];
+ if (fullPath) {
+ // do we need to make sure the file actually exists?
+ // create file uri
+ NSString* strUri = [fullPath stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
+ NSURL* fileUrl = [NSURL fileURLWithPath:strUri];
+ if (fileUrl) {
+ result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: [fileUrl absoluteString]];
+ jsString = [result toSuccessCallbackString:callbackId];
+ } // else NOT_FOUND_ERR
+ }
+ if(!jsString) {
+ // was error
+ result = [PluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt: NOT_FOUND_ERR cast: @"window.localFileSystem._castError"];
+ jsString = [result toErrorCallbackString:callbackId];
+ }
+
+ [self writeJavascript:jsString];
+}*/
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command.arguments objectAtIndex:0];
+
+ __block CDVPluginResult* result = nil;
+
+ NSString* fullPath = argPath; // [self getFullPath: argPath];
+
+ if (fullPath) {
+ if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ // In this case, we need to use an asynchronous method to retrieve the file.
+ // Because of this, we can't just assign to `result` and send it at the end of the method.
+ // Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Populate the dictionary and send it off.
+ NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
+ ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
+ [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"];
+ [fileInfo setObject:argPath forKey:@"fullPath"];
+ NSString* filename = [assetRepresentation filename];
+ [fileInfo setObject:filename forKey:@"name"];
+ [fileInfo setObject:[self getMimeTypeFromPath:filename] forKey:@"type"];
+ NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate];
+ NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000];
+ [fileInfo setObject:msDate forKey:@"lastModifiedDate"];
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ } else {
+ // We couldn't find the asset. Send the appropriate error.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }
+ };
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock];
+ return;
+ } else {
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir = NO;
+ // make sure it exists and is not a directory
+ BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir];
+ if (!bExists || bIsDir) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ } else {
+ // create dictionary of file info
+ NSError* __autoreleasing error = nil;
+ NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:&error];
+ NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
+ [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"];
+ [fileInfo setObject:argPath forKey:@"fullPath"];
+ [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping
+ [fileInfo setObject:[argPath lastPathComponent] forKey:@"name"];
+ NSDate* modDate = [fileAttrs fileModificationDate];
+ NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000];
+ [fileInfo setObject:msDate forKey:@"lastModifiedDate"];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
+ }
+ }
+ }
+ if (!result) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_INSTANTIATION_EXCEPTION];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)readEntries:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* fullPath = [command.arguments objectAtIndex:0];
+
+ // return unsupported result for assets-library URLs
+ if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVPluginResult* result = nil;
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* __autoreleasing error = nil;
+ NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fullPath error:&error];
+
+ if (contents) {
+ NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
+ if ([contents count] > 0) {
+ // create an Entry (as JSON) for each file/dir
+ for (NSString* name in contents) {
+ // see if is dir or file
+ NSString* entryPath = [fullPath stringByAppendingPathComponent:name];
+ BOOL bIsDir = NO;
+ [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
+ NSDictionary* entryDict = [self getDirectoryEntry:entryPath isDirectory:bIsDir];
+ [entries addObject:entryDict];
+ }
+ }
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
+ } else {
+ // assume not found but could check error for more specific error conditions
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)readFileWithPath:(NSString*)path start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
+{
+ if (path == nil) {
+ callback(nil, nil, SYNTAX_ERR);
+ } else {
+ [self.commandDelegate runInBackground:^ {
+ if ([path hasPrefix:kCDVAssetsLibraryPrefix]) {
+ // In this case, we need to use an asynchronous method to retrieve the file.
+ // Because of this, we can't just assign to `result` and send it at the end of the method.
+ // Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Get the data and send it off.
+ ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
+ Byte* buffer = (Byte*)malloc([assetRepresentation size]);
+ NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil];
+ NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
+ NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType);
+
+ callback(data, MIMEType, NO_ERROR);
+ } else {
+ callback(nil, nil, NOT_FOUND_ERR);
+ }
+ };
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ NSLog(@"Error: %@", error);
+ callback(nil, nil, SECURITY_ERR);
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[NSURL URLWithString:path] resultBlock:resultBlock failureBlock:failureBlock];
+ } else {
+ NSString* mimeType = [self getMimeTypeFromPath:path];
+ if (mimeType == nil) {
+ mimeType = @"*/*";
+ }
+ NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
+ if (start > 0) {
+ [file seekToFileOffset:start];
+ }
+
+ NSData* readData;
+ if (end < 0) {
+ readData = [file readDataToEndOfFile];
+ } else {
+ readData = [file readDataOfLength:(end - start)];
+ }
+
+ [file closeFile];
+
+ callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
+ }
+ }];
+ }
+}
+
+/* read and return file data
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * 1 - NSString* encoding
+ * 2 - NSString* start
+ * 3 - NSString* end
+ */
+- (void)readAsText:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* path = [command argumentAtIndex:0];
+ NSString* encoding = [command argumentAtIndex:1];
+ NSInteger start = [[command argumentAtIndex:2] integerValue];
+ NSInteger end = [[command argumentAtIndex:3] integerValue];
+
+ // TODO: implement
+ if ([@"UTF-8" caseInsensitiveCompare : encoding] != NSOrderedSame) {
+ NSLog(@"Only UTF-8 encodings are currently supported by readAsText");
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO];
+ // Check that UTF8 conversion did not fail.
+ if (str != nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str];
+ result.associatedObject = data;
+ } else {
+ errorCode = ENCODING_ERR;
+ }
+ }
+ if (result == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+/* Read content of text file and return as base64 encoded data url.
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * 1 - NSString* start
+ * 2 - NSString* end
+ *
+ * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined.
+ */
+
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command
+{
+ NSString* path = [command argumentAtIndex:0];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ // TODO: Would be faster to base64 encode directly to the final string.
+ NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+/* Read content of text file and return as an arraybuffer
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* fullPath
+ * 1 - NSString* start
+ * 2 - NSString* end
+ */
+
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command
+{
+ NSString* path = [command argumentAtIndex:0];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command
+{
+ NSString* path = [command argumentAtIndex:0];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
+ result.associatedObject = data;
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+/* helper function to get the mimeType from the file extension
+ * IN:
+ * NSString* fullPath - filename (may include path)
+ * OUT:
+ * NSString* the mime type as type/subtype. nil if not able to determine
+ */
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath
+{
+ NSString* mimeType = nil;
+
+ if (fullPath) {
+ CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
+ if (typeId) {
+ mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
+ if (!mimeType) {
+ // special case for m4a
+ if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
+ mimeType = @"audio/mp4";
+ } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
+ mimeType = @"audio/wav";
+ }
+ }
+ CFRelease(typeId);
+ }
+ }
+ return mimeType;
+}
+
+- (void)truncate:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command.arguments objectAtIndex:0];
+ unsigned long long pos = (unsigned long long)[[command.arguments objectAtIndex:1] longLongValue];
+
+ // assets-library files can't be truncated
+ if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ NSString* appFile = argPath; // [self getFullPath:argPath];
+
+ unsigned long long newPos = [self truncateFile:appFile atPosition:pos];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:newPos];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
+{
+ unsigned long long newPos = 0UL;
+
+ NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
+
+ if (file) {
+ [file truncateFileAtOffset:(unsigned long long)pos];
+ newPos = [file offsetInFile];
+ [file synchronizeFile];
+ [file closeFile];
+ }
+ return newPos;
+}
+
+/* write
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* file path to write to
+ * 1 - NSString* or NSData* data to write
+ * 2 - NSNumber* position to begin writing
+ */
+- (void)write:(CDVInvokedUrlCommand*)command
+{
+ NSString* callbackId = command.callbackId;
+ NSArray* arguments = command.arguments;
+
+ // arguments
+ NSString* argPath = [arguments objectAtIndex:0];
+ id argData = [arguments objectAtIndex:1];
+ unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue];
+
+ // text can't be written into assets-library files
+ if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ NSString* fullPath = argPath; // [self getFullPath:argPath];
+
+ [self truncateFile:fullPath atPosition:pos];
+
+ if ([argData isKindOfClass:[NSString class]]) {
+ [self writeToFile:fullPath withString:argData encoding:NSUTF8StringEncoding append:YES callback:callbackId];
+ } else if ([argData isKindOfClass:[NSData class]]) {
+ [self writeToFile:fullPath withData:argData append:YES callback:callbackId];
+ } else {
+ CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"];
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }
+
+}
+
+- (void)writeToFile:(NSString*)filePath withData:(NSData*)encData append:(BOOL)shouldAppend callback:(NSString*)callbackId
+{
+ CDVPluginResult* result = nil;
+ CDVFileError errCode = INVALID_MODIFICATION_ERR;
+ int bytesWritten = 0;
+
+ if (filePath) {
+ NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
+ if (fileStream) {
+ NSUInteger len = [encData length];
+ [fileStream open];
+
+ bytesWritten = [fileStream write:[encData bytes] maxLength:len];
+
+ [fileStream close];
+ if (bytesWritten > 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
+ // } else {
+ // can probably get more detailed error info via [fileStream streamError]
+ // errCode already set to INVALID_MODIFICATION_ERR;
+ // bytesWritten = 0; // may be set to -1 on error
+ }
+ } // else fileStream not created return INVALID_MODIFICATION_ERR
+ } else {
+ // invalid filePath
+ errCode = NOT_FOUND_ERR;
+ }
+ if (!result) {
+ // was an error
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+}
+
+- (void)writeToFile:(NSString*)filePath withString:(NSString*)stringData encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId
+{
+ [self writeToFile:filePath withData:[stringData dataUsingEncoding:encoding allowLossyConversion:YES] append:shouldAppend callback:callbackId];
+}
+
+- (void)testFileExists:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command.arguments objectAtIndex:0];
+
+ // Get the file manager
+ NSFileManager* fMgr = [NSFileManager defaultManager];
+ NSString* appFile = argPath; // [ self getFullPath: argPath];
+
+ BOOL bExists = [fMgr fileExistsAtPath:appFile];
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1 : 0)];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command.arguments objectAtIndex:0];
+
+ // Get the file manager
+ NSFileManager* fMgr = [[NSFileManager alloc] init];
+ NSString* appFile = argPath; // [self getFullPath: argPath];
+ BOOL bIsDir = NO;
+ BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir];
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1 : 0)];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+// Returns number of bytes available via callback
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command
+{
+ // no arguments
+
+ NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appDocsPath];
+
+ NSString* strFreeSpace = [NSString stringWithFormat:@"%qu", [pNumAvail unsignedLongLongValue]];
+ // NSLog(@"Free space is %@", strFreeSpace );
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:strFreeSpace];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+@end
diff --git a/plugins/org.apache.cordova.file/src/windows8/FileProxy.js b/plugins/org.apache.cordova.file/src/windows8/FileProxy.js
new file mode 100644
index 00000000..1445ae70
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/windows8/FileProxy.js
@@ -0,0 +1,845 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var cordova = require('cordova');
+var Entry = require('./Entry'),
+ File = require('./File'),
+ FileEntry = require('./FileEntry'),
+ FileError = require('./FileError'),
+ DirectoryEntry = require('./DirectoryEntry'),
+ Flags = require('./Flags'),
+ FileSystem = require('./FileSystem'),
+ LocalFileSystem = require('./LocalFileSystem');
+
+module.exports = {
+
+ getFileMetadata:function(win,fail,args) {
+ var fullPath = args[0];
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).done(
+ function (storageFile) {
+ storageFile.getBasicPropertiesAsync().then(
+ function (basicProperties) {
+ win(new File(storageFile.name, storageFile.path, storageFile.fileType, basicProperties.dateModified, basicProperties.size));
+ }, function () {
+ fail && fail(FileError.NOT_READABLE_ERR);
+ }
+ );
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ getMetadata:function(success,fail,args) {
+ var fullPath = args[0];
+
+ var dealFile = function (sFile) {
+ Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).then(
+ function (storageFile) {
+ return storageFile.getBasicPropertiesAsync();
+ },
+ function () {
+ fail && fail(FileError.NOT_READABLE_ERR);
+ }
+ // get the basic properties of the file.
+ ).then(
+ function (basicProperties) {
+ success(basicProperties.dateModified);
+ },
+ function () {
+ fail && fail(FileError.NOT_READABLE_ERR);
+ }
+ );
+ };
+
+ var dealFolder = function (sFolder) {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then(
+ function (storageFolder) {
+ return storageFolder.getBasicPropertiesAsync();
+ },
+ function () {
+ fail && fail(FileError.NOT_READABLE_ERR);
+ }
+ // get the basic properties of the folder.
+ ).then(
+ function (basicProperties) {
+ success(basicProperties.dateModified);
+ },
+ function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ };
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).then(
+ // the path is file.
+ function (sFile) {
+ dealFile(sFile);
+ },
+ // the path is folder
+ function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then(
+ function (sFolder) {
+ dealFolder(sFolder);
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ },
+
+ getParent:function(win,fail,args) { // ["fullPath"]
+ var fullPath = args[0];
+
+ var storageFolderPer = Windows.Storage.ApplicationData.current.localFolder;
+ var storageFolderTem = Windows.Storage.ApplicationData.current.temporaryFolder;
+
+ if (fullPath == storageFolderPer.path) {
+ win(new DirectoryEntry(storageFolderPer.name, storageFolderPer.path));
+ return;
+ } else if (fullPath == storageFolderTem.path) {
+ win(new DirectoryEntry(storageFolderTem.name, storageFolderTem.path));
+ return;
+ }
+ var splitArr = fullPath.split(new RegExp(/\/|\\/g));
+
+ var popItem = splitArr.pop();
+
+ var result = new DirectoryEntry(popItem, fullPath.substr(0, fullPath.length - popItem.length - 1));
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(result.fullPath).done(
+ function () { win(result); },
+ function () { fail && fail(FileError.INVALID_STATE_ERR); }
+ );
+ },
+
+ readAsText:function(win,fail,args) {
+ var fileName = args[0];
+ var enc = args[1];
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fileName).done(
+ function (storageFile) {
+ var value = Windows.Storage.Streams.UnicodeEncoding.utf8;
+ if (enc == 'Utf16LE' || enc == 'utf16LE') {
+ value = Windows.Storage.Streams.UnicodeEncoding.utf16LE;
+ }else if (enc == 'Utf16BE' || enc == 'utf16BE') {
+ value = Windows.Storage.Streams.UnicodeEncoding.utf16BE;
+ }
+ Windows.Storage.FileIO.readTextAsync(storageFile, value).done(
+ function (fileContent) {
+ win(fileContent);
+ },
+ function () {
+ fail && fail(FileError.ENCODING_ERR);
+ }
+ );
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ readAsDataURL:function(win,fail,args) {
+ var fileName = args[0];
+
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fileName).then(
+ function (storageFile) {
+ Windows.Storage.FileIO.readBufferAsync(storageFile).done(
+ function (buffer) {
+ var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
+ //the method encodeToBase64String will add "77u/" as a prefix, so we should remove it
+ if(String(strBase64).substr(0,4) == "77u/") {
+ strBase64 = strBase64.substr(4);
+ }
+ var mediaType = storageFile.contentType;
+ var result = "data:" + mediaType + ";base64," + strBase64;
+ win(result);
+ }
+ );
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ getDirectory:function(win,fail,args) {
+ var fullPath = args[0];
+ var path = args[1];
+ var options = args[2];
+
+ var flag = "";
+ if (options !== null) {
+ flag = new Flags(options.create, options.exclusive);
+ } else {
+ flag = new Flags(false, false);
+ }
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then(
+ function (storageFolder) {
+ if (flag.create === true && flag.exclusive === true) {
+ storageFolder.createFolderAsync(path, Windows.Storage.CreationCollisionOption.failIfExists).done(
+ function (storageFolder) {
+ win(new DirectoryEntry(storageFolder.name, storageFolder.path));
+ }, function () {
+ fail && fail(FileError.PATH_EXISTS_ERR);
+ }
+ );
+ } else if (flag.create === true && flag.exclusive === false) {
+ storageFolder.createFolderAsync(path, Windows.Storage.CreationCollisionOption.openIfExists).done(
+ function (storageFolder) {
+ win(new DirectoryEntry(storageFolder.name, storageFolder.path));
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ } else if (flag.create === false) {
+ if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(path)) {
+ fail && fail(FileError.ENCODING_ERR);
+ return;
+ }
+
+ storageFolder.getFolderAsync(path).done(
+ function (storageFolder) {
+ win(new DirectoryEntry(storageFolder.name, storageFolder.path));
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ remove:function(win,fail,args) {
+ var fullPath = args[0];
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).then(
+ function (sFile) {
+ Windows.Storage.StorageFile.getFileFromPathAsync(fullPath).done(function (storageFile) {
+ storageFile.deleteAsync().done(win, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+
+ });
+ });
+ },
+ function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then(
+ function (sFolder) {
+ var removeEntry = function () {
+ var storageFolderTop = null;
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then(
+ function (storageFolder) {
+ // FileSystem root can't be removed!
+ var storageFolderPer = Windows.Storage.ApplicationData.current.localFolder;
+ var storageFolderTem = Windows.Storage.ApplicationData.current.temporaryFolder;
+ if (fullPath == storageFolderPer.path || fullPath == storageFolderTem.path) {
+ fail && fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ storageFolderTop = storageFolder;
+ return storageFolder.createFileQuery().getFilesAsync();
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+
+ }
+ // check sub-files.
+ ).then(function (fileList) {
+ if (fileList) {
+ if (fileList.length === 0) {
+ return storageFolderTop.createFolderQuery().getFoldersAsync();
+ } else {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ }
+ // check sub-folders.
+ }).then(function (folderList) {
+ if (folderList) {
+ if (folderList.length === 0) {
+ storageFolderTop.deleteAsync().done(win, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+
+ });
+ } else {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ }
+
+ });
+ };
+ removeEntry();
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ },
+
+ removeRecursively:function(successCallback,fail,args) {
+ var fullPath = args[0];
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).done(function (storageFolder) {
+ var storageFolderPer = Windows.Storage.ApplicationData.current.localFolder;
+ var storageFolderTem = Windows.Storage.ApplicationData.current.temporaryFolder;
+
+ if (storageFolder.path == storageFolderPer.path || storageFolder.path == storageFolderTem.path) {
+ fail && fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ var removeFolders = function (path) {
+ return new WinJS.Promise(function (complete) {
+ var filePromiseArr = [];
+ var storageFolderTop = null;
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(
+ function (storageFolder) {
+ var fileListPromise = storageFolder.createFileQuery().getFilesAsync();
+
+ storageFolderTop = storageFolder;
+ return fileListPromise;
+ }
+ // remove all the files directly under the folder.
+ ).then(function (fileList) {
+ if (fileList !== null) {
+ for (var i = 0; i < fileList.length; i++) {
+ var filePromise = fileList[i].deleteAsync();
+ filePromiseArr.push(filePromise);
+ }
+ }
+ WinJS.Promise.join(filePromiseArr).then(function () {
+ var folderListPromise = storageFolderTop.createFolderQuery().getFoldersAsync();
+ return folderListPromise;
+ // remove empty folders.
+ }).then(function (folderList) {
+ var folderPromiseArr = [];
+ if (folderList.length !== 0) {
+ for (var j = 0; j < folderList.length; j++) {
+
+ folderPromiseArr.push(removeFolders(folderList[j].path));
+ }
+ WinJS.Promise.join(folderPromiseArr).then(function () {
+ storageFolderTop.deleteAsync().then(complete);
+ });
+ } else {
+ storageFolderTop.deleteAsync().then(complete);
+ }
+ }, function () { });
+ }, function () { });
+ });
+ };
+ removeFolders(storageFolder.path).then(function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(storageFolder.path).then(
+ function () {},
+ function () {
+ if (typeof successCallback !== 'undefined' && successCallback !== null) { successCallback(); }
+ });
+ });
+ });
+ },
+
+ getFile:function(win,fail,args) {
+ var fullPath = args[0];
+ var path = args[1];
+ var options = args[2];
+
+ var flag = "";
+ if (options !== null) {
+ flag = new Flags(options.create, options.exclusive);
+ } else {
+ flag = new Flags(false, false);
+ }
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(fullPath).then(
+ function (storageFolder) {
+ if (flag.create === true && flag.exclusive === true) {
+ storageFolder.createFileAsync(path, Windows.Storage.CreationCollisionOption.failIfExists).done(
+ function (storageFile) {
+ win(new FileEntry(storageFile.name, storageFile.path));
+ }, function () {
+ fail && fail(FileError.PATH_EXISTS_ERR);
+ }
+ );
+ } else if (flag.create === true && flag.exclusive === false) {
+ storageFolder.createFileAsync(path, Windows.Storage.CreationCollisionOption.openIfExists).done(
+ function (storageFile) {
+ win(new FileEntry(storageFile.name, storageFile.path));
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ } else if (flag.create === false) {
+ if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(path)) {
+ fail && fail(FileError.ENCODING_ERR);
+ return;
+ }
+ storageFolder.getFileAsync(path).done(
+ function (storageFile) {
+ win(new FileEntry(storageFile.name, storageFile.path));
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ readEntries:function(win,fail,args) { // ["fullPath"]
+ var path = args[0];
+
+ var result = [];
+
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(function (storageFolder) {
+ var promiseArr = [];
+ var index = 0;
+ promiseArr[index++] = storageFolder.createFileQuery().getFilesAsync().then(function (fileList) {
+ if (fileList !== null) {
+ for (var i = 0; i < fileList.length; i++) {
+ result.push(new FileEntry(fileList[i].name, fileList[i].path));
+ }
+ }
+ });
+ promiseArr[index++] = storageFolder.createFolderQuery().getFoldersAsync().then(function (folderList) {
+ if (folderList !== null) {
+ for (var j = 0; j < folderList.length; j++) {
+ result.push(new FileEntry(folderList[j].name, folderList[j].path));
+ }
+ }
+ });
+ WinJS.Promise.join(promiseArr).then(function () {
+ win(result);
+ });
+
+ }, function () { fail && fail(FileError.NOT_FOUND_ERR); });
+ },
+
+ write:function(win,fail,args) {
+ var fileName = args[0];
+ var text = args[1];
+ var position = args[2];
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fileName).done(
+ function (storageFile) {
+ Windows.Storage.FileIO.writeTextAsync(storageFile,text,Windows.Storage.Streams.UnicodeEncoding.utf8).done(
+ function() {
+ win(String(text).length);
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ }, function() {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ truncate:function(win,fail,args) { // ["fileName","size"]
+ var fileName = args[0];
+ var size = args[1];
+
+ Windows.Storage.StorageFile.getFileFromPathAsync(fileName).done(function(storageFile){
+ //the current length of the file.
+ var leng = 0;
+
+ storageFile.getBasicPropertiesAsync().then(function (basicProperties) {
+ leng = basicProperties.size;
+ if (Number(size) >= leng) {
+ win(this.length);
+ return;
+ }
+ if (Number(size) >= 0) {
+ Windows.Storage.FileIO.readTextAsync(storageFile, Windows.Storage.Streams.UnicodeEncoding.utf8).then(function (fileContent) {
+ fileContent = fileContent.substr(0, size);
+ var fullPath = storageFile.path;
+ var name = storageFile.name;
+ var entry = new Entry(true, false, name, fullPath);
+ var parentPath = "";
+ var successCallBack = function (entry) {
+ parentPath = entry.fullPath;
+ storageFile.deleteAsync().then(function () {
+ return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath);
+ }).then(function (storageFolder) {
+ storageFolder.createFileAsync(name).then(function (newStorageFile) {
+ Windows.Storage.FileIO.writeTextAsync(newStorageFile, fileContent).done(function () {
+ win(String(fileContent).length);
+ }, function () {
+ fail && fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ });
+ });
+ });
+ };
+ entry.getParent(successCallBack, null);
+ }, function () { fail && fail(FileError.NOT_FOUND_ERR); });
+ }
+ });
+ }, function () { fail && fail(FileError.NOT_FOUND_ERR); });
+ },
+
+ copyTo:function(success,fail,args) { // ["fullPath","parent", "newName"]
+ var srcPath = args[0];
+ var parentFullPath = args[1];
+ var name = args[2];
+
+ //name can't be invalid
+ if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(name)) {
+ fail && fail(FileError.ENCODING_ERR);
+ return;
+ }
+ // copy
+ var copyFiles = "";
+ Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then(
+ function (sFile) {
+ copyFiles = function (srcPath, parentPath) {
+ var storageFileTop = null;
+ Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then(function (storageFile) {
+ storageFileTop = storageFile;
+ return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath);
+ }, function () {
+
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }).then(function (storageFolder) {
+ storageFileTop.copyAsync(storageFolder, name, Windows.Storage.NameCollisionOption.failIfExists).then(function (storageFile) {
+
+ success(new FileEntry(storageFile.name, storageFile.path));
+ }, function () {
+
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ });
+ }, function () {
+
+ fail && fail(FileError.NOT_FOUND_ERR);
+ });
+ };
+ var copyFinish = function (srcPath, parentPath) {
+ copyFiles(srcPath, parentPath);
+ };
+ copyFinish(srcPath, parentFullPath);
+ },
+ function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(
+ function (sFolder) {
+ copyFiles = function (srcPath, parentPath) {
+ var coreCopy = function (storageFolderTop, complete) {
+ storageFolderTop.createFolderQuery().getFoldersAsync().then(function (folderList) {
+ var folderPromiseArr = [];
+ if (folderList.length === 0) { complete(); }
+ else {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (storageFolderTarget) {
+ var tempPromiseArr = [];
+ var index = 0;
+ for (var j = 0; j < folderList.length; j++) {
+ tempPromiseArr[index++] = storageFolderTarget.createFolderAsync(folderList[j].name).then(function (targetFolder) {
+ folderPromiseArr.push(copyFiles(folderList[j].path, targetFolder.path));
+ });
+ }
+ WinJS.Promise.join(tempPromiseArr).then(function () {
+ WinJS.Promise.join(folderPromiseArr).then(complete);
+ });
+ });
+ }
+ });
+ };
+
+ return new WinJS.Promise(function (complete) {
+ var storageFolderTop = null;
+ var filePromiseArr = [];
+ var fileListTop = null;
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(function (storageFolder) {
+ storageFolderTop = storageFolder;
+ return storageFolder.createFileQuery().getFilesAsync();
+ }).then(function (fileList) {
+ fileListTop = fileList;
+ if (fileList) {
+ return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath);
+ }
+ }).then(function (targetStorageFolder) {
+ for (var i = 0; i < fileListTop.length; i++) {
+ filePromiseArr.push(fileListTop[i].copyAsync(targetStorageFolder));
+ }
+ WinJS.Promise.join(filePromiseArr).then(function () {
+ coreCopy(storageFolderTop, complete);
+ });
+ });
+ });
+ };
+ var copyFinish = function (srcPath, parentPath) {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (storageFolder) {
+ storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists).then(function (newStorageFolder) {
+ //can't copy onto itself
+ if (srcPath == newStorageFolder.path) {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ //can't copy into itself
+ if (srcPath == parentPath) {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ copyFiles(srcPath, newStorageFolder.path).then(function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(newStorageFolder.path).done(
+ function (storageFolder) {
+ success(new DirectoryEntry(storageFolder.name, storageFolder.path));
+ },
+ function () { fail && fail(FileError.NOT_FOUND_ERR); }
+ );
+ });
+ }, function () { fail && fail(FileError.INVALID_MODIFICATION_ERR); });
+ }, function () { fail && fail(FileError.INVALID_MODIFICATION_ERR); });
+ };
+ copyFinish(srcPath, parentFullPath);
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ },
+
+ moveTo:function(success,fail,args) {
+ var srcPath = args[0];
+ var parentFullPath = args[1];
+ var name = args[2];
+
+
+ //name can't be invalid
+ if (/\?|\\|\*|\||\"|<|>|\:|\//g.test(name)) {
+ fail && fail(FileError.ENCODING_ERR);
+ return;
+ }
+
+ var moveFiles = "";
+ Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then(
+ function (sFile) {
+ moveFiles = function (srcPath, parentPath) {
+ var storageFileTop = null;
+ Windows.Storage.StorageFile.getFileFromPathAsync(srcPath).then(function (storageFile) {
+ storageFileTop = storageFile;
+ return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath);
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }).then(function (storageFolder) {
+ storageFileTop.moveAsync(storageFolder, name, Windows.Storage.NameCollisionOption.replaceExisting).then(function () {
+ success(new FileEntry(name, storageFileTop.path));
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ });
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ });
+ };
+ var moveFinish = function (srcPath, parentPath) {
+ //can't copy onto itself
+ if (srcPath == parentPath + "\\" + name) {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ moveFiles(srcPath, parentFullPath);
+ };
+ moveFinish(srcPath, parentFullPath);
+ },
+ function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(
+ function (sFolder) {
+ moveFiles = function (srcPath, parentPath) {
+ var coreMove = function (storageFolderTop, complete) {
+ storageFolderTop.createFolderQuery().getFoldersAsync().then(function (folderList) {
+ var folderPromiseArr = [];
+ if (folderList.length === 0) {
+ // If failed, we must cancel the deletion of folders & files.So here wo can't delete the folder.
+ complete();
+ }
+ else {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (storageFolderTarget) {
+ var tempPromiseArr = [];
+ var index = 0;
+ for (var j = 0; j < folderList.length; j++) {
+ tempPromiseArr[index++] = storageFolderTarget.createFolderAsync(folderList[j].name).then(function (targetFolder) {
+ folderPromiseArr.push(moveFiles(folderList[j].path, targetFolder.path));
+ });
+ }
+ WinJS.Promise.join(tempPromiseArr).then(function () {
+ WinJS.Promise.join(folderPromiseArr).then(complete);
+ });
+ });
+ }
+ });
+ };
+ return new WinJS.Promise(function (complete) {
+ var storageFolderTop = null;
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(function (storageFolder) {
+ storageFolderTop = storageFolder;
+ return storageFolder.createFileQuery().getFilesAsync();
+ }).then(function (fileList) {
+ var filePromiseArr = [];
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath).then(function (dstStorageFolder) {
+ if (fileList) {
+ for (var i = 0; i < fileList.length; i++) {
+ filePromiseArr.push(fileList[i].moveAsync(dstStorageFolder));
+ }
+ }
+ WinJS.Promise.join(filePromiseArr).then(function () {
+ coreMove(storageFolderTop, complete);
+ }, function () { });
+ });
+ });
+ });
+ };
+ var moveFinish = function (srcPath, parentPath) {
+ var originFolderTop = null;
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(srcPath).then(function (originFolder) {
+ originFolderTop = originFolder;
+ return Windows.Storage.StorageFolder.getFolderFromPathAsync(parentPath);
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }).then(function (storageFolder) {
+ return storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists);
+ }, function () {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ }).then(function (newStorageFolder) {
+ //can't move onto directory that is not empty
+ newStorageFolder.createFileQuery().getFilesAsync().then(function (fileList) {
+ newStorageFolder.createFolderQuery().getFoldersAsync().then(function (folderList) {
+ if (fileList.length !== 0 || folderList.length !== 0) {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ //can't copy onto itself
+ if (srcPath == newStorageFolder.path) {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ //can't copy into itself
+ if (srcPath == parentPath) {
+ fail && fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ moveFiles(srcPath, newStorageFolder.path).then(function () {
+ var successCallback = function () {
+ success(new DirectoryEntry(name, newStorageFolder.path));
+ };
+ var temp = new DirectoryEntry(originFolderTop.name, originFolderTop.path).removeRecursively(successCallback, fail);
+
+ }, function () { console.log("error!"); });
+ });
+ });
+ }, function () { fail && fail(FileError.INVALID_MODIFICATION_ERR); });
+
+ };
+ moveFinish(srcPath, parentFullPath);
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ },
+ tempFileSystem:null,
+
+ persistentFileSystem:null,
+
+ requestFileSystem:function(win,fail,args) {
+ var type = args[0];
+ var size = args[1];
+
+ var filePath = "";
+ var result = null;
+ var fsTypeName = "";
+
+ switch (type) {
+ case LocalFileSystem.TEMPORARY:
+ filePath = Windows.Storage.ApplicationData.current.temporaryFolder.path;
+ fsTypeName = "temporary";
+ break;
+ case LocalFileSystem.PERSISTENT:
+ filePath = Windows.Storage.ApplicationData.current.localFolder.path;
+ fsTypeName = "persistent";
+ break;
+ }
+
+ var MAX_SIZE = 10000000000;
+ if (size > MAX_SIZE) {
+ fail && fail(FileError.QUOTA_EXCEEDED_ERR);
+ return;
+ }
+
+ var fileSystem = new FileSystem(fsTypeName, new DirectoryEntry(fsTypeName, filePath));
+ result = fileSystem;
+ win(result);
+ },
+
+ resolveLocalFileSystemURI:function(success,fail,args) {
+ var uri = args[0];
+
+ var path = uri;
+
+ // support for file name with parameters
+ if (/\?/g.test(path)) {
+ path = String(path).split("?")[0];
+ }
+
+ // support for encodeURI
+ if (/\%5/g.test(path)) {
+ path = decodeURI(path);
+ }
+
+ // support for special path start with file:///
+ if (path.substr(0, 8) == "file:///") {
+ path = Windows.Storage.ApplicationData.current.localFolder.path + "\\" + String(path).substr(8).split("/").join("\\");
+ Windows.Storage.StorageFile.getFileFromPathAsync(path).then(
+ function (storageFile) {
+ success(new FileEntry(storageFile.name, storageFile.path));
+ }, function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(
+ function (storageFolder) {
+ success(new DirectoryEntry(storageFolder.name, storageFolder.path));
+ }, function () {
+ fail && fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ } else {
+ Windows.Storage.StorageFile.getFileFromPathAsync(path).then(
+ function (storageFile) {
+ success(new FileEntry(storageFile.name, storageFile.path));
+ }, function () {
+ Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(
+ function (storageFolder) {
+ success(new DirectoryEntry(storageFolder.name, storageFolder.path));
+ }, function () {
+ fail && fail(FileError.ENCODING_ERR);
+ }
+ );
+ }
+ );
+ }
+ }
+
+};
+
+require("cordova/windows8/commandProxy").add("File",module.exports);
diff --git a/plugins/org.apache.cordova.file/src/wp/File.cs b/plugins/org.apache.cordova.file/src/wp/File.cs
new file mode 100644
index 00000000..4fc61dde
--- /dev/null
+++ b/plugins/org.apache.cordova.file/src/wp/File.cs
@@ -0,0 +1,1676 @@
+/*
+ Licensed 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.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Runtime.Serialization;
+using System.Security;
+using System.Text;
+using System.Windows;
+using System.Windows.Resources;
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ /// <summary>
+ /// Provides access to isolated storage
+ /// </summary>
+ public class File : BaseCommand
+ {
+ // Error codes
+ public const int NOT_FOUND_ERR = 1;
+ public const int SECURITY_ERR = 2;
+ public const int ABORT_ERR = 3;
+ public const int NOT_READABLE_ERR = 4;
+ public const int ENCODING_ERR = 5;
+ public const int NO_MODIFICATION_ALLOWED_ERR = 6;
+ public const int INVALID_STATE_ERR = 7;
+ public const int SYNTAX_ERR = 8;
+ public const int INVALID_MODIFICATION_ERR = 9;
+ public const int QUOTA_EXCEEDED_ERR = 10;
+ public const int TYPE_MISMATCH_ERR = 11;
+ public const int PATH_EXISTS_ERR = 12;
+
+ // File system options
+ public const int TEMPORARY = 0;
+ public const int PERSISTENT = 1;
+ public const int RESOURCE = 2;
+ public const int APPLICATION = 3;
+
+ /// <summary>
+ /// Temporary directory name
+ /// </summary>
+ private readonly string TMP_DIRECTORY_NAME = "tmp";
+
+ /// <summary>
+ /// Represents error code for callback
+ /// </summary>
+ [DataContract]
+ public class ErrorCode
+ {
+ /// <summary>
+ /// Error code
+ /// </summary>
+ [DataMember(IsRequired = true, Name = "code")]
+ public int Code { get; set; }
+
+ /// <summary>
+ /// Creates ErrorCode object
+ /// </summary>
+ public ErrorCode(int code)
+ {
+ this.Code = code;
+ }
+ }
+
+ /// <summary>
+ /// Represents File action options.
+ /// </summary>
+ [DataContract]
+ public class FileOptions
+ {
+ /// <summary>
+ /// File path
+ /// </summary>
+ ///
+ private string _fileName;
+ [DataMember(Name = "fileName")]
+ public string FilePath
+ {
+ get
+ {
+ return this._fileName;
+ }
+
+ set
+ {
+ int index = value.IndexOfAny(new char[] { '#', '?' });
+ this._fileName = index > -1 ? value.Substring(0, index) : value;
+ }
+ }
+
+ /// <summary>
+ /// Full entryPath
+ /// </summary>
+ [DataMember(Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ /// <summary>
+ /// Directory name
+ /// </summary>
+ [DataMember(Name = "dirName")]
+ public string DirectoryName { get; set; }
+
+ /// <summary>
+ /// Path to create file/directory
+ /// </summary>
+ [DataMember(Name = "path")]
+ public string Path { get; set; }
+
+ /// <summary>
+ /// The encoding to use to encode the file's content. Default is UTF8.
+ /// </summary>
+ [DataMember(Name = "encoding")]
+ public string Encoding { get; set; }
+
+ /// <summary>
+ /// Uri to get file
+ /// </summary>
+ ///
+ private string _uri;
+ [DataMember(Name = "uri")]
+ public string Uri
+ {
+ get
+ {
+ return this._uri;
+ }
+
+ set
+ {
+ int index = value.IndexOfAny(new char[] { '#', '?' });
+ this._uri = index > -1 ? value.Substring(0, index) : value;
+ }
+ }
+
+ /// <summary>
+ /// Size to truncate file
+ /// </summary>
+ [DataMember(Name = "size")]
+ public long Size { get; set; }
+
+ /// <summary>
+ /// Data to write in file
+ /// </summary>
+ [DataMember(Name = "data")]
+ public string Data { get; set; }
+
+ /// <summary>
+ /// Position the writing starts with
+ /// </summary>
+ [DataMember(Name = "position")]
+ public int Position { get; set; }
+
+ /// <summary>
+ /// Type of file system requested
+ /// </summary>
+ [DataMember(Name = "type")]
+ public int FileSystemType { get; set; }
+
+ /// <summary>
+ /// New file/directory name
+ /// </summary>
+ [DataMember(Name = "newName")]
+ public string NewName { get; set; }
+
+ /// <summary>
+ /// Destination directory to copy/move file/directory
+ /// </summary>
+ [DataMember(Name = "parent")]
+ public string Parent { get; set; }
+
+ /// <summary>
+ /// Options for getFile/getDirectory methods
+ /// </summary>
+ [DataMember(Name = "options")]
+ public CreatingOptions CreatingOpt { get; set; }
+
+ /// <summary>
+ /// Creates options object with default parameters
+ /// </summary>
+ public FileOptions()
+ {
+ this.SetDefaultValues(new StreamingContext());
+ }
+
+ /// <summary>
+ /// Initializes default values for class fields.
+ /// Implemented in separate method because default constructor is not invoked during deserialization.
+ /// </summary>
+ /// <param name="context"></param>
+ [OnDeserializing()]
+ public void SetDefaultValues(StreamingContext context)
+ {
+ this.Encoding = "UTF-8";
+ this.FilePath = "";
+ this.FileSystemType = -1;
+ }
+ }
+
+ /// <summary>
+ /// Stores image info
+ /// </summary>
+ [DataContract]
+ public class FileMetadata
+ {
+ [DataMember(Name = "fileName")]
+ public string FileName { get; set; }
+
+ [DataMember(Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ [DataMember(Name = "type")]
+ public string Type { get; set; }
+
+ [DataMember(Name = "lastModifiedDate")]
+ public string LastModifiedDate { get; set; }
+
+ [DataMember(Name = "size")]
+ public long Size { get; set; }
+
+ public FileMetadata(string filePath)
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ throw new FileNotFoundException("File doesn't exist");
+ }
+ else if (!isoFile.FileExists(filePath))
+ {
+ // attempt to get it from the resources
+ if (filePath.IndexOf("www") == 0)
+ {
+ Uri fileUri = new Uri(filePath, UriKind.Relative);
+ StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri);
+ if (streamInfo != null)
+ {
+ this.Size = streamInfo.Stream.Length;
+ this.FileName = filePath.Substring(filePath.LastIndexOf("/") + 1);
+ this.FullPath = filePath;
+ }
+ }
+ else
+ {
+ throw new FileNotFoundException("File doesn't exist");
+ }
+ }
+ else
+ {
+ //TODO get file size the other way if possible
+ using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.Read, isoFile))
+ {
+ this.Size = stream.Length;
+ }
+ this.FullPath = filePath;
+ this.FileName = System.IO.Path.GetFileName(filePath);
+ this.LastModifiedDate = isoFile.GetLastWriteTime(filePath).DateTime.ToString();
+ }
+ this.Type = MimeTypeMapper.GetMimeType(this.FileName);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Represents file or directory modification metadata
+ /// </summary>
+ [DataContract]
+ public class ModificationMetadata
+ {
+ /// <summary>
+ /// Modification time
+ /// </summary>
+ [DataMember]
+ public string modificationTime { get; set; }
+ }
+
+ /// <summary>
+ /// Represents file or directory entry
+ /// </summary>
+ [DataContract]
+ public class FileEntry
+ {
+
+ /// <summary>
+ /// File type
+ /// </summary>
+ [DataMember(Name = "isFile")]
+ public bool IsFile { get; set; }
+
+ /// <summary>
+ /// Directory type
+ /// </summary>
+ [DataMember(Name = "isDirectory")]
+ public bool IsDirectory { get; set; }
+
+ /// <summary>
+ /// File/directory name
+ /// </summary>
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Full path to file/directory
+ /// </summary>
+ [DataMember(Name = "fullPath")]
+ public string FullPath { get; set; }
+
+ public bool IsResource { get; set; }
+
+ public static FileEntry GetEntry(string filePath, bool bIsRes=false)
+ {
+ FileEntry entry = null;
+ try
+ {
+ entry = new FileEntry(filePath, bIsRes);
+
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Exception in GetEntry for filePath :: " + filePath + " " + ex.Message);
+ }
+ return entry;
+ }
+
+ /// <summary>
+ /// Creates object and sets necessary properties
+ /// </summary>
+ /// <param name="filePath"></param>
+ public FileEntry(string filePath, bool bIsRes = false)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ throw new ArgumentException();
+ }
+
+ if(filePath.Contains(" "))
+ {
+ Debug.WriteLine("FilePath with spaces :: " + filePath);
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ IsResource = bIsRes;
+ IsFile = isoFile.FileExists(filePath);
+ IsDirectory = isoFile.DirectoryExists(filePath);
+ if (IsFile)
+ {
+ this.Name = Path.GetFileName(filePath);
+ }
+ else if (IsDirectory)
+ {
+ this.Name = this.GetDirectoryName(filePath);
+ if (string.IsNullOrEmpty(Name))
+ {
+ this.Name = "/";
+ }
+ }
+ else
+ {
+ if (IsResource)
+ {
+ this.Name = Path.GetFileName(filePath);
+ }
+ else
+ {
+ throw new FileNotFoundException();
+ }
+ }
+
+ try
+ {
+ this.FullPath = filePath.Replace('\\', '/'); // new Uri(filePath).LocalPath;
+ }
+ catch (Exception)
+ {
+ this.FullPath = filePath;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Extracts directory name from path string
+ /// Path should refer to a directory, for example \foo\ or /foo.
+ /// </summary>
+ /// <param name="path"></param>
+ /// <returns></returns>
+ private string GetDirectoryName(string path)
+ {
+ if (String.IsNullOrEmpty(path))
+ {
+ return path;
+ }
+
+ string[] split = path.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
+ if (split.Length < 1)
+ {
+ return null;
+ }
+ else
+ {
+ return split[split.Length - 1];
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Represents info about requested file system
+ /// </summary>
+ [DataContract]
+ public class FileSystemInfo
+ {
+ /// <summary>
+ /// file system type
+ /// </summary>
+ [DataMember(Name = "name", IsRequired = true)]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Root directory entry
+ /// </summary>
+ [DataMember(Name = "root", EmitDefaultValue = false)]
+ public FileEntry Root { get; set; }
+
+ /// <summary>
+ /// Creates class instance
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="rootEntry"> Root directory</param>
+ public FileSystemInfo(string name, FileEntry rootEntry = null)
+ {
+ Name = name;
+ Root = rootEntry;
+ }
+ }
+
+ [DataContract]
+ public class CreatingOptions
+ {
+ /// <summary>
+ /// Create file/directory if is doesn't exist
+ /// </summary>
+ [DataMember(Name = "create")]
+ public bool Create { get; set; }
+
+ /// <summary>
+ /// Generate an exception if create=true and file/directory already exists
+ /// </summary>
+ [DataMember(Name = "exclusive")]
+ public bool Exclusive { get; set; }
+
+
+ }
+
+ // returns null value if it fails.
+ private string[] getOptionStrings(string options)
+ {
+ string[] optStings = null;
+ try
+ {
+ optStings = JSON.JsonHelper.Deserialize<string[]>(options);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), CurrentCommandCallbackId);
+ }
+ return optStings;
+ }
+
+ /// <summary>
+ /// Gets amount of free space available for Isolated Storage
+ /// </summary>
+ /// <param name="options">No options is needed for this method</param>
+ public void getFreeDiskSpace(string options)
+ {
+ string callbackId = getOptionStrings(options)[0];
+
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isoFile.AvailableFreeSpace), callbackId);
+ }
+ }
+ catch (IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Check if file exists
+ /// </summary>
+ /// <param name="options">File path</param>
+ public void testFileExists(string options)
+ {
+ IsDirectoryOrFileExist(options, false);
+ }
+
+ /// <summary>
+ /// Check if directory exists
+ /// </summary>
+ /// <param name="options">directory name</param>
+ public void testDirectoryExists(string options)
+ {
+ IsDirectoryOrFileExist(options, true);
+ }
+
+ /// <summary>
+ /// Check if file or directory exist
+ /// </summary>
+ /// <param name="options">File path/Directory name</param>
+ /// <param name="isDirectory">Flag to recognize what we should check</param>
+ public void IsDirectoryOrFileExist(string options, bool isDirectory)
+ {
+ string[] args = getOptionStrings(options);
+ string callbackId = args[1];
+ FileOptions fileOptions = JSON.JsonHelper.Deserialize<FileOptions>(args[0]);
+ string filePath = args[0];
+
+ if (fileOptions == null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ }
+
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool isExist;
+ if (isDirectory)
+ {
+ isExist = isoFile.DirectoryExists(fileOptions.DirectoryName);
+ }
+ else
+ {
+ isExist = isoFile.FileExists(fileOptions.FilePath);
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isExist), callbackId);
+ }
+ }
+ catch (IsolatedStorageException) // default handler throws INVALID_MODIFICATION_ERR
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+
+ }
+
+ public void readAsDataURL(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ int startPos = int.Parse(optStrings[1]);
+ int endPos = int.Parse(optStrings[2]);
+ string callbackId = optStrings[3];
+
+ if (filePath != null)
+ {
+ try
+ {
+ string base64URL = null;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+ string mimeType = MimeTypeMapper.GetMimeType(filePath);
+
+ using (IsolatedStorageFileStream stream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
+ {
+ string base64String = GetFileContent(stream);
+ base64URL = "data:" + mimeType + ";base64," + base64String;
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, base64URL), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+ }
+
+ public void readAsArrayBuffer(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ int startPos = int.Parse(optStrings[1]);
+ int endPos = int.Parse(optStrings[2]);
+ string callbackId = optStrings[3];
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId);
+ }
+
+ public void readAsBinaryString(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ int startPos = int.Parse(optStrings[1]);
+ int endPos = int.Parse(optStrings[2]);
+ string callbackId = optStrings[3];
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId);
+ }
+
+ public void readAsText(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string filePath = optStrings[0];
+ string encStr = optStrings[1];
+ int startPos = int.Parse(optStrings[2]);
+ int endPos = int.Parse(optStrings[3]);
+ string callbackId = optStrings[4];
+
+ try
+ {
+ string text = "";
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ readResourceAsText(options);
+ return;
+ }
+ Encoding encoding = Encoding.GetEncoding(encStr);
+
+ using (TextReader reader = new StreamReader(isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read), encoding))
+ {
+ text = reader.ReadToEnd();
+ if (startPos < 0)
+ {
+ startPos = Math.Max(text.Length + startPos, 0);
+ }
+ else if (startPos > 0)
+ {
+ startPos = Math.Min(text.Length, startPos);
+ }
+
+ if (endPos > 0)
+ {
+ endPos = Math.Min(text.Length, endPos);
+ }
+ else if (endPos < 0)
+ {
+ endPos = Math.Max(endPos + text.Length, 0);
+ }
+
+
+ text = text.Substring(startPos, endPos - startPos);
+
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reads application resource as a text
+ /// </summary>
+ /// <param name="options">Path to a resource</param>
+ public void readResourceAsText(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+ string pathToResource = optStrings[0];
+ string encStr = optStrings[1];
+ int start = int.Parse(optStrings[2]);
+ int endMarker = int.Parse(optStrings[3]);
+ string callbackId = optStrings[4];
+
+ try
+ {
+ if (pathToResource.StartsWith("/"))
+ {
+ pathToResource = pathToResource.Remove(0, 1);
+ }
+
+ var resource = Application.GetResourceStream(new Uri(pathToResource, UriKind.Relative));
+
+ if (resource == null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ string text;
+ StreamReader streamReader = new StreamReader(resource.Stream);
+ text = streamReader.ReadToEnd();
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ public void truncate(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+
+ string filePath = optStrings[0];
+ int size = int.Parse(optStrings[1]);
+ string callbackId = optStrings[2];
+
+ try
+ {
+ long streamLength = 0;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoFile.FileExists(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile))
+ {
+ if (0 <= size && size <= stream.Length)
+ {
+ stream.SetLength(size);
+ }
+ streamLength = stream.Length;
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, streamLength), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ //write:[filePath,data,position,isBinary,callbackId]
+ public void write(string options)
+ {
+ string[] optStrings = getOptionStrings(options);
+
+ string filePath = optStrings[0];
+ string data = optStrings[1];
+ int position = int.Parse(optStrings[2]);
+ bool isBinary = bool.Parse(optStrings[3]);
+ string callbackId = optStrings[4];
+
+ try
+ {
+ if (string.IsNullOrEmpty(data))
+ {
+ Debug.WriteLine("Expected some data to be send in the write command to {0}", filePath);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ // create the file if not exists
+ if (!isoFile.FileExists(filePath))
+ {
+ var file = isoFile.CreateFile(filePath);
+ file.Close();
+ }
+
+ using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile))
+ {
+ if (0 <= position && position <= stream.Length)
+ {
+ stream.SetLength(position);
+ }
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Seek(0, SeekOrigin.End);
+ writer.Write(data.ToCharArray());
+ }
+ }
+ }
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, data.Length), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Look up metadata about this entry.
+ /// </summary>
+ /// <param name="options">filePath to entry</param>
+ public void getMetadata(string options)
+ {
+ string[] optStings = getOptionStrings(options);
+ string filePath = optStings[0];
+ string callbackId = optStings[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.FileExists(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK,
+ new ModificationMetadata() { modificationTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString() }), callbackId);
+ }
+ else if (isoFile.DirectoryExists(filePath))
+ {
+ string modTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString();
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new ModificationMetadata() { modificationTime = modTime }), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+
+ }
+ }
+ catch (IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+
+ }
+
+
+ /// <summary>
+ /// Returns a File that represents the current state of the file that this FileEntry represents.
+ /// </summary>
+ /// <param name="filePath">filePath to entry</param>
+ /// <returns></returns>
+ public void getFileMetadata(string options)
+ {
+ string[] optStings = getOptionStrings(options);
+ string filePath = optStings[0];
+ string callbackId = optStings[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ FileMetadata metaData = new FileMetadata(filePath);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, metaData), callbackId);
+ }
+ catch (IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Look up the parent DirectoryEntry containing this Entry.
+ /// If this Entry is the root of IsolatedStorage, its parent is itself.
+ /// </summary>
+ /// <param name="options"></param>
+ public void getParent(string options)
+ {
+ string[] optStings = getOptionStrings(options);
+ string filePath = optStings[0];
+ string callbackId = optStings[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ FileEntry entry;
+
+ if (isoFile.FileExists(filePath) || isoFile.DirectoryExists(filePath))
+ {
+
+
+ string path = this.GetParentDirectory(filePath);
+ entry = FileEntry.GetEntry(path);
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry),callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+ }
+ }
+ }
+
+ public void remove(string options)
+ {
+ string[] args = getOptionStrings(options);
+ string filePath = args[0];
+ string callbackId = args[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ if (filePath == "/" || filePath == "" || filePath == @"\")
+ {
+ throw new Exception("Cannot delete root file system") ;
+ }
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.FileExists(filePath))
+ {
+ isoFile.DeleteFile(filePath);
+ }
+ else
+ {
+ if (isoFile.DirectoryExists(filePath))
+ {
+ isoFile.DeleteDirectory(filePath);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ return;
+ }
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK),callbackId);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ }
+ }
+ }
+ }
+
+ public void removeRecursively(string options)
+ {
+ string[] args = getOptionStrings(options);
+ string filePath = args[0];
+ string callbackId = args[1];
+
+ if (filePath != null)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ }
+ else
+ {
+ if (removeDirRecursively(filePath, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
+ }
+ }
+ }
+ }
+
+ public void readEntries(string options)
+ {
+ string[] args = getOptionStrings(options);
+ string filePath = args[0];
+ string callbackId = args[1];
+
+ if (filePath != null)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.DirectoryExists(filePath))
+ {
+ string path = File.AddSlashToDirectory(filePath);
+ List<FileEntry> entries = new List<FileEntry>();
+ string[] files = isoFile.GetFileNames(path + "*");
+ string[] dirs = isoFile.GetDirectoryNames(path + "*");
+ foreach (string file in files)
+ {
+ entries.Add(FileEntry.GetEntry(path + file));
+ }
+ foreach (string dir in dirs)
+ {
+ entries.Add(FileEntry.GetEntry(path + dir + "/"));
+ }
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entries),callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ }
+ }
+ }
+ }
+
+ public void requestFileSystem(string options)
+ {
+ // TODO: try/catch
+ string[] optVals = getOptionStrings(options);
+ //FileOptions fileOptions = new FileOptions();
+ int fileSystemType = int.Parse(optVals[0]);
+ double size = double.Parse(optVals[1]);
+ string callbackId = optVals[2];
+
+
+ IsolatedStorageFile.GetUserStoreForApplication();
+
+ if (size > (10 * 1024 * 1024)) // 10 MB, compier will clean this up!
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId);
+ return;
+ }
+
+ try
+ {
+ if (size != 0)
+ {
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ long availableSize = isoFile.AvailableFreeSpace;
+ if (size > availableSize)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId);
+ return;
+ }
+ }
+ }
+
+ if (fileSystemType == PERSISTENT)
+ {
+ // TODO: this should be in it's own folder to prevent overwriting of the app assets, which are also in ISO
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("persistent", FileEntry.GetEntry("/"))), callbackId);
+ }
+ else if (fileSystemType == TEMPORARY)
+ {
+ using (IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (!isoStorage.FileExists(TMP_DIRECTORY_NAME))
+ {
+ isoStorage.CreateDirectory(TMP_DIRECTORY_NAME);
+ }
+ }
+
+ string tmpFolder = "/" + TMP_DIRECTORY_NAME + "/";
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("temporary", FileEntry.GetEntry(tmpFolder))), callbackId);
+ }
+ else if (fileSystemType == RESOURCE)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("resource")), callbackId);
+ }
+ else if (fileSystemType == APPLICATION)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("application")), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+
+ public void resolveLocalFileSystemURI(string options)
+ {
+
+ string[] optVals = getOptionStrings(options);
+ string uri = optVals[0].Split('?')[0];
+ string callbackId = optVals[1];
+
+ if (uri != null)
+ {
+ // a single '/' is valid, however, '/someDir' is not, but '/tmp//somedir' and '///someDir' are valid
+ if (uri.StartsWith("/") && uri.IndexOf("//") < 0 && uri != "/")
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+ try
+ {
+ // fix encoded spaces
+ string path = Uri.UnescapeDataString(uri);
+
+ FileEntry uriEntry = FileEntry.GetEntry(path);
+ if (uriEntry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, uriEntry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+ }
+
+ public void copyTo(string options)
+ {
+ TransferTo(options, false);
+ }
+
+ public void moveTo(string options)
+ {
+ TransferTo(options, true);
+ }
+
+ public void getFile(string options)
+ {
+ GetFileOrDirectory(options, false);
+ }
+
+ public void getDirectory(string options)
+ {
+ GetFileOrDirectory(options, true);
+ }
+
+ #region internal functionality
+
+ /// <summary>
+ /// Retrieves the parent directory name of the specified path,
+ /// </summary>
+ /// <param name="path">Path</param>
+ /// <returns>Parent directory name</returns>
+ private string GetParentDirectory(string path)
+ {
+ if (String.IsNullOrEmpty(path) || path == "/")
+ {
+ return "/";
+ }
+
+ if (path.EndsWith(@"/") || path.EndsWith(@"\"))
+ {
+ return this.GetParentDirectory(Path.GetDirectoryName(path));
+ }
+
+ string result = Path.GetDirectoryName(path);
+ if (result == null)
+ {
+ result = "/";
+ }
+
+ return result;
+ }
+
+ private bool removeDirRecursively(string fullPath,string callbackId)
+ {
+ try
+ {
+ if (fullPath == "/")
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ return false;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ if (isoFile.DirectoryExists(fullPath))
+ {
+ string tempPath = File.AddSlashToDirectory(fullPath);
+ string[] files = isoFile.GetFileNames(tempPath + "*");
+ if (files.Length > 0)
+ {
+ foreach (string file in files)
+ {
+ isoFile.DeleteFile(tempPath + file);
+ }
+ }
+ string[] dirs = isoFile.GetDirectoryNames(tempPath + "*");
+ if (dirs.Length > 0)
+ {
+ foreach (string dir in dirs)
+ {
+ if (!removeDirRecursively(tempPath + dir, callbackId))
+ {
+ return false;
+ }
+ }
+ }
+ isoFile.DeleteDirectory(fullPath);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private bool CanonicalCompare(string pathA, string pathB)
+ {
+ string a = pathA.Replace("//", "/");
+ string b = pathB.Replace("//", "/");
+
+ return a.Equals(b, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /*
+ * copyTo:["fullPath","parent", "newName"],
+ * moveTo:["fullPath","parent", "newName"],
+ */
+ private void TransferTo(string options, bool move)
+ {
+ // TODO: try/catch
+ string[] optStrings = getOptionStrings(options);
+ string fullPath = optStrings[0];
+ string parent = optStrings[1];
+ string newFileName = optStrings[2];
+ string callbackId = optStrings[3];
+
+ char[] invalids = Path.GetInvalidPathChars();
+
+ if (newFileName.IndexOfAny(invalids) > -1 || newFileName.IndexOf(":") > -1 )
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+
+ try
+ {
+ if ((parent == null) || (string.IsNullOrEmpty(parent)) || (string.IsNullOrEmpty(fullPath)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ string parentPath = File.AddSlashToDirectory(parent);
+ string currentPath = fullPath;
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool isFileExist = isoFile.FileExists(currentPath);
+ bool isDirectoryExist = isoFile.DirectoryExists(currentPath);
+ bool isParentExist = isoFile.DirectoryExists(parentPath);
+
+ if ( ( !isFileExist && !isDirectoryExist ) || !isParentExist )
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+ string newName;
+ string newPath;
+ if (isFileExist)
+ {
+ newName = (string.IsNullOrEmpty(newFileName))
+ ? Path.GetFileName(currentPath)
+ : newFileName;
+
+ newPath = Path.Combine(parentPath, newName);
+
+ // sanity check ..
+ // cannot copy file onto itself
+ if (CanonicalCompare(newPath,currentPath)) //(parent + newFileName))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId);
+ return;
+ }
+ else if (isoFile.DirectoryExists(newPath))
+ {
+ // there is already a folder with the same name, operation is not allowed
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId);
+ return;
+ }
+ else if (isoFile.FileExists(newPath))
+ { // remove destination file if exists, in other case there will be exception
+ isoFile.DeleteFile(newPath);
+ }
+
+ if (move)
+ {
+ isoFile.MoveFile(currentPath, newPath);
+ }
+ else
+ {
+ isoFile.CopyFile(currentPath, newPath, true);
+ }
+ }
+ else
+ {
+ newName = (string.IsNullOrEmpty(newFileName))
+ ? currentPath
+ : newFileName;
+
+ newPath = Path.Combine(parentPath, newName);
+
+ if (move)
+ {
+ // remove destination directory if exists, in other case there will be exception
+ // target directory should be empty
+ if (!newPath.Equals(currentPath) && isoFile.DirectoryExists(newPath))
+ {
+ isoFile.DeleteDirectory(newPath);
+ }
+
+ isoFile.MoveDirectory(currentPath, newPath);
+ }
+ else
+ {
+ CopyDirectory(currentPath, newPath, isoFile);
+ }
+ }
+ FileEntry entry = FileEntry.GetEntry(newPath);
+ if (entry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex, callbackId))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+
+ private bool HandleException(Exception ex, string cbId="")
+ {
+ bool handled = false;
+ string callbackId = String.IsNullOrEmpty(cbId) ? this.CurrentCommandCallbackId : cbId;
+ if (ex is SecurityException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is FileNotFoundException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is ArgumentException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is IsolatedStorageException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId);
+ handled = true;
+ }
+ else if (ex is DirectoryNotFoundException)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ handled = true;
+ }
+ return handled;
+ }
+
+ private void CopyDirectory(string sourceDir, string destDir, IsolatedStorageFile isoFile)
+ {
+ string path = File.AddSlashToDirectory(sourceDir);
+
+ bool bExists = isoFile.DirectoryExists(destDir);
+
+ if (!bExists)
+ {
+ isoFile.CreateDirectory(destDir);
+ }
+
+ destDir = File.AddSlashToDirectory(destDir);
+
+ string[] files = isoFile.GetFileNames(path + "*");
+
+ if (files.Length > 0)
+ {
+ foreach (string file in files)
+ {
+ isoFile.CopyFile(path + file, destDir + file,true);
+ }
+ }
+ string[] dirs = isoFile.GetDirectoryNames(path + "*");
+ if (dirs.Length > 0)
+ {
+ foreach (string dir in dirs)
+ {
+ CopyDirectory(path + dir, destDir + dir, isoFile);
+ }
+ }
+ }
+
+ private void GetFileOrDirectory(string options, bool getDirectory)
+ {
+ FileOptions fOptions = new FileOptions();
+ string[] args = getOptionStrings(options);
+
+ fOptions.FullPath = args[0];
+ fOptions.Path = args[1];
+
+ string callbackId = args[3];
+
+ try
+ {
+ fOptions.CreatingOpt = JSON.JsonHelper.Deserialize<CreatingOptions>(args[2]);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId);
+ return;
+ }
+
+ try
+ {
+ if ((string.IsNullOrEmpty(fOptions.Path)) || (string.IsNullOrEmpty(fOptions.FullPath)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+
+ string path;
+
+ if (fOptions.Path.Split(':').Length > 2)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+
+ try
+ {
+ path = Path.Combine(fOptions.FullPath + "/", fOptions.Path);
+ }
+ catch (Exception)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId);
+ return;
+ }
+
+ using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
+ {
+ bool isFile = isoFile.FileExists(path);
+ bool isDirectory = isoFile.DirectoryExists(path);
+ bool create = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Create;
+ bool exclusive = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Exclusive;
+ if (create)
+ {
+ if (exclusive && (isoFile.FileExists(path) || isoFile.DirectoryExists(path)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, PATH_EXISTS_ERR), callbackId);
+ return;
+ }
+
+ // need to make sure the parent exists
+ // it is an error to create a directory whose immediate parent does not yet exist
+ // see issue: https://issues.apache.org/jira/browse/CB-339
+ string[] pathParts = path.Split('/');
+ string builtPath = pathParts[0];
+ for (int n = 1; n < pathParts.Length - 1; n++)
+ {
+ builtPath += "/" + pathParts[n];
+ if (!isoFile.DirectoryExists(builtPath))
+ {
+ Debug.WriteLine(String.Format("Error :: Parent folder \"{0}\" does not exist, when attempting to create \"{1}\"",builtPath,path));
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ return;
+ }
+ }
+
+ if ((getDirectory) && (!isDirectory))
+ {
+ isoFile.CreateDirectory(path);
+ }
+ else
+ {
+ if ((!getDirectory) && (!isFile))
+ {
+
+ IsolatedStorageFileStream fileStream = isoFile.CreateFile(path);
+ fileStream.Close();
+ }
+ }
+ }
+ else // (not create)
+ {
+ if ((!isFile) && (!isDirectory))
+ {
+ if (path.IndexOf("//www") == 0)
+ {
+ Uri fileUri = new Uri(path.Remove(0,2), UriKind.Relative);
+ StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri);
+ if (streamInfo != null)
+ {
+ FileEntry _entry = FileEntry.GetEntry(fileUri.OriginalString,true);
+
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, _entry), callbackId);
+
+ //using (BinaryReader br = new BinaryReader(streamInfo.Stream))
+ //{
+ // byte[] data = br.ReadBytes((int)streamInfo.Stream.Length);
+
+ //}
+
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+
+
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ return;
+ }
+ if (((getDirectory) && (!isDirectory)) || ((!getDirectory) && (!isFile)))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, TYPE_MISMATCH_ERR), callbackId);
+ return;
+ }
+ }
+ FileEntry entry = FileEntry.GetEntry(path);
+ if (entry != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId);
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!this.HandleException(ex))
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId);
+ }
+ }
+ }
+
+ private static string AddSlashToDirectory(string dirPath)
+ {
+ if (dirPath.EndsWith("/"))
+ {
+ return dirPath;
+ }
+ else
+ {
+ return dirPath + "/";
+ }
+ }
+
+ /// <summary>
+ /// Returns file content in a form of base64 string
+ /// </summary>
+ /// <param name="stream">File stream</param>
+ /// <returns>Base64 representation of the file</returns>
+ private string GetFileContent(Stream stream)
+ {
+ int streamLength = (int)stream.Length;
+ byte[] fileData = new byte[streamLength + 1];
+ stream.Read(fileData, 0, streamLength);
+ stream.Close();
+ return Convert.ToBase64String(fileData);
+ }
+
+ #endregion
+
+ }
+}