summaryrefslogtreecommitdiff
path: root/StoneIsland/plugins/cordova-plugin-file/src
diff options
context:
space:
mode:
Diffstat (limited to 'StoneIsland/plugins/cordova-plugin-file/src')
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/AssetFilesystem.java294
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/ContentFilesystem.java223
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/DirectoryManager.java134
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/EncodingException.java29
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/FileExistsException.java29
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/FileUtils.java1225
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/Filesystem.java331
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/InvalidModificationException.java30
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystem.java513
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystemURL.java64
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/NoModificationAllowedException.java29
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/PendingRequests.java94
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/TypeMismatchException.java30
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/android/build-extras.gradle47
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/browser/FileProxy.js1059
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h30
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m253
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.h157
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.m1119
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h32
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m750
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.h189
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.m1056
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.h32
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m733
-rw-r--r--StoneIsland/plugins/cordova-plugin-file/src/windows/FileProxy.js1190
26 files changed, 9672 insertions, 0 deletions
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/AssetFilesystem.java b/StoneIsland/plugins/cordova-plugin-file/src/android/AssetFilesystem.java
new file mode 100644
index 00000000..b035c40e
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/AssetFilesystem.java
@@ -0,0 +1,294 @@
+/*
+ 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.content.res.AssetManager;
+import android.net.Uri;
+
+import org.apache.cordova.CordovaResourceApi;
+import org.apache.cordova.LOG;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AssetFilesystem extends Filesystem {
+
+ private final AssetManager assetManager;
+
+ // A custom gradle hook creates the cdvasset.manifest file, which speeds up asset listing a tonne.
+ // See: http://stackoverflow.com/questions/16911558/android-assetmanager-list-incredibly-slow
+ private static Object listCacheLock = new Object();
+ private static boolean listCacheFromFile;
+ private static Map<String, String[]> listCache;
+ private static Map<String, Long> lengthCache;
+
+ private static final String LOG_TAG = "AssetFilesystem";
+
+ private void lazyInitCaches() {
+ synchronized (listCacheLock) {
+ if (listCache == null) {
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(assetManager.open("cdvasset.manifest"));
+ listCache = (Map<String, String[]>) ois.readObject();
+ lengthCache = (Map<String, Long>) ois.readObject();
+ listCacheFromFile = true;
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ // Asset manifest won't exist if the gradle hook isn't set up correctly.
+ } finally {
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException e) {
+ LOG.d(LOG_TAG, e.getLocalizedMessage());
+ }
+ }
+ }
+ if (listCache == null) {
+ LOG.w("AssetFilesystem", "Asset manifest not found. Recursive copies and directory listing will be slow.");
+ listCache = new HashMap<String, String[]>();
+ }
+ }
+ }
+ }
+
+ private String[] listAssets(String assetPath) throws IOException {
+ if (assetPath.startsWith("/")) {
+ assetPath = assetPath.substring(1);
+ }
+ if (assetPath.endsWith("/")) {
+ assetPath = assetPath.substring(0, assetPath.length() - 1);
+ }
+ lazyInitCaches();
+ String[] ret = listCache.get(assetPath);
+ if (ret == null) {
+ if (listCacheFromFile) {
+ ret = new String[0];
+ } else {
+ ret = assetManager.list(assetPath);
+ listCache.put(assetPath, ret);
+ }
+ }
+ return ret;
+ }
+
+ private long getAssetSize(String assetPath) throws FileNotFoundException {
+ if (assetPath.startsWith("/")) {
+ assetPath = assetPath.substring(1);
+ }
+ lazyInitCaches();
+ if (lengthCache != null) {
+ Long ret = lengthCache.get(assetPath);
+ if (ret == null) {
+ throw new FileNotFoundException("Asset not found: " + assetPath);
+ }
+ return ret;
+ }
+ CordovaResourceApi.OpenForReadResult offr = null;
+ try {
+ offr = resourceApi.openForRead(nativeUriForFullPath(assetPath));
+ long length = offr.length;
+ if (length < 0) {
+ // available() doesn't always yield the file size, but for assets it does.
+ length = offr.inputStream.available();
+ }
+ return length;
+ } catch (IOException e) {
+ FileNotFoundException fnfe = new FileNotFoundException("File not found: " + assetPath);
+ fnfe.initCause(e);
+ throw fnfe;
+ } finally {
+ if (offr != null) {
+ try {
+ offr.inputStream.close();
+ } catch (IOException e) {
+ LOG.d(LOG_TAG, e.getLocalizedMessage());
+ }
+ }
+ }
+ }
+
+ public AssetFilesystem(AssetManager assetManager, CordovaResourceApi resourceApi) {
+ super(Uri.parse("file:///android_asset/"), "assets", resourceApi);
+ this.assetManager = assetManager;
+ }
+
+ @Override
+ public Uri toNativeUri(LocalFilesystemURL inputURL) {
+ return nativeUriForFullPath(inputURL.path);
+ }
+
+ @Override
+ public LocalFilesystemURL toLocalUri(Uri inputURL) {
+ if (!"file".equals(inputURL.getScheme())) {
+ return null;
+ }
+ File f = new File(inputURL.getPath());
+ // Removes and duplicate /s (e.g. file:///a//b/c)
+ Uri resolvedUri = Uri.fromFile(f);
+ String rootUriNoTrailingSlash = rootUri.getEncodedPath();
+ rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
+ if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
+ return null;
+ }
+ String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
+ // Strip leading slash
+ if (!subPath.isEmpty()) {
+ subPath = subPath.substring(1);
+ }
+ Uri.Builder b = new Uri.Builder()
+ .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
+ .authority("localhost")
+ .path(name);
+ if (!subPath.isEmpty()) {
+ b.appendEncodedPath(subPath);
+ }
+ if (isDirectory(subPath) || inputURL.getPath().endsWith("/")) {
+ // Add trailing / for directories.
+ b.appendEncodedPath("");
+ }
+ return LocalFilesystemURL.parse(b.build());
+ }
+
+ private boolean isDirectory(String assetPath) {
+ try {
+ return listAssets(assetPath).length != 0;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ String pathNoSlashes = inputURL.path.substring(1);
+ if (pathNoSlashes.endsWith("/")) {
+ pathNoSlashes = pathNoSlashes.substring(0, pathNoSlashes.length() - 1);
+ }
+
+ String[] files;
+ try {
+ files = listAssets(pathNoSlashes);
+ } catch (IOException e) {
+ FileNotFoundException fnfe = new FileNotFoundException();
+ fnfe.initCause(e);
+ throw fnfe;
+ }
+
+ LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
+ for (int i = 0; i < files.length; ++i) {
+ entries[i] = localUrlforFullPath(new File(inputURL.path, files[i]).getPath());
+ }
+ return entries;
+ }
+
+ @Override
+ public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+ String path, JSONObject options, boolean directory)
+ throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ if (options != null && options.optBoolean("create")) {
+ throw new UnsupportedOperationException("Assets are read-only");
+ }
+
+ // Check whether the supplied path is absolute or relative
+ if (directory && !path.endsWith("/")) {
+ path += "/";
+ }
+
+ LocalFilesystemURL requestedURL;
+ if (path.startsWith("/")) {
+ requestedURL = localUrlforFullPath(normalizePath(path));
+ } else {
+ requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
+ }
+
+ // Throws a FileNotFoundException if it doesn't exist.
+ getFileMetadataForLocalURL(requestedURL);
+
+ boolean isDir = isDirectory(requestedURL.path);
+ if (directory && !isDir) {
+ throw new TypeMismatchException("path doesn't exist or is file");
+ } else if (!directory && isDir) {
+ throw new TypeMismatchException("path doesn't exist or is directory");
+ }
+
+ // Return the directory
+ return makeEntryForURL(requestedURL);
+ }
+
+ @Override
+ public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ JSONObject metadata = new JSONObject();
+ long size = inputURL.isDirectory ? 0 : getAssetSize(inputURL.path);
+ try {
+ metadata.put("size", size);
+ metadata.put("type", inputURL.isDirectory ? "text/directory" : resourceApi.getMimeType(toNativeUri(inputURL)));
+ metadata.put("name", new File(inputURL.path).getName());
+ metadata.put("fullPath", inputURL.path);
+ metadata.put("lastModifiedDate", 0);
+ } catch (JSONException e) {
+ return null;
+ }
+ return metadata;
+ }
+
+ @Override
+ public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+ return false;
+ }
+
+ @Override
+ long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset, boolean isBinary) throws NoModificationAllowedException, IOException {
+ throw new NoModificationAllowedException("Assets are read-only");
+ }
+
+ @Override
+ long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException, NoModificationAllowedException {
+ throw new NoModificationAllowedException("Assets are read-only");
+ }
+
+ @Override
+ String filesystemPathForURL(LocalFilesystemURL url) {
+ return new File(rootUri.getPath(), url.path).toString();
+ }
+
+ @Override
+ LocalFilesystemURL URLforFilesystemPath(String path) {
+ return null;
+ }
+
+ @Override
+ boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException {
+ throw new NoModificationAllowedException("Assets are read-only");
+ }
+
+ @Override
+ boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Assets are read-only");
+ }
+
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/ContentFilesystem.java b/StoneIsland/plugins/cordova-plugin-file/src/android/ContentFilesystem.java
new file mode 100644
index 00000000..6b983c08
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/ContentFilesystem.java
@@ -0,0 +1,223 @@
+/*
+ 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.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import org.apache.cordova.CordovaResourceApi;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ContentFilesystem extends Filesystem {
+
+ private final Context context;
+
+ public ContentFilesystem(Context context, CordovaResourceApi resourceApi) {
+ super(Uri.parse("content://"), "content", resourceApi);
+ this.context = context;
+ }
+
+ @Override
+ public Uri toNativeUri(LocalFilesystemURL inputURL) {
+ String authorityAndPath = inputURL.uri.getEncodedPath().substring(this.name.length() + 2);
+ if (authorityAndPath.length() < 2) {
+ return null;
+ }
+ String ret = "content://" + authorityAndPath;
+ String query = inputURL.uri.getEncodedQuery();
+ if (query != null) {
+ ret += '?' + query;
+ }
+ String frag = inputURL.uri.getEncodedFragment();
+ if (frag != null) {
+ ret += '#' + frag;
+ }
+ return Uri.parse(ret);
+ }
+
+ @Override
+ public LocalFilesystemURL toLocalUri(Uri inputURL) {
+ if (!"content".equals(inputURL.getScheme())) {
+ return null;
+ }
+ String subPath = inputURL.getEncodedPath();
+ if (subPath.length() > 0) {
+ subPath = subPath.substring(1);
+ }
+ Uri.Builder b = new Uri.Builder()
+ .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
+ .authority("localhost")
+ .path(name)
+ .appendPath(inputURL.getAuthority());
+ if (subPath.length() > 0) {
+ b.appendEncodedPath(subPath);
+ }
+ Uri localUri = b.encodedQuery(inputURL.getEncodedQuery())
+ .encodedFragment(inputURL.getEncodedFragment())
+ .build();
+ return LocalFilesystemURL.parse(localUri);
+ }
+
+ @Override
+ public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+ String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
+ throw new UnsupportedOperationException("getFile() not supported for content:. Use resolveLocalFileSystemURL instead.");
+ }
+
+ @Override
+ public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
+ throws NoModificationAllowedException {
+ Uri contentUri = toNativeUri(inputURL);
+ try {
+ context.getContentResolver().delete(contentUri, null, null);
+ } 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.
+ NoModificationAllowedException nmae = new NoModificationAllowedException("Deleting not supported for content uri: " + contentUri);
+ nmae.initCause(t);
+ throw nmae;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
+ throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Cannot remove content url");
+ }
+
+ @Override
+ public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ throw new UnsupportedOperationException("readEntriesAtLocalURL() not supported for content:. Use resolveLocalFileSystemURL instead.");
+ }
+
+ @Override
+ public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ long size = -1;
+ long lastModified = 0;
+ Uri nativeUri = toNativeUri(inputURL);
+ String mimeType = resourceApi.getMimeType(nativeUri);
+ Cursor cursor = openCursorForURL(nativeUri);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ Long sizeForCursor = resourceSizeForCursor(cursor);
+ if (sizeForCursor != null) {
+ size = sizeForCursor.longValue();
+ }
+ Long modified = lastModifiedDateForCursor(cursor);
+ if (modified != null)
+ lastModified = modified.longValue();
+ } else {
+ // Some content providers don't support cursors at all!
+ CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(nativeUri);
+ size = offr.length;
+ }
+ } catch (IOException e) {
+ FileNotFoundException fnfe = new FileNotFoundException();
+ fnfe.initCause(e);
+ throw fnfe;
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ JSONObject metadata = new JSONObject();
+ try {
+ metadata.put("size", size);
+ metadata.put("type", mimeType);
+ metadata.put("name", name);
+ metadata.put("fullPath", inputURL.path);
+ metadata.put("lastModifiedDate", lastModified);
+ } catch (JSONException e) {
+ return null;
+ }
+ return metadata;
+ }
+
+ @Override
+ public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
+ int offset, boolean isBinary) throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Couldn't write to file given its content URI");
+ }
+ @Override
+ public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
+ throws NoModificationAllowedException {
+ throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
+ }
+
+ protected Cursor openCursorForURL(Uri nativeUri) {
+ ContentResolver contentResolver = context.getContentResolver();
+ try {
+ return contentResolver.query(nativeUri, null, null, null, null);
+ } catch (UnsupportedOperationException e) {
+ return null;
+ }
+ }
+
+ private Long resourceSizeForCursor(Cursor cursor) {
+ int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+ if (columnIndex != -1) {
+ String sizeStr = cursor.getString(columnIndex);
+ if (sizeStr != null) {
+ return Long.parseLong(sizeStr);
+ }
+ }
+ return null;
+ }
+
+ protected Long lastModifiedDateForCursor(Cursor cursor) {
+ int columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
+ if (columnIndex == -1) {
+ columnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED);
+ }
+ if (columnIndex != -1) {
+ String dateStr = cursor.getString(columnIndex);
+ if (dateStr != null) {
+ return Long.parseLong(dateStr);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String filesystemPathForURL(LocalFilesystemURL url) {
+ File f = resourceApi.mapUriToFile(toNativeUri(url));
+ return f == null ? null : f.getAbsolutePath();
+ }
+
+ @Override
+ public LocalFilesystemURL URLforFilesystemPath(String path) {
+ // Returns null as we don't support reverse mapping back to content:// URLs
+ return null;
+ }
+
+ @Override
+ public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+ return true;
+ }
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/DirectoryManager.java b/StoneIsland/plugins/cordova-plugin-file/src/android/DirectoryManager.java
new file mode 100644
index 00000000..07af5ea2
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/DirectoryManager.java
@@ -0,0 +1,134 @@
+/*
+ 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 space in external storage
+ *
+ * @return Size in KB or -1 if not available
+ */
+ public static long getFreeExternalStorageSpace() {
+ String status = Environment.getExternalStorageState();
+ long freeSpaceInBytes = 0;
+
+ // Check if external storage exists
+ if (status.equals(Environment.MEDIA_MOUNTED)) {
+ freeSpaceInBytes = getFreeSpaceInBytes(Environment.getExternalStorageDirectory().getPath());
+ } else {
+ // If no external storage then return -1
+ return -1;
+ }
+
+ return freeSpaceInBytes / 1024;
+ }
+
+ /**
+ * Given a path return the number of free bytes in the filesystem containing the path.
+ *
+ * @param path to the file system
+ * @return free space in bytes
+ */
+ public static long getFreeSpaceInBytes(String path) {
+ try {
+ StatFs stat = new StatFs(path);
+ long blockSize = stat.getBlockSize();
+ long availableBlocks = stat.getAvailableBlocks();
+ return availableBlocks * blockSize;
+ } catch (IllegalArgumentException e) {
+ // The path was invalid. Just return 0 free bytes.
+ return 0;
+ }
+ }
+
+ /**
+ * 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/StoneIsland/plugins/cordova-plugin-file/src/android/EncodingException.java b/StoneIsland/plugins/cordova-plugin-file/src/android/EncodingException.java
new file mode 100644
index 00000000..e9e1653b
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-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/StoneIsland/plugins/cordova-plugin-file/src/android/FileExistsException.java b/StoneIsland/plugins/cordova-plugin-file/src/android/FileExistsException.java
new file mode 100644
index 00000000..5c4d83dc
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-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/StoneIsland/plugins/cordova-plugin-file/src/android/FileUtils.java b/StoneIsland/plugins/cordova-plugin-file/src/android/FileUtils.java
new file mode 100644
index 00000000..1d6e61fa
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/FileUtils.java
@@ -0,0 +1,1225 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+package org.apache.cordova.file;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Base64;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PermissionHelper;
+import org.apache.cordova.PluginResult;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class provides file and directory services to JavaScript.
+ */
+public class FileUtils extends CordovaPlugin {
+ private static final String LOG_TAG = "FileUtils";
+
+ public static int NOT_FOUND_ERR = 1;
+ public static int SECURITY_ERR = 2;
+ public static int ABORT_ERR = 3;
+
+ public static int NOT_READABLE_ERR = 4;
+ public static int ENCODING_ERR = 5;
+ public static int NO_MODIFICATION_ALLOWED_ERR = 6;
+ public static int INVALID_STATE_ERR = 7;
+ public static int SYNTAX_ERR = 8;
+ public static int INVALID_MODIFICATION_ERR = 9;
+ public static int QUOTA_EXCEEDED_ERR = 10;
+ public static int TYPE_MISMATCH_ERR = 11;
+ public static int PATH_EXISTS_ERR = 12;
+
+ /*
+ * Permission callback codes
+ */
+
+ public static final int ACTION_GET_FILE = 0;
+ public static final int ACTION_WRITE = 1;
+ public static final int ACTION_GET_DIRECTORY = 2;
+
+ public static final int WRITE = 3;
+ public static final int READ = 4;
+
+ public static int UNKNOWN_ERR = 1000;
+
+ private boolean configured = false;
+
+ private PendingRequests pendingRequests;
+
+
+
+ /*
+ * We need both read and write when accessing the storage, I think.
+ */
+
+ private String [] permissions = {
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE };
+
+ // This field exists only to support getEntry, below, which has been deprecated
+ private static FileUtils filePlugin;
+
+ private interface FileOp {
+ void run(JSONArray args) throws Exception;
+ }
+
+ private ArrayList<Filesystem> filesystems;
+
+ public void registerFilesystem(Filesystem fs) {
+ if (fs != null && filesystemForName(fs.name)== null) {
+ this.filesystems.add(fs);
+ }
+ }
+
+ private Filesystem filesystemForName(String name) {
+ for (Filesystem fs:filesystems) {
+ if (fs != null && fs.name != null && fs.name.equals(name)) {
+ return fs;
+ }
+ }
+ return null;
+ }
+
+ protected String[] getExtraFileSystemsPreference(Activity activity) {
+ String fileSystemsStr = preferences.getString("androidextrafilesystems", "files,files-external,documents,sdcard,cache,cache-external,assets,root");
+ return fileSystemsStr.split(",");
+ }
+
+ protected void registerExtraFileSystems(String[] filesystems, HashMap<String, String> availableFileSystems) {
+ HashSet<String> installedFileSystems = new HashSet<String>();
+
+ /* Register filesystems in order */
+ for (String fsName : filesystems) {
+ if (!installedFileSystems.contains(fsName)) {
+ String fsRoot = availableFileSystems.get(fsName);
+ if (fsRoot != null) {
+ File newRoot = new File(fsRoot);
+ if (newRoot.mkdirs() || newRoot.isDirectory()) {
+ registerFilesystem(new LocalFilesystem(fsName, webView.getContext(), webView.getResourceApi(), newRoot));
+ installedFileSystems.add(fsName);
+ } else {
+ LOG.d(LOG_TAG, "Unable to create root dir for filesystem \"" + fsName + "\", skipping");
+ }
+ } else {
+ LOG.d(LOG_TAG, "Unrecognized extra filesystem identifier: " + fsName);
+ }
+ }
+ }
+ }
+
+ protected HashMap<String, String> getAvailableFileSystems(Activity activity) {
+ Context context = activity.getApplicationContext();
+ HashMap<String, String> availableFileSystems = new HashMap<String,String>();
+
+ availableFileSystems.put("files", context.getFilesDir().getAbsolutePath());
+ availableFileSystems.put("documents", new File(context.getFilesDir(), "Documents").getAbsolutePath());
+ availableFileSystems.put("cache", context.getCacheDir().getAbsolutePath());
+ availableFileSystems.put("root", "/");
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ try {
+ availableFileSystems.put("files-external", context.getExternalFilesDir(null).getAbsolutePath());
+ availableFileSystems.put("sdcard", Environment.getExternalStorageDirectory().getAbsolutePath());
+ availableFileSystems.put("cache-external", context.getExternalCacheDir().getAbsolutePath());
+ }
+ catch(NullPointerException e) {
+ LOG.d(LOG_TAG, "External storage unavailable, check to see if USB Mass Storage Mode is on");
+ }
+ }
+
+ return availableFileSystems;
+ }
+
+ @Override
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ this.filesystems = new ArrayList<Filesystem>();
+ this.pendingRequests = new PendingRequests();
+
+ String tempRoot = null;
+ String persistentRoot = null;
+
+ Activity activity = cordova.getActivity();
+ String packageName = activity.getPackageName();
+
+ String location = preferences.getString("androidpersistentfilelocation", "internal");
+
+ tempRoot = activity.getCacheDir().getAbsolutePath();
+ if ("internal".equalsIgnoreCase(location)) {
+ persistentRoot = activity.getFilesDir().getAbsolutePath() + "/files/";
+ this.configured = true;
+ } else if ("compatibility".equalsIgnoreCase(location)) {
+ /*
+ * Fall-back to compatibility mode -- this is the logic implemented in
+ * earlier versions of this plugin, and should be maintained here so
+ * that apps which were originally deployed with older versions of the
+ * plugin can continue to provide access to files stored under those
+ * versions.
+ */
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ persistentRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
+ tempRoot = Environment.getExternalStorageDirectory().getAbsolutePath() +
+ "/Android/data/" + packageName + "/cache/";
+ } else {
+ persistentRoot = "/data/data/" + packageName;
+ }
+ this.configured = true;
+ }
+
+ if (this.configured) {
+ // Create the directories if they don't exist.
+ File tmpRootFile = new File(tempRoot);
+ File persistentRootFile = new File(persistentRoot);
+ tmpRootFile.mkdirs();
+ persistentRootFile.mkdirs();
+
+ // Register initial filesystems
+ // Note: The temporary and persistent filesystems need to be the first two
+ // registered, so that they will match window.TEMPORARY and window.PERSISTENT,
+ // per spec.
+ this.registerFilesystem(new LocalFilesystem("temporary", webView.getContext(), webView.getResourceApi(), tmpRootFile));
+ this.registerFilesystem(new LocalFilesystem("persistent", webView.getContext(), webView.getResourceApi(), persistentRootFile));
+ this.registerFilesystem(new ContentFilesystem(webView.getContext(), webView.getResourceApi()));
+ this.registerFilesystem(new AssetFilesystem(webView.getContext().getAssets(), webView.getResourceApi()));
+
+ registerExtraFileSystems(getExtraFileSystemsPreference(activity), getAvailableFileSystems(activity));
+
+ // Initialize static plugin reference for deprecated getEntry method
+ if (filePlugin == null) {
+ FileUtils.filePlugin = this;
+ }
+ } else {
+ LOG.e(LOG_TAG, "File plugin configuration error: Please set AndroidPersistentFileLocation in config.xml to one of \"internal\" (for new applications) or \"compatibility\" (for compatibility with previous versions)");
+ activity.finish();
+ }
+ }
+
+ public static FileUtils getFilePlugin() {
+ return filePlugin;
+ }
+
+ private Filesystem filesystemForURL(LocalFilesystemURL localURL) {
+ if (localURL == null) return null;
+ return filesystemForName(localURL.fsName);
+ }
+
+ @Override
+ public Uri remapUri(Uri uri) {
+ // Remap only cdvfile: URLs (not content:).
+ if (!LocalFilesystemURL.FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
+ return null;
+ }
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ return null;
+ }
+ String path = fs.filesystemPathForURL(inputURL);
+ if (path != null) {
+ return Uri.parse("file://" + fs.filesystemPathForURL(inputURL));
+ }
+ return null;
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ public boolean execute(String action, final String rawArgs, final CallbackContext callbackContext) {
+ if (!configured) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "File plugin is not configured. Please see the README.md file for details on how to update config.xml"));
+ return true;
+ }
+ if (action.equals("testSaveLocationExists")) {
+ threadhelper(new FileOp() {
+ public void run(JSONArray args) {
+ boolean b = DirectoryManager.testSaveLocationExists();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("getFreeDiskSpace")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) {
+ // The getFreeDiskSpace plugin API is not documented, but some apps call it anyway via exec().
+ // For compatibility it always returns free space in the primary external storage, and
+ // does NOT fallback to internal store if external storage is unavailable.
+ long l = DirectoryManager.getFreeExternalStorageSpace();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("testFileExists")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException {
+ String fname=args.getString(0);
+ boolean b = DirectoryManager.testFileExists(fname);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("testDirectoryExists")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException {
+ String fname=args.getString(0);
+ boolean b = DirectoryManager.testFileExists(fname);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("readAsText")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
+ String encoding = args.getString(1);
+ int start = args.getInt(2);
+ int end = args.getInt(3);
+ String fname=args.getString(0);
+ readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("readAsDataURL")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
+ int start = args.getInt(1);
+ int end = args.getInt(2);
+ String fname=args.getString(0);
+ readFileAs(fname, start, end, callbackContext, null, -1);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("readAsArrayBuffer")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
+ int start = args.getInt(1);
+ int end = args.getInt(2);
+ String fname=args.getString(0);
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("readAsBinaryString")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, MalformedURLException {
+ int start = args.getInt(1);
+ int end = args.getInt(2);
+ String fname=args.getString(0);
+ readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("write")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
+ String fname=args.getString(0);
+ String nativeURL = resolveLocalFileSystemURI(fname).getString("nativeURL");
+ String data=args.getString(1);
+ int offset=args.getInt(2);
+ Boolean isBinary=args.getBoolean(3);
+
+ if(needPermission(nativeURL, WRITE)) {
+ getWritePermission(rawArgs, ACTION_WRITE, callbackContext);
+ }
+ else {
+ long fileSize = write(fname, data, offset, isBinary);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("truncate")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
+ String fname=args.getString(0);
+ int offset=args.getInt(1);
+ long fileSize = truncateFile(fname, offset);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("requestAllFileSystems")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws IOException, JSONException {
+ callbackContext.success(requestAllFileSystems());
+ }
+ }, rawArgs, callbackContext);
+ } else if (action.equals("requestAllPaths")) {
+ cordova.getThreadPool().execute(
+ new Runnable() {
+ public void run() {
+ try {
+ callbackContext.success(requestAllPaths());
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ );
+ } else if (action.equals("requestFileSystem")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException {
+ int fstype = args.getInt(0);
+ long requiredSize = args.optLong(1);
+ requestFileSystem(fstype, requiredSize, callbackContext);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("resolveLocalFileSystemURI")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws IOException, JSONException {
+ String fname=args.getString(0);
+ JSONObject obj = resolveLocalFileSystemURI(fname);
+ callbackContext.success(obj);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("getFileMetadata")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
+ String fname=args.getString(0);
+ JSONObject obj = getFileMetadata(fname);
+ callbackContext.success(obj);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("getParent")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, IOException {
+ String fname=args.getString(0);
+ JSONObject obj = getParent(fname);
+ callbackContext.success(obj);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("getDirectory")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ String dirname = args.getString(0);
+ String path = args.getString(1);
+ String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
+ boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
+
+ if(containsCreate && needPermission(nativeURL, WRITE)) {
+ getWritePermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
+ }
+ else if(!containsCreate && needPermission(nativeURL, READ)) {
+ getReadPermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
+ }
+ else {
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
+ callbackContext.success(obj);
+ }
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("getFile")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ String dirname = args.getString(0);
+ String path = args.getString(1);
+ String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
+ boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
+
+ if(containsCreate && needPermission(nativeURL, WRITE)) {
+ getWritePermission(rawArgs, ACTION_GET_FILE, callbackContext);
+ }
+ else if(!containsCreate && needPermission(nativeURL, READ)) {
+ getReadPermission(rawArgs, ACTION_GET_FILE, callbackContext);
+ }
+ else {
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
+ callbackContext.success(obj);
+ }
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("remove")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, NoModificationAllowedException, InvalidModificationException, MalformedURLException {
+ String fname=args.getString(0);
+ boolean success = remove(fname);
+ if (success) {
+ callbackContext.success();
+ } else {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ }
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("removeRecursively")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, FileExistsException, MalformedURLException, NoModificationAllowedException {
+ String fname=args.getString(0);
+ boolean success = removeRecursively(fname);
+ if (success) {
+ callbackContext.success();
+ } else {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ }
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("moveTo")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ String fname=args.getString(0);
+ String newParent=args.getString(1);
+ String newName=args.getString(2);
+ JSONObject entry = transferTo(fname, newParent, newName, true);
+ callbackContext.success(entry);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("copyTo")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ String fname=args.getString(0);
+ String newParent=args.getString(1);
+ String newName=args.getString(2);
+ JSONObject entry = transferTo(fname, newParent, newName, false);
+ callbackContext.success(entry);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("readEntries")) {
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
+ String fname=args.getString(0);
+ JSONArray entries = readEntries(fname);
+ callbackContext.success(entries);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else if (action.equals("_getLocalFilesystemPath")) {
+ // Internal method for testing: Get the on-disk location of a local filesystem url.
+ // [Currently used for testing file-transfer]
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
+ String localURLstr = args.getString(0);
+ String fname = filesystemPathForURL(localURLstr);
+ callbackContext.success(fname);
+ }
+ }, rawArgs, callbackContext);
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ private void getReadPermission(String rawArgs, int action, CallbackContext callbackContext) {
+ int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
+ PermissionHelper.requestPermission(this, requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+
+ private void getWritePermission(String rawArgs, int action, CallbackContext callbackContext) {
+ int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
+ PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+
+ private boolean hasReadPermission() {
+ return PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+
+ private boolean hasWritePermission() {
+ return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+
+ private boolean needPermission(String nativeURL, int permissionType) throws JSONException {
+ JSONObject j = requestAllPaths();
+ ArrayList<String> allowedStorageDirectories = new ArrayList<String>();
+ allowedStorageDirectories.add(j.getString("applicationDirectory"));
+ allowedStorageDirectories.add(j.getString("applicationStorageDirectory"));
+ if(j.has("externalApplicationStorageDirectory")) {
+ allowedStorageDirectories.add(j.getString("externalApplicationStorageDirectory"));
+ }
+
+ if(permissionType == READ && hasReadPermission()) {
+ return false;
+ }
+ else if(permissionType == WRITE && hasWritePermission()) {
+ return false;
+ }
+
+ // Permission required if the native url lies outside the allowed storage directories
+ for(String directory : allowedStorageDirectories) {
+ if(nativeURL.startsWith(directory)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ public LocalFilesystemURL resolveNativeUri(Uri nativeUri) {
+ LocalFilesystemURL localURL = null;
+
+ // Try all installed filesystems. Return the best matching URL
+ // (determined by the shortest resulting URL)
+ for (Filesystem fs : filesystems) {
+ LocalFilesystemURL url = fs.toLocalUri(nativeUri);
+ if (url != null) {
+ // A shorter fullPath implies that the filesystem is a better
+ // match for the local path than the previous best.
+ if (localURL == null || (url.uri.toString().length() < localURL.toString().length())) {
+ localURL = url;
+ }
+ }
+ }
+ return localURL;
+ }
+
+ /*
+ * These two native-only methods can be used by other plugins to translate between
+ * device file system paths and URLs. By design, there is no direct JavaScript
+ * interface to these methods.
+ */
+
+ public String filesystemPathForURL(String localURLstr) throws MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(localURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.filesystemPathForURL(inputURL);
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+ public LocalFilesystemURL filesystemURLforLocalPath(String localPath) {
+ LocalFilesystemURL localURL = null;
+ int shortestFullPath = 0;
+
+ // Try all installed filesystems. Return the best matching URL
+ // (determined by the shortest resulting URL)
+ for (Filesystem fs: filesystems) {
+ LocalFilesystemURL url = fs.URLforFilesystemPath(localPath);
+ if (url != null) {
+ // A shorter fullPath implies that the filesystem is a better
+ // match for the local path than the previous best.
+ if (localURL == null || (url.path.length() < shortestFullPath)) {
+ localURL = url;
+ shortestFullPath = url.path.length();
+ }
+ }
+ }
+ return localURL;
+ }
+
+
+ /* helper to execute functions async and handle the result codes
+ *
+ */
+ private void threadhelper(final FileOp f, final String rawArgs, final CallbackContext callbackContext){
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ JSONArray args = new JSONArray(rawArgs);
+ f.run(args);
+ } catch ( Exception e) {
+ if( e instanceof EncodingException){
+ callbackContext.error(FileUtils.ENCODING_ERR);
+ } else if(e instanceof FileNotFoundException) {
+ callbackContext.error(FileUtils.NOT_FOUND_ERR);
+ } else if(e instanceof FileExistsException) {
+ callbackContext.error(FileUtils.PATH_EXISTS_ERR);
+ } else if(e instanceof NoModificationAllowedException ) {
+ callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+ } else if(e instanceof InvalidModificationException ) {
+ callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
+ } else if(e instanceof MalformedURLException ) {
+ callbackContext.error(FileUtils.ENCODING_ERR);
+ } else if(e instanceof IOException ) {
+ callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
+ } else if(e instanceof EncodingException ) {
+ callbackContext.error(FileUtils.ENCODING_ERR);
+ } else if(e instanceof TypeMismatchException ) {
+ callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
+ } else if(e instanceof JSONException ) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ } else if (e instanceof SecurityException) {
+ callbackContext.error(FileUtils.SECURITY_ERR);
+ } else {
+ e.printStackTrace();
+ callbackContext.error(FileUtils.UNKNOWN_ERR);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Allows the user to look up the Entry for a file or directory referred to by a local URI.
+ *
+ * @param uriString of the file/directory to look up
+ * @return a JSONObject representing a Entry from the filesystem
+ * @throws MalformedURLException if the url is not valid
+ * @throws FileNotFoundException if the file does not exist
+ * @throws IOException if the user can't read the file
+ * @throws JSONException
+ */
+ private JSONObject resolveLocalFileSystemURI(String uriString) throws IOException, JSONException {
+ if (uriString == null) {
+ throw new MalformedURLException("Unrecognized filesystem URL");
+ }
+ Uri uri = Uri.parse(uriString);
+ boolean isNativeUri = false;
+
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
+ if (inputURL == null) {
+ /* Check for file://, content:// urls */
+ inputURL = resolveNativeUri(uri);
+ isNativeUri = true;
+ }
+
+ try {
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ if (fs.exists(inputURL)) {
+ if (!isNativeUri) {
+ // If not already resolved as native URI, resolve to a native URI and back to
+ // fix the terminating slash based on whether the entry is a directory or file.
+ inputURL = fs.toLocalUri(fs.toNativeUri(inputURL));
+ }
+
+ return fs.getEntryForLocalURL(inputURL);
+ }
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ throw new FileNotFoundException();
+ }
+
+ /**
+ * Read the list of files from this directory.
+ *
+ * @return a JSONArray containing JSONObjects that represent Entry objects.
+ * @throws FileNotFoundException if the directory is not found.
+ * @throws JSONException
+ * @throws MalformedURLException
+ */
+ private JSONArray readEntries(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.readEntriesAtLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+ /**
+ * A setup method that handles the move/copy of files/directories
+ *
+ * @param newName for the file directory to be called, if null use existing file name
+ * @param move if false do a copy, if true do a move
+ * @return a Entry object
+ * @throws NoModificationAllowedException
+ * @throws IOException
+ * @throws InvalidModificationException
+ * @throws EncodingException
+ * @throws JSONException
+ * @throws FileExistsException
+ */
+ private JSONObject transferTo(String srcURLstr, String destURLstr, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+ if (srcURLstr == null || destURLstr == null) {
+ // either no source or no destination provided
+ throw new FileNotFoundException();
+ }
+
+ LocalFilesystemURL srcURL = LocalFilesystemURL.parse(srcURLstr);
+ LocalFilesystemURL destURL = LocalFilesystemURL.parse(destURLstr);
+
+ Filesystem srcFs = this.filesystemForURL(srcURL);
+ Filesystem destFs = this.filesystemForURL(destURL);
+
+ // Check for invalid file name
+ if (newName != null && newName.contains(":")) {
+ throw new EncodingException("Bad file name");
+ }
+
+ return destFs.copyFileToURL(destURL, newName, srcFs, srcURL, move);
+ }
+
+ /**
+ * Deletes a directory and all of its contents, if any. In the event of an error
+ * [e.g. trying to delete a directory that contains a file that cannot be removed],
+ * some of the contents of the directory may be deleted.
+ * It is an error to attempt to delete the root directory of a filesystem.
+ *
+ * @return a boolean representing success of failure
+ * @throws FileExistsException
+ * @throws NoModificationAllowedException
+ * @throws MalformedURLException
+ */
+ private boolean removeRecursively(String baseURLstr) throws FileExistsException, NoModificationAllowedException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+ // You can't delete the root directory.
+ if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
+ throw new NoModificationAllowedException("You can't delete the root directory");
+ }
+
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.recursiveRemoveFileAtLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+
+ /**
+ * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty.
+ * It is an error to attempt to delete the root directory of a filesystem.
+ *
+ * @return a boolean representing success of failure
+ * @throws NoModificationAllowedException
+ * @throws InvalidModificationException
+ * @throws MalformedURLException
+ */
+ private boolean remove(String baseURLstr) throws NoModificationAllowedException, InvalidModificationException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+ // You can't delete the root directory.
+ if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
+
+ throw new NoModificationAllowedException("You can't delete the root directory");
+ }
+
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.removeFileAtLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+ /**
+ * Creates or looks up a file.
+ *
+ * @param baseURLstr base directory
+ * @param path file/directory to lookup or create
+ * @param options specify whether to create or not
+ * @param directory if true look up directory, if false look up file
+ * @return a Entry object
+ * @throws FileExistsException
+ * @throws IOException
+ * @throws TypeMismatchException
+ * @throws EncodingException
+ * @throws JSONException
+ */
+ private JSONObject getFile(String baseURLstr, String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getFileForLocalURL(inputURL, path, options, directory);
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+
+ }
+
+ /**
+ * Look up the parent DirectoryEntry containing this Entry.
+ * If this Entry is the root of its filesystem, its parent is itself.
+ */
+ private JSONObject getParent(String baseURLstr) throws JSONException, IOException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getParentForLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+ /**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ *
+ * @return returns a JSONObject represent a W3C File object
+ */
+ private JSONObject getFileMetadata(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+ return fs.getFileMetadataForLocalURL(inputURL);
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+ /**
+ * Requests a filesystem in which to store application data.
+ *
+ * @param type of file system requested
+ * @param requiredSize required free space in the file system in bytes
+ * @param callbackContext context for returning the result or error
+ * @throws JSONException
+ */
+ private void requestFileSystem(int type, long requiredSize, final CallbackContext callbackContext) throws JSONException {
+ Filesystem rootFs = null;
+ try {
+ rootFs = this.filesystems.get(type);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // Pass null through
+ }
+ if (rootFs == null) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR));
+ } else {
+ // If a nonzero required size was specified, check that the retrieved filesystem has enough free space.
+ long availableSize = 0;
+ if (requiredSize > 0) {
+ availableSize = rootFs.getFreeSpaceInBytes();
+ }
+
+ if (availableSize < requiredSize) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
+ } else {
+ JSONObject fs = new JSONObject();
+ fs.put("name", rootFs.name);
+ fs.put("root", rootFs.getRootEntry());
+ callbackContext.success(fs);
+ }
+ }
+ }
+
+ /**
+ * Requests a filesystem in which to store application data.
+ *
+ * @return a JSONObject representing the file system
+ */
+ private JSONArray requestAllFileSystems() throws IOException, JSONException {
+ JSONArray ret = new JSONArray();
+ for (Filesystem fs : filesystems) {
+ ret.put(fs.getRootEntry());
+ }
+ return ret;
+ }
+
+ private static String toDirUrl(File f) {
+ return Uri.fromFile(f).toString() + '/';
+ }
+
+ private JSONObject requestAllPaths() throws JSONException {
+ Context context = cordova.getActivity();
+ JSONObject ret = new JSONObject();
+ ret.put("applicationDirectory", "file:///android_asset/");
+ ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
+ ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
+ ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ try {
+ ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
+ ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
+ ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
+ ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
+ }
+ catch(NullPointerException e) {
+ /* If external storage is unavailable, context.getExternal* returns null */
+ LOG.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a JSON object representing the given File. Internal APIs should be modified
+ * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
+ *
+ * @param file the File to convert
+ * @return a JSON representation of the given File
+ * @throws JSONException
+ */
+ public JSONObject getEntryForFile(File file) throws JSONException {
+ JSONObject entry;
+
+ for (Filesystem fs : filesystems) {
+ entry = fs.makeEntryForFile(file);
+ if (entry != null) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a JSON object representing the given File. Deprecated, as this is only used by
+ * FileTransfer, and because it is a static method that should really be an instance method,
+ * since it depends on the actual filesystem roots in use. Internal APIs should be modified
+ * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
+ *
+ * @param file the File to convert
+ * @return a JSON representation of the given File
+ * @throws JSONException
+ */
+ @Deprecated
+ public static JSONObject getEntry(File file) throws JSONException {
+ if (getFilePlugin() != null) {
+ return getFilePlugin().getEntryForFile(file);
+ }
+ return null;
+ }
+
+ /**
+ * Read the contents of a file.
+ * This is done in a background thread; the result is sent to the callback.
+ *
+ * @param start Start position in the file.
+ * @param end End position to stop at (exclusive).
+ * @param callbackContext The context through which to send the result.
+ * @param encoding The encoding to return contents as. Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
+ * @param resultType The desired type of data to send to the callback.
+ * @return Contents of file.
+ */
+ public void readFileAs(final String srcURLstr, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) throws MalformedURLException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+
+ fs.readFileAtURL(inputURL, start, end, new Filesystem.ReadFileCallback() {
+ public void handleData(InputStream inputStream, String contentType) {
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final int BUFFER_SIZE = 8192;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ for (;;) {
+ int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+ if (bytesRead <= 0) {
+ break;
+ }
+ os.write(buffer, 0, bytesRead);
+ }
+
+ PluginResult result;
+ switch (resultType) {
+ case PluginResult.MESSAGE_TYPE_STRING:
+ result = new PluginResult(PluginResult.Status.OK, os.toString(encoding));
+ break;
+ case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+ result = new PluginResult(PluginResult.Status.OK, os.toByteArray());
+ break;
+ case PluginResult.MESSAGE_TYPE_BINARYSTRING:
+ result = new PluginResult(PluginResult.Status.OK, os.toByteArray(), true);
+ break;
+ default: // Base64.
+ byte[] base64 = Base64.encode(os.toByteArray(), Base64.NO_WRAP);
+ String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
+ result = new PluginResult(PluginResult.Status.OK, s);
+ }
+
+ callbackContext.sendPluginResult(result);
+ } catch (IOException e) {
+ LOG.d(LOG_TAG, e.getLocalizedMessage());
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+ }
+ }
+ });
+
+
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ } catch (FileNotFoundException e) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
+ } catch (IOException e) {
+ LOG.d(LOG_TAG, e.getLocalizedMessage());
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+ }
+ }
+
+
+ /**
+ * Write contents of file.
+ *
+ * @param data The contents of the file.
+ * @param offset The position to begin writing the file.
+ * @param isBinary True if the file contents are base64-encoded binary data
+ */
+ /**/
+ public long write(String srcURLstr, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+
+ long x = fs.writeToFileAtURL(inputURL, data, offset, isBinary); LOG.d("TEST",srcURLstr + ": "+x); return x;
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+
+ }
+
+ /**
+ * Truncate the file to size
+ */
+ private long truncateFile(String srcURLstr, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
+ try {
+ LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
+ Filesystem fs = this.filesystemForURL(inputURL);
+ if (fs == null) {
+ throw new MalformedURLException("No installed handlers for this URL");
+ }
+
+ return fs.truncateFileAtURL(inputURL, size);
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+
+
+ /*
+ * Handle the response
+ */
+
+ public void onRequestPermissionResult(int requestCode, String[] permissions,
+ int[] grantResults) throws JSONException {
+
+ final PendingRequests.Request req = pendingRequests.getAndRemove(requestCode);
+ if (req != null) {
+ for(int r:grantResults)
+ {
+ if(r == PackageManager.PERMISSION_DENIED)
+ {
+ req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR));
+ return;
+ }
+ }
+ switch(req.getAction())
+ {
+ case ACTION_GET_FILE:
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ String dirname = args.getString(0);
+
+ String path = args.getString(1);
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
+ req.getCallbackContext().success(obj);
+ }
+ }, req.getRawArgs(), req.getCallbackContext());
+ break;
+ case ACTION_GET_DIRECTORY:
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+ String dirname = args.getString(0);
+
+ String path = args.getString(1);
+ JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
+ req.getCallbackContext().success(obj);
+ }
+ }, req.getRawArgs(), req.getCallbackContext());
+ break;
+ case ACTION_WRITE:
+ threadhelper( new FileOp( ){
+ public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
+ String fname=args.getString(0);
+ String data=args.getString(1);
+ int offset=args.getInt(2);
+ Boolean isBinary=args.getBoolean(3);
+ long fileSize = write(fname, data, offset, isBinary);
+ req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+ }
+ }, req.getRawArgs(), req.getCallbackContext());
+ break;
+ }
+ } else {
+ LOG.d(LOG_TAG, "Received permission callback for unknown request code");
+ }
+ }
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/Filesystem.java b/StoneIsland/plugins/cordova-plugin-file/src/android/Filesystem.java
new file mode 100644
index 00000000..c69d3bdd
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/Filesystem.java
@@ -0,0 +1,331 @@
+/*
+ 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.net.Uri;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.cordova.CordovaResourceApi;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public abstract class Filesystem {
+
+ protected final Uri rootUri;
+ protected final CordovaResourceApi resourceApi;
+ public final String name;
+ private JSONObject rootEntry;
+
+ public Filesystem(Uri rootUri, String name, CordovaResourceApi resourceApi) {
+ this.rootUri = rootUri;
+ this.name = name;
+ this.resourceApi = resourceApi;
+ }
+
+ public interface ReadFileCallback {
+ public void handleData(InputStream inputStream, String contentType) throws IOException;
+ }
+
+ public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Uri nativeURL) {
+ try {
+ String path = inputURL.path;
+ int end = path.endsWith("/") ? 1 : 0;
+ String[] parts = path.substring(0, path.length() - end).split("/+");
+ String fileName = parts[parts.length - 1];
+
+ JSONObject entry = new JSONObject();
+ entry.put("isFile", !inputURL.isDirectory);
+ entry.put("isDirectory", inputURL.isDirectory);
+ entry.put("name", fileName);
+ entry.put("fullPath", path);
+ // The file system can't be specified, as it would lead to an infinite loop,
+ // but the filesystem name can be.
+ entry.put("filesystemName", inputURL.fsName);
+ // Backwards compatibility
+ entry.put("filesystem", "temporary".equals(inputURL.fsName) ? 0 : 1);
+
+ String nativeUrlStr = nativeURL.toString();
+ if (inputURL.isDirectory && !nativeUrlStr.endsWith("/")) {
+ nativeUrlStr += "/";
+ }
+ entry.put("nativeURL", nativeUrlStr);
+ return entry;
+ } catch (JSONException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public JSONObject makeEntryForURL(LocalFilesystemURL inputURL) {
+ Uri nativeUri = toNativeUri(inputURL);
+ return nativeUri == null ? null : makeEntryForURL(inputURL, nativeUri);
+ }
+
+ public JSONObject makeEntryForNativeUri(Uri nativeUri) {
+ LocalFilesystemURL inputUrl = toLocalUri(nativeUri);
+ return inputUrl == null ? null : makeEntryForURL(inputUrl, nativeUri);
+ }
+
+ public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+ return makeEntryForURL(inputURL);
+ }
+
+ public JSONObject makeEntryForFile(File file) {
+ return makeEntryForNativeUri(Uri.fromFile(file));
+ }
+
+ abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
+ JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
+
+ abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
+
+ abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
+
+ abstract LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException;
+
+ public final JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ LocalFilesystemURL[] children = listChildren(inputURL);
+ JSONArray entries = new JSONArray();
+ if (children != null) {
+ for (LocalFilesystemURL url : children) {
+ entries.put(makeEntryForURL(url));
+ }
+ }
+ return entries;
+ }
+
+ abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
+
+ public Uri getRootUri() {
+ return rootUri;
+ }
+
+ public boolean exists(LocalFilesystemURL inputURL) {
+ try {
+ getFileMetadataForLocalURL(inputURL);
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public Uri nativeUriForFullPath(String fullPath) {
+ Uri ret = null;
+ if (fullPath != null) {
+ String encodedPath = Uri.fromFile(new File(fullPath)).getEncodedPath();
+ if (encodedPath.startsWith("/")) {
+ encodedPath = encodedPath.substring(1);
+ }
+ ret = rootUri.buildUpon().appendEncodedPath(encodedPath).build();
+ }
+ return ret;
+ }
+
+ public LocalFilesystemURL localUrlforFullPath(String fullPath) {
+ Uri nativeUri = nativeUriForFullPath(fullPath);
+ if (nativeUri != null) {
+ return toLocalUri(nativeUri);
+ }
+ return null;
+ }
+
+ /**
+ * Removes multiple repeated //s, and collapses processes ../s.
+ */
+ protected static String normalizePath(String rawPath) {
+ // If this is an absolute path, trim the leading "/" and replace it later
+ boolean isAbsolutePath = rawPath.startsWith("/");
+ if (isAbsolutePath) {
+ rawPath = rawPath.replaceFirst("/+", "");
+ }
+ ArrayList<String> components = new ArrayList<String>(Arrays.asList(rawPath.split("/+")));
+ for (int index = 0; index < components.size(); ++index) {
+ if (components.get(index).equals("..")) {
+ components.remove(index);
+ if (index > 0) {
+ components.remove(index-1);
+ --index;
+ }
+ }
+ }
+ StringBuilder normalizedPath = new StringBuilder();
+ for(String component: components) {
+ normalizedPath.append("/");
+ normalizedPath.append(component);
+ }
+ if (isAbsolutePath) {
+ return normalizedPath.toString();
+ } else {
+ return normalizedPath.toString().substring(1);
+ }
+ }
+
+ /**
+ * Gets the free space in bytes available on this filesystem.
+ * Subclasses may override this method to return nonzero free space.
+ */
+ public long getFreeSpaceInBytes() {
+ return 0;
+ }
+
+ public abstract Uri toNativeUri(LocalFilesystemURL inputURL);
+ public abstract LocalFilesystemURL toLocalUri(Uri inputURL);
+
+ public JSONObject getRootEntry() {
+ if (rootEntry == null) {
+ rootEntry = makeEntryForNativeUri(rootUri);
+ }
+ return rootEntry;
+ }
+
+ public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+ Uri parentUri = inputURL.uri;
+ String parentPath = new File(inputURL.uri.getPath()).getParent();
+ if (!"/".equals(parentPath)) {
+ parentUri = inputURL.uri.buildUpon().path(parentPath + '/').build();
+ }
+ return getEntryForLocalURL(LocalFilesystemURL.parse(parentUri));
+ }
+
+ protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL, boolean isDirectory) {
+ // I know this looks weird but it is to work around a JSON bug.
+ if ("null".equals(newName) || "".equals(newName)) {
+ newName = srcURL.uri.getLastPathSegment();;
+ }
+
+ String newDest = destURL.uri.toString();
+ if (newDest.endsWith("/")) {
+ newDest = newDest + newName;
+ } else {
+ newDest = newDest + "/" + newName;
+ }
+ if (isDirectory) {
+ newDest += '/';
+ }
+ return LocalFilesystemURL.parse(newDest);
+ }
+
+ /* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
+ * the destination URL on this filesystem, optionally with a new filename.
+ * If move is true, then this method should either perform an atomic move operation
+ * or remove the source file when finished.
+ */
+ public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+ Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
+ // First, check to see that we can do it
+ if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
+ throw new NoModificationAllowedException("Cannot move file at source URL");
+ }
+ final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
+
+ Uri srcNativeUri = srcFs.toNativeUri(srcURL);
+
+ CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcNativeUri);
+ OutputStream os = null;
+ try {
+ os = getOutputStreamForURL(destination);
+ } catch (IOException e) {
+ ofrr.inputStream.close();
+ throw e;
+ }
+ // Closes streams.
+ resourceApi.copyResource(ofrr, os);
+
+ if (move) {
+ srcFs.removeFileAtLocalURL(srcURL);
+ }
+ return getEntryForLocalURL(destination);
+ }
+
+ public OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException {
+ return resourceApi.openOutputStream(toNativeUri(inputURL));
+ }
+
+ public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
+ ReadFileCallback readFileCallback) throws IOException {
+ CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(toNativeUri(inputURL));
+ if (end < 0) {
+ end = ofrr.length;
+ }
+ long numBytesToRead = end - start;
+ try {
+ if (start > 0) {
+ ofrr.inputStream.skip(start);
+ }
+ InputStream inputStream = ofrr.inputStream;
+ if (end < ofrr.length) {
+ inputStream = new LimitedInputStream(inputStream, numBytesToRead);
+ }
+ readFileCallback.handleData(inputStream, ofrr.mimeType);
+ } finally {
+ ofrr.inputStream.close();
+ }
+ }
+
+ abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
+ boolean isBinary) throws NoModificationAllowedException, IOException;
+
+ abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
+ throws IOException, NoModificationAllowedException;
+
+ // This method should return null if filesystem urls cannot be mapped to paths
+ abstract String filesystemPathForURL(LocalFilesystemURL url);
+
+ abstract LocalFilesystemURL URLforFilesystemPath(String path);
+
+ abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
+
+ protected class LimitedInputStream extends FilterInputStream {
+ long numBytesToRead;
+ public LimitedInputStream(InputStream in, long numBytesToRead) {
+ super(in);
+ this.numBytesToRead = numBytesToRead;
+ }
+ @Override
+ public int read() throws IOException {
+ if (numBytesToRead <= 0) {
+ return -1;
+ }
+ numBytesToRead--;
+ return in.read();
+ }
+ @Override
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ if (numBytesToRead <= 0) {
+ return -1;
+ }
+ int bytesToRead = byteCount;
+ if (byteCount > numBytesToRead) {
+ bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
+ }
+ int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
+ numBytesToRead -= numBytesRead;
+ return numBytesRead;
+ }
+ }
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/InvalidModificationException.java b/StoneIsland/plugins/cordova-plugin-file/src/android/InvalidModificationException.java
new file mode 100644
index 00000000..8f6bec59
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-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/StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystem.java b/StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystem.java
new file mode 100644
index 00000000..051f9949
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystem.java
@@ -0,0 +1,513 @@
+/*
+ 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 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.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import org.apache.cordova.CordovaResourceApi;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.os.Build;
+import android.os.Environment;
+import android.util.Base64;
+import android.net.Uri;
+import android.content.Context;
+import android.content.Intent;
+
+import java.nio.charset.Charset;
+
+public class LocalFilesystem extends Filesystem {
+ private final Context context;
+
+ public LocalFilesystem(String name, Context context, CordovaResourceApi resourceApi, File fsRoot) {
+ super(Uri.fromFile(fsRoot).buildUpon().appendEncodedPath("").build(), name, resourceApi);
+ this.context = context;
+ }
+
+ public String filesystemPathForFullPath(String fullPath) {
+ return new File(rootUri.getPath(), fullPath).toString();
+ }
+
+ @Override
+ public String filesystemPathForURL(LocalFilesystemURL url) {
+ return filesystemPathForFullPath(url.path);
+ }
+
+ private String fullPathForFilesystemPath(String absolutePath) {
+ if (absolutePath != null && absolutePath.startsWith(rootUri.getPath())) {
+ return absolutePath.substring(rootUri.getPath().length() - 1);
+ }
+ return null;
+ }
+
+ @Override
+ public Uri toNativeUri(LocalFilesystemURL inputURL) {
+ return nativeUriForFullPath(inputURL.path);
+ }
+
+ @Override
+ public LocalFilesystemURL toLocalUri(Uri inputURL) {
+ if (!"file".equals(inputURL.getScheme())) {
+ return null;
+ }
+ File f = new File(inputURL.getPath());
+ // Removes and duplicate /s (e.g. file:///a//b/c)
+ Uri resolvedUri = Uri.fromFile(f);
+ String rootUriNoTrailingSlash = rootUri.getEncodedPath();
+ rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
+ if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
+ return null;
+ }
+ String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
+ // Strip leading slash
+ if (!subPath.isEmpty()) {
+ subPath = subPath.substring(1);
+ }
+ Uri.Builder b = new Uri.Builder()
+ .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
+ .authority("localhost")
+ .path(name);
+ if (!subPath.isEmpty()) {
+ b.appendEncodedPath(subPath);
+ }
+ if (f.isDirectory()) {
+ // Add trailing / for directories.
+ b.appendEncodedPath("");
+ }
+ return LocalFilesystemURL.parse(b.build());
+ }
+
+ @Override
+ public LocalFilesystemURL URLforFilesystemPath(String path) {
+ return localUrlforFullPath(fullPathForFilesystemPath(path));
+ }
+
+ @Override
+ public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+ String path, 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 (path.contains(":")) {
+ throw new EncodingException("This path has an invalid \":\" in it.");
+ }
+
+ LocalFilesystemURL requestedURL;
+
+ // Check whether the supplied path is absolute or relative
+ if (directory && !path.endsWith("/")) {
+ path += "/";
+ }
+ if (path.startsWith("/")) {
+ requestedURL = localUrlforFullPath(normalizePath(path));
+ } else {
+ requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
+ }
+
+ File fp = new File(this.filesystemPathForURL(requestedURL));
+
+ 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 makeEntryForURL(requestedURL);
+ }
+
+ @Override
+ public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException {
+
+ File fp = new File(filesystemPathForURL(inputURL));
+
+ // 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();
+ }
+
+ @Override
+ public boolean exists(LocalFilesystemURL inputURL) {
+ File fp = new File(filesystemPathForURL(inputURL));
+ return fp.exists();
+ }
+
+ @Override
+ public long getFreeSpaceInBytes() {
+ return DirectoryManager.getFreeSpaceInBytes(rootUri.getPath());
+ }
+
+ @Override
+ public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException {
+ File directory = new File(filesystemPathForURL(inputURL));
+ return removeDirRecursively(directory);
+ }
+
+ protected 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;
+ }
+ }
+
+ @Override
+ public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ File fp = new File(filesystemPathForURL(inputURL));
+
+ if (!fp.exists()) {
+ // The directory we are listing doesn't exist so we should fail.
+ throw new FileNotFoundException();
+ }
+
+ File[] files = fp.listFiles();
+ if (files == null) {
+ // inputURL is a directory
+ return null;
+ }
+ LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
+ for (int i = 0; i < files.length; i++) {
+ entries[i] = URLforFilesystemPath(files[i].getPath());
+ }
+
+ return entries;
+ }
+
+ @Override
+ public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+ File file = new File(filesystemPathForURL(inputURL));
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
+ }
+
+ JSONObject metadata = new JSONObject();
+ try {
+ // Ensure that directories report a size of 0
+ metadata.put("size", file.isDirectory() ? 0 : file.length());
+ metadata.put("type", resourceApi.getMimeType(Uri.fromFile(file)));
+ metadata.put("name", file.getName());
+ metadata.put("fullPath", inputURL.path);
+ metadata.put("lastModifiedDate", file.lastModified());
+ } catch (JSONException e) {
+ return null;
+ }
+ return metadata;
+ }
+
+ private void copyFile(Filesystem srcFs, LocalFilesystemURL srcURL, File destFile, boolean move) throws IOException, InvalidModificationException, NoModificationAllowedException {
+ if (move) {
+ String realSrcPath = srcFs.filesystemPathForURL(srcURL);
+ if (realSrcPath != null) {
+ File srcFile = new File(realSrcPath);
+ if (srcFile.renameTo(destFile)) {
+ return;
+ }
+ // Trying to rename the file failed. Possibly because we moved across file system on the device.
+ }
+ }
+
+ CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(srcFs.toNativeUri(srcURL));
+ copyResource(offr, new FileOutputStream(destFile));
+
+ if (move) {
+ srcFs.removeFileAtLocalURL(srcURL);
+ }
+ }
+
+ private void copyDirectory(Filesystem srcFs, LocalFilesystemURL srcURL, File dstDir, boolean move) throws IOException, NoModificationAllowedException, InvalidModificationException, FileExistsException {
+ if (move) {
+ String realSrcPath = srcFs.filesystemPathForURL(srcURL);
+ if (realSrcPath != null) {
+ File srcDir = new File(realSrcPath);
+ // If the destination directory already exists and is empty then delete it. This is according to spec.
+ if (dstDir.exists()) {
+ if (dstDir.list().length > 0) {
+ throw new InvalidModificationException("directory is not empty");
+ }
+ dstDir.delete();
+ }
+ // Try to rename the directory
+ if (srcDir.renameTo(dstDir)) {
+ return;
+ }
+ // Trying to rename the file failed. Possibly because we moved across file system on the device.
+ }
+ }
+
+ if (dstDir.exists()) {
+ if (dstDir.list().length > 0) {
+ throw new InvalidModificationException("directory is not empty");
+ }
+ } else {
+ if (!dstDir.mkdir()) {
+ // If we can't create the directory then fail
+ throw new NoModificationAllowedException("Couldn't create the destination directory");
+ }
+ }
+
+ LocalFilesystemURL[] children = srcFs.listChildren(srcURL);
+ for (LocalFilesystemURL childLocalUrl : children) {
+ File target = new File(dstDir, new File(childLocalUrl.path).getName());
+ if (childLocalUrl.isDirectory) {
+ copyDirectory(srcFs, childLocalUrl, target, false);
+ } else {
+ copyFile(srcFs, childLocalUrl, target, false);
+ }
+ }
+
+ if (move) {
+ srcFs.recursiveRemoveFileAtLocalURL(srcURL);
+ }
+ }
+
+ @Override
+ public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+ Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
+
+ // Check to see if the destination directory exists
+ String newParent = this.filesystemPathForURL(destURL);
+ 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
+ final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
+
+ Uri dstNativeUri = toNativeUri(destinationURL);
+ Uri srcNativeUri = srcFs.toNativeUri(srcURL);
+ // Check to see if source and destination are the same file
+ if (dstNativeUri.equals(srcNativeUri)) {
+ throw new InvalidModificationException("Can't copy onto itself");
+ }
+
+ if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
+ throw new InvalidModificationException("Source URL is read-only (cannot move)");
+ }
+
+ File destFile = new File(dstNativeUri.getPath());
+ if (destFile.exists()) {
+ if (!srcURL.isDirectory && destFile.isDirectory()) {
+ throw new InvalidModificationException("Can't copy/move a file to an existing directory");
+ } else if (srcURL.isDirectory && destFile.isFile()) {
+ throw new InvalidModificationException("Can't copy/move a directory to an existing file");
+ }
+ }
+
+ if (srcURL.isDirectory) {
+ // E.g. Copy /sdcard/myDir to /sdcard/myDir/backup
+ if (dstNativeUri.toString().startsWith(srcNativeUri.toString() + '/')) {
+ throw new InvalidModificationException("Can't copy directory into itself");
+ }
+ copyDirectory(srcFs, srcURL, destFile, move);
+ } else {
+ copyFile(srcFs, srcURL, destFile, move);
+ }
+ return makeEntryForURL(destinationURL);
+ }
+
+ @Override
+ public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
+ int offset, boolean isBinary) throws IOException, NoModificationAllowedException {
+
+ boolean append = false;
+ if (offset > 0) {
+ this.truncateFileAtURL(inputURL, offset);
+ append = true;
+ }
+
+ byte[] rawData;
+ if (isBinary) {
+ rawData = Base64.decode(data, Base64.DEFAULT);
+ } else {
+ rawData = data.getBytes(Charset.defaultCharset());
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(rawData);
+ try
+ {
+ byte buff[] = new byte[rawData.length];
+ String absolutePath = filesystemPathForURL(inputURL);
+ FileOutputStream out = new FileOutputStream(absolutePath, append);
+ try {
+ in.read(buff, 0, buff.length);
+ out.write(buff, 0, rawData.length);
+ out.flush();
+ } finally {
+ // Always close the output
+ out.close();
+ }
+ if (isPublicDirectory(absolutePath)) {
+ broadcastNewFile(Uri.fromFile(new File(absolutePath)));
+ }
+ }
+ catch (NullPointerException e)
+ {
+ // This is a bug in the Android implementation of the Java Stack
+ NoModificationAllowedException realException = new NoModificationAllowedException(inputURL.toString());
+ realException.initCause(e);
+ throw realException;
+ }
+
+ return rawData.length;
+ }
+
+ private boolean isPublicDirectory(String absolutePath) {
+ // TODO: should expose a way to scan app's private files (maybe via a flag).
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // Lollipop has a bug where SD cards are null.
+ for (File f : context.getExternalMediaDirs()) {
+ if(f != null && absolutePath.startsWith(f.getAbsolutePath())) {
+ return true;
+ }
+ }
+ }
+
+ String extPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+ return absolutePath.startsWith(extPath);
+ }
+
+ /**
+ * Send broadcast of new file so files appear over MTP
+ */
+ private void broadcastNewFile(Uri nativeUri) {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, nativeUri);
+ context.sendBroadcast(intent);
+ }
+
+ @Override
+ public long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException {
+ File file = new File(filesystemPathForURL(inputURL));
+
+ if (!file.exists()) {
+ throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
+ }
+
+ RandomAccessFile raf = new RandomAccessFile(filesystemPathForURL(inputURL), "rw");
+ try {
+ if (raf.length() >= size) {
+ FileChannel channel = raf.getChannel();
+ channel.truncate(size);
+ return size;
+ }
+
+ return raf.length();
+ } finally {
+ raf.close();
+ }
+
+
+ }
+
+ @Override
+ public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+ String path = filesystemPathForURL(inputURL);
+ File file = new File(path);
+ return file.exists();
+ }
+
+ // This is a copy & paste from CordovaResource API that is required since CordovaResourceApi
+ // has a bug pre-4.0.0.
+ // TODO: Once cordova-android@4.0.0 is released, delete this copy and make the plugin depend on
+ // 4.0.0 with an engine tag.
+ private static void copyResource(CordovaResourceApi.OpenForReadResult input, OutputStream outputStream) throws IOException {
+ try {
+ InputStream inputStream = input.inputStream;
+ if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
+ FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
+ FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
+ long offset = 0;
+ long length = input.length;
+ if (input.assetFd != null) {
+ offset = input.assetFd.getStartOffset();
+ }
+ // transferFrom()'s 2nd arg is a relative position. Need to set the absolute
+ // position first.
+ inChannel.position(offset);
+ outChannel.transferFrom(inChannel, 0, length);
+ } else {
+ final int BUFFER_SIZE = 8192;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ for (;;) {
+ int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+ if (bytesRead <= 0) {
+ break;
+ }
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ }
+ } finally {
+ input.inputStream.close();
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ }
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystemURL.java b/StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystemURL.java
new file mode 100644
index 00000000..b96b6ee4
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/LocalFilesystemURL.java
@@ -0,0 +1,64 @@
+/*
+ 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.net.Uri;
+
+public class LocalFilesystemURL {
+
+ public static final String FILESYSTEM_PROTOCOL = "cdvfile";
+
+ public final Uri uri;
+ public final String fsName;
+ public final String path;
+ public final boolean isDirectory;
+
+ private LocalFilesystemURL(Uri uri, String fsName, String fsPath, boolean isDirectory) {
+ this.uri = uri;
+ this.fsName = fsName;
+ this.path = fsPath;
+ this.isDirectory = isDirectory;
+ }
+
+ public static LocalFilesystemURL parse(Uri uri) {
+ if (!FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
+ return null;
+ }
+ String path = uri.getPath();
+ if (path.length() < 1) {
+ return null;
+ }
+ int firstSlashIdx = path.indexOf('/', 1);
+ if (firstSlashIdx < 0) {
+ return null;
+ }
+ String fsName = path.substring(1, firstSlashIdx);
+ path = path.substring(firstSlashIdx);
+ boolean isDirectory = path.charAt(path.length() - 1) == '/';
+ return new LocalFilesystemURL(uri, fsName, path, isDirectory);
+ }
+
+ public static LocalFilesystemURL parse(String uri) {
+ return parse(Uri.parse(uri));
+ }
+
+ public String toString() {
+ return uri.toString();
+ }
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/NoModificationAllowedException.java b/StoneIsland/plugins/cordova-plugin-file/src/android/NoModificationAllowedException.java
new file mode 100644
index 00000000..627eafb5
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-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/StoneIsland/plugins/cordova-plugin-file/src/android/PendingRequests.java b/StoneIsland/plugins/cordova-plugin-file/src/android/PendingRequests.java
new file mode 100644
index 00000000..4c75f423
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/PendingRequests.java
@@ -0,0 +1,94 @@
+/*
+ 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.util.SparseArray;
+
+import org.apache.cordova.CallbackContext;
+
+/**
+ * Holds pending runtime permission requests
+ */
+class PendingRequests {
+ private int currentReqId = 0;
+ private SparseArray<Request> requests = new SparseArray<Request>();
+
+ /**
+ * Creates a request and adds it to the array of pending requests. Each created request gets a
+ * unique result code for use with requestPermission()
+ * @param rawArgs The raw arguments passed to the plugin
+ * @param action The action this request corresponds to (get file, etc.)
+ * @param callbackContext The CallbackContext for this plugin call
+ * @return The request code that can be used to retrieve the Request object
+ */
+ public synchronized int createRequest(String rawArgs, int action, CallbackContext callbackContext) {
+ Request req = new Request(rawArgs, action, callbackContext);
+ requests.put(req.requestCode, req);
+ return req.requestCode;
+ }
+
+ /**
+ * Gets the request corresponding to this request code and removes it from the pending requests
+ * @param requestCode The request code for the desired request
+ * @return The request corresponding to the given request code or null if such a
+ * request is not found
+ */
+ public synchronized Request getAndRemove(int requestCode) {
+ Request result = requests.get(requestCode);
+ requests.remove(requestCode);
+ return result;
+ }
+
+ /**
+ * Holds the options and CallbackContext for a call made to the plugin.
+ */
+ public class Request {
+
+ // Unique int used to identify this request in any Android permission callback
+ private int requestCode;
+
+ // Action to be performed after permission request result
+ private int action;
+
+ // Raw arguments passed to plugin
+ private String rawArgs;
+
+ // The callback context for this plugin request
+ private CallbackContext callbackContext;
+
+ private Request(String rawArgs, int action, CallbackContext callbackContext) {
+ this.rawArgs = rawArgs;
+ this.action = action;
+ this.callbackContext = callbackContext;
+ this.requestCode = currentReqId ++;
+ }
+
+ public int getAction() {
+ return this.action;
+ }
+
+ public String getRawArgs() {
+ return rawArgs;
+ }
+
+ public CallbackContext getCallbackContext() {
+ return callbackContext;
+ }
+ }
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/android/TypeMismatchException.java b/StoneIsland/plugins/cordova-plugin-file/src/android/TypeMismatchException.java
new file mode 100644
index 00000000..1315f9a9
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-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/StoneIsland/plugins/cordova-plugin-file/src/android/build-extras.gradle b/StoneIsland/plugins/cordova-plugin-file/src/android/build-extras.gradle
new file mode 100644
index 00000000..a0a7844a
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/android/build-extras.gradle
@@ -0,0 +1,47 @@
+/*
+ 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.
+ */
+ext.postBuildExtras = {
+ def inAssetsDir = file("assets")
+ def outAssetsDir = inAssetsDir
+ def outFile = new File(outAssetsDir, "cdvasset.manifest")
+
+ def newTask = task("cdvCreateAssetManifest") << {
+ def contents = new HashMap()
+ def sizes = new HashMap()
+ contents[""] = inAssetsDir.list()
+ def tree = fileTree(dir: inAssetsDir)
+ tree.visit { fileDetails ->
+ if (fileDetails.isDirectory()) {
+ contents[fileDetails.relativePath.toString()] = fileDetails.file.list()
+ } else {
+ sizes[fileDetails.relativePath.toString()] = fileDetails.file.length()
+ }
+ }
+
+ outAssetsDir.mkdirs()
+ outFile.withObjectOutputStream { oos ->
+ oos.writeObject(contents)
+ oos.writeObject(sizes)
+ }
+ }
+ newTask.inputs.dir inAssetsDir
+ newTask.outputs.file outFile
+ def preBuildTask = tasks["preBuild"]
+ preBuildTask.dependsOn(newTask)
+}
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/browser/FileProxy.js b/StoneIsland/plugins/cordova-plugin-file/src/browser/FileProxy.js
new file mode 100644
index 00000000..17955b68
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/browser/FileProxy.js
@@ -0,0 +1,1059 @@
+/*
+ *
+ * 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.
+ *
+ */
+(function () {
+ /* global require, exports, module */
+ /* global FILESYSTEM_PREFIX */
+ /* global IDBKeyRange */
+ /* global FileReader */
+ /* global atob, btoa, Blob */
+
+ /* Heavily based on https://github.com/ebidel/idb.filesystem.js */
+
+ // For chrome we don't need to implement proxy methods
+ // All functionality can be accessed natively.
+ if (require('./isChrome')()) {
+ var pathsPrefix = {
+ // Read-only directory where the application is installed.
+ applicationDirectory: location.origin + '/', // eslint-disable-line no-undef
+ // Where to put app-specific data files.
+ dataDirectory: 'filesystem:file:///persistent/',
+ // Cached files that should survive app restarts.
+ // Apps should not rely on the OS to delete files in here.
+ cacheDirectory: 'filesystem:file:///temporary/'
+ };
+
+ exports.requestAllPaths = function (successCallback) {
+ successCallback(pathsPrefix);
+ };
+
+ require('cordova/exec/proxy').add('File', module.exports);
+ return;
+ }
+
+ var LocalFileSystem = require('./LocalFileSystem');
+ var FileSystem = require('./FileSystem');
+ var FileEntry = require('./FileEntry');
+ var FileError = require('./FileError');
+ var DirectoryEntry = require('./DirectoryEntry');
+ var File = require('./File');
+
+ (function (exports, global) {
+ var indexedDB = global.indexedDB || global.mozIndexedDB;
+ if (!indexedDB) {
+ throw 'Firefox OS File plugin: indexedDB not supported';
+ }
+
+ var fs_ = null;
+
+ var idb_ = {};
+ idb_.db = null;
+ var FILE_STORE_ = 'entries';
+
+ var DIR_SEPARATOR = '/';
+
+ var pathsPrefix = {
+ // Read-only directory where the application is installed.
+ applicationDirectory: location.origin + '/', // eslint-disable-line no-undef
+ // Where to put app-specific data files.
+ dataDirectory: 'file:///persistent/',
+ // Cached files that should survive app restarts.
+ // Apps should not rely on the OS to delete files in here.
+ cacheDirectory: 'file:///temporary/'
+ };
+
+ var unicodeLastChar = 65535;
+
+ /** * Exported functionality ***/
+
+ exports.requestFileSystem = function (successCallback, errorCallback, args) {
+ var type = args[0];
+ // Size is ignored since IDB filesystem size depends
+ // on browser implementation and can't be set up by user
+ var size = args[1]; // eslint-disable-line no-unused-vars
+
+ if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ return;
+ }
+
+ var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
+ var storageName = (location.protocol + location.host).replace(/:/g, '_'); // eslint-disable-line no-undef
+
+ var root = new DirectoryEntry('', DIR_SEPARATOR);
+ fs_ = new FileSystem(name, root);
+
+ idb_.open(storageName, function () {
+ successCallback(fs_);
+ }, errorCallback);
+ };
+
+ // Overridden by Android, BlackBerry 10 and iOS to populate fsMap
+ require('./fileSystems').getFs = function (name, callback) {
+ callback(new FileSystem(name, fs_.root));
+ };
+
+ // list a directory's contents (files and folders).
+ exports.readEntries = function (successCallback, errorCallback, args) {
+ var fullPath = args[0];
+
+ if (typeof successCallback !== 'function') {
+ throw Error('Expected successCallback argument.');
+ }
+
+ var path = resolveToFullPath_(fullPath);
+
+ exports.getDirectory(function () {
+ idb_.getAllEntries(path.fullPath + DIR_SEPARATOR, path.storagePath, function (entries) {
+ successCallback(entries);
+ }, errorCallback);
+ }, function () {
+ if (errorCallback) {
+ errorCallback(FileError.NOT_FOUND_ERR);
+ }
+ }, [path.storagePath, path.fullPath, {create: false}]);
+ };
+
+ exports.getFile = function (successCallback, errorCallback, args) {
+ var fullPath = args[0];
+ var path = args[1];
+ var options = args[2] || {};
+
+ // Create an absolute path if we were handed a relative one.
+ path = resolveToFullPath_(fullPath, path);
+
+ idb_.get(path.storagePath, function (fileEntry) {
+ if (options.create === true && options.exclusive === true && fileEntry) {
+ // If create and exclusive are both true, and the path already exists,
+ // getFile must fail.
+
+ if (errorCallback) {
+ errorCallback(FileError.PATH_EXISTS_ERR);
+ }
+ } else if (options.create === true && !fileEntry) {
+ // If create is true, the path doesn't exist, and no other error occurs,
+ // getFile must create it as a zero-length file and return a corresponding
+ // FileEntry.
+ var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
+
+ newFileEntry.file_ = new MyFile({
+ size: 0,
+ name: newFileEntry.name,
+ lastModifiedDate: new Date(),
+ storagePath: path.storagePath
+ });
+
+ idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
+ } else if (options.create === true && fileEntry) {
+ if (fileEntry.isFile) {
+ // Overwrite file, delete then create new.
+ idb_['delete'](path.storagePath, function () {
+ var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
+
+ newFileEntry.file_ = new MyFile({
+ size: 0,
+ name: newFileEntry.name,
+ lastModifiedDate: new Date(),
+ storagePath: path.storagePath
+ });
+
+ idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
+ }, errorCallback);
+ } else {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ }
+ } else if ((!options.create || options.create === false) && !fileEntry) {
+ // If create is not true and the path doesn't exist, getFile must fail.
+ if (errorCallback) {
+ errorCallback(FileError.NOT_FOUND_ERR);
+ }
+ } else if ((!options.create || options.create === false) && fileEntry &&
+ fileEntry.isDirectory) {
+ // If create is not true and the path exists, but is a directory, getFile
+ // must fail.
+ if (errorCallback) {
+ errorCallback(FileError.TYPE_MISMATCH_ERR);
+ }
+ } else {
+ // Otherwise, if no other error occurs, getFile must return a FileEntry
+ // corresponding to path.
+
+ successCallback(fileEntryFromIdbEntry(fileEntry));
+ }
+ }, errorCallback);
+ };
+
+ exports.getFileMetadata = function (successCallback, errorCallback, args) {
+ var fullPath = args[0];
+
+ exports.getFile(function (fileEntry) {
+ successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
+ fileEntry.file_.size));
+ }, errorCallback, [fullPath, null]);
+ };
+
+ exports.getMetadata = function (successCallback, errorCallback, args) {
+ exports.getFile(function (fileEntry) {
+ successCallback(
+ {
+ modificationTime: fileEntry.file_.lastModifiedDate,
+ size: fileEntry.file_.lastModifiedDate
+ });
+ }, errorCallback, args);
+ };
+
+ exports.setMetadata = function (successCallback, errorCallback, args) {
+ var fullPath = args[0];
+ var metadataObject = args[1];
+
+ exports.getFile(function (fileEntry) {
+ fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
+ idb_.put(fileEntry, fileEntry.file_.storagePath, successCallback, errorCallback);
+ }, errorCallback, [fullPath, null]);
+ };
+
+ exports.write = function (successCallback, errorCallback, args) {
+ var fileName = args[0];
+ var data = args[1];
+ var position = args[2];
+ var isBinary = args[3]; // eslint-disable-line no-unused-vars
+
+ if (!data) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ return;
+ }
+
+ if (typeof data === 'string' || data instanceof String) {
+ data = new Blob([data]); // eslint-disable-line no-undef
+ }
+
+ exports.getFile(function (fileEntry) {
+ var blob_ = fileEntry.file_.blob_;
+
+ if (!blob_) {
+ blob_ = new Blob([data], {type: data.type}); // eslint-disable-line no-undef
+ } else {
+ // Calc the head and tail fragments
+ var head = blob_.slice(0, position);
+ var tail = blob_.slice(position + (data.size || data.byteLength));
+
+ // Calc the padding
+ var padding = position - head.size;
+ if (padding < 0) {
+ padding = 0;
+ }
+
+ // Do the "write". In fact, a full overwrite of the Blob.
+ blob_ = new Blob([head, new Uint8Array(padding), data, tail], // eslint-disable-line no-undef
+ {type: data.type});
+ }
+
+ // Set the blob we're writing on this file entry so we can recall it later.
+ fileEntry.file_.blob_ = blob_;
+ fileEntry.file_.lastModifiedDate = new Date() || null;
+ fileEntry.file_.size = blob_.size;
+ fileEntry.file_.name = blob_.name;
+ fileEntry.file_.type = blob_.type;
+
+ idb_.put(fileEntry, fileEntry.file_.storagePath, function () {
+ successCallback(data.size || data.byteLength);
+ }, errorCallback);
+ }, errorCallback, [fileName, null]);
+ };
+
+ exports.readAsText = function (successCallback, errorCallback, args) {
+ var fileName = args[0];
+ var enc = args[1];
+ var startPos = args[2];
+ var endPos = args[3];
+
+ readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
+ };
+
+ exports.readAsDataURL = function (successCallback, errorCallback, args) {
+ var fileName = args[0];
+ var startPos = args[1];
+ var endPos = args[2];
+
+ readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
+ };
+
+ exports.readAsBinaryString = function (successCallback, errorCallback, args) {
+ var fileName = args[0];
+ var startPos = args[1];
+ var endPos = args[2];
+
+ readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
+ };
+
+ exports.readAsArrayBuffer = function (successCallback, errorCallback, args) {
+ var fileName = args[0];
+ var startPos = args[1];
+ var endPos = args[2];
+
+ readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
+ };
+
+ exports.removeRecursively = exports.remove = function (successCallback, errorCallback, args) {
+ if (typeof successCallback !== 'function') {
+ throw Error('Expected successCallback argument.');
+ }
+
+ var fullPath = resolveToFullPath_(args[0]).storagePath;
+ if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) {
+ errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ function deleteEntry (isDirectory) {
+ // TODO: This doesn't protect against directories that have content in it.
+ // Should throw an error instead if the dirEntry is not empty.
+ idb_['delete'](fullPath, function () {
+ successCallback();
+ }, function () {
+ if (errorCallback) { errorCallback(); }
+ }, isDirectory);
+ }
+
+ // We need to to understand what we are deleting:
+ exports.getDirectory(function (entry) {
+ deleteEntry(entry.isDirectory);
+ }, function () {
+ // DirectoryEntry was already deleted or entry is FileEntry
+ deleteEntry(false);
+ }, [fullPath, null, {create: false}]);
+ };
+
+ exports.getDirectory = function (successCallback, errorCallback, args) {
+ var fullPath = args[0];
+ var path = args[1];
+ var options = args[2];
+
+ // Create an absolute path if we were handed a relative one.
+ path = resolveToFullPath_(fullPath, path);
+
+ idb_.get(path.storagePath, function (folderEntry) {
+ if (!options) {
+ options = {};
+ }
+
+ if (options.create === true && options.exclusive === true && folderEntry) {
+ // If create and exclusive are both true, and the path already exists,
+ // getDirectory must fail.
+ if (errorCallback) {
+ errorCallback(FileError.PATH_EXISTS_ERR);
+ }
+ // There is a strange bug in mobilespec + FF, which results in coming to multiple else-if's
+ // so we are shielding from it with returns.
+ return;
+ }
+
+ if (options.create === true && !folderEntry) {
+ // If create is true, the path doesn't exist, and no other error occurs,
+ // getDirectory must create it as a zero-length file and return a corresponding
+ // MyDirectoryEntry.
+ var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
+
+ idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
+ return;
+ }
+
+ if (options.create === true && folderEntry) {
+
+ if (folderEntry.isDirectory) {
+ // IDB won't save methods, so we need re-create the MyDirectoryEntry.
+ successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
+ } else {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ }
+ return;
+ }
+
+ if ((!options.create || options.create === false) && !folderEntry) {
+ // Handle root special. It should always exist.
+ if (path.fullPath === DIR_SEPARATOR) {
+ successCallback(fs_.root);
+ return;
+ }
+
+ // If create is not true and the path doesn't exist, getDirectory must fail.
+ if (errorCallback) {
+ errorCallback(FileError.NOT_FOUND_ERR);
+ }
+
+ return;
+ }
+ if ((!options.create || options.create === false) && folderEntry && folderEntry.isFile) {
+ // If create is not true and the path exists, but is a file, getDirectory
+ // must fail.
+ if (errorCallback) {
+ errorCallback(FileError.TYPE_MISMATCH_ERR);
+ }
+ return;
+ }
+
+ // Otherwise, if no other error occurs, getDirectory must return a
+ // MyDirectoryEntry corresponding to path.
+
+ // IDB won't' save methods, so we need re-create MyDirectoryEntry.
+ successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
+ }, errorCallback);
+ };
+
+ exports.getParent = function (successCallback, errorCallback, args) {
+ if (typeof successCallback !== 'function') {
+ throw Error('Expected successCallback argument.');
+ }
+
+ var fullPath = args[0];
+ // fullPath is like this:
+ // file:///persistent/path/to/file or
+ // file:///persistent/path/to/directory/
+
+ if (fullPath === DIR_SEPARATOR || fullPath === pathsPrefix.cacheDirectory ||
+ fullPath === pathsPrefix.dataDirectory) {
+ successCallback(fs_.root);
+ return;
+ }
+
+ // To delete all slashes at the end
+ while (fullPath[fullPath.length - 1] === '/') {
+ fullPath = fullPath.substr(0, fullPath.length - 1);
+ }
+
+ var pathArr = fullPath.split(DIR_SEPARATOR);
+ pathArr.pop();
+ var parentName = pathArr.pop();
+ var path = pathArr.join(DIR_SEPARATOR) + DIR_SEPARATOR;
+
+ // To get parent of root files
+ var joined = path + parentName + DIR_SEPARATOR;// is like this: file:///persistent/
+ if (joined === pathsPrefix.cacheDirectory || joined === pathsPrefix.dataDirectory) {
+ exports.getDirectory(successCallback, errorCallback, [joined, DIR_SEPARATOR, {create: false}]);
+ return;
+ }
+
+ exports.getDirectory(successCallback, errorCallback, [path, parentName, {create: false}]);
+ };
+
+ exports.copyTo = function (successCallback, errorCallback, args) {
+ var srcPath = args[0];
+ var parentFullPath = args[1];
+ var name = args[2];
+
+ if (name.indexOf('/') !== -1 || srcPath === parentFullPath + name) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+
+ return;
+ }
+
+ // Read src file
+ exports.getFile(function (srcFileEntry) {
+
+ var path = resolveToFullPath_(parentFullPath);
+ // Check directory
+ exports.getDirectory(function () {
+
+ // Create dest file
+ exports.getFile(function (dstFileEntry) {
+
+ exports.write(function () {
+ successCallback(dstFileEntry);
+ }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
+
+ }, errorCallback, [parentFullPath, name, {create: true}]);
+
+ }, function () { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } },
+ [path.storagePath, null, {create: false}]);
+
+ }, errorCallback, [srcPath, null]);
+ };
+
+ exports.moveTo = function (successCallback, errorCallback, args) {
+ var srcPath = args[0];
+ // parentFullPath and name parameters is ignored because
+ // args is being passed downstream to exports.copyTo method
+ var parentFullPath = args[1]; // eslint-disable-line
+ var name = args[2]; // eslint-disable-line
+
+ exports.copyTo(function (fileEntry) {
+
+ exports.remove(function () {
+ successCallback(fileEntry);
+ }, errorCallback, [srcPath]);
+
+ }, errorCallback, args);
+ };
+
+ exports.resolveLocalFileSystemURI = function (successCallback, errorCallback, args) {
+ var path = args[0];
+
+ // Ignore parameters
+ if (path.indexOf('?') !== -1) {
+ path = String(path).split('?')[0];
+ }
+
+ // support for encodeURI
+ if (/\%5/g.test(path) || /\%20/g.test(path)) { // eslint-disable-line no-useless-escape
+ path = decodeURI(path);
+ }
+
+ if (path.trim()[0] === '/') {
+ if (errorCallback) {
+ errorCallback(FileError.ENCODING_ERR);
+ }
+ return;
+ }
+
+ // support for cdvfile
+ if (path.trim().substr(0, 7) === 'cdvfile') {
+ if (path.indexOf('cdvfile://localhost') === -1) {
+ if (errorCallback) {
+ errorCallback(FileError.ENCODING_ERR);
+ }
+ return;
+ }
+
+ var indexPersistent = path.indexOf('persistent');
+ var indexTemporary = path.indexOf('temporary');
+
+ // cdvfile://localhost/persistent/path/to/file
+ if (indexPersistent !== -1) {
+ path = 'file:///persistent' + path.substr(indexPersistent + 10);
+ } else if (indexTemporary !== -1) {
+ path = 'file:///temporary' + path.substr(indexTemporary + 9);
+ } else {
+ if (errorCallback) {
+ errorCallback(FileError.ENCODING_ERR);
+ }
+ return;
+ }
+ }
+
+ // to avoid path form of '///path/to/file'
+ function handlePathSlashes (path) {
+ var cutIndex = 0;
+ for (var i = 0; i < path.length - 1; i++) {
+ if (path[i] === DIR_SEPARATOR && path[i + 1] === DIR_SEPARATOR) {
+ cutIndex = i + 1;
+ } else break;
+ }
+
+ return path.substr(cutIndex);
+ }
+
+ // Handle localhost containing paths (see specs )
+ if (path.indexOf('file://localhost/') === 0) {
+ path = path.replace('file://localhost/', 'file:///');
+ }
+
+ if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
+ path = path.substring(pathsPrefix.dataDirectory.length - 1);
+ path = handlePathSlashes(path);
+
+ exports.requestFileSystem(function () {
+ exports.getFile(successCallback, function () {
+ exports.getDirectory(successCallback, errorCallback, [pathsPrefix.dataDirectory, path,
+ {create: false}]);
+ }, [pathsPrefix.dataDirectory, path, {create: false}]);
+ }, errorCallback, [LocalFileSystem.PERSISTENT]);
+ } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
+ path = path.substring(pathsPrefix.cacheDirectory.length - 1);
+ path = handlePathSlashes(path);
+
+ exports.requestFileSystem(function () {
+ exports.getFile(successCallback, function () {
+ exports.getDirectory(successCallback, errorCallback, [pathsPrefix.cacheDirectory, path,
+ {create: false}]);
+ }, [pathsPrefix.cacheDirectory, path, {create: false}]);
+ }, errorCallback, [LocalFileSystem.TEMPORARY]);
+ } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
+ path = path.substring(pathsPrefix.applicationDirectory.length);
+ // TODO: need to cut out redundant slashes?
+
+ var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
+ xhr.open('GET', path, true);
+ xhr.onreadystatechange = function () {
+ if (xhr.status === 200 && xhr.readyState === 4) {
+ exports.requestFileSystem(function (fs) {
+ fs.name = location.hostname; // eslint-disable-line no-undef
+
+ // TODO: need to call exports.getFile(...) to handle errors correct
+ fs.root.getFile(path, {create: true}, writeFile, errorCallback);
+ }, errorCallback, [LocalFileSystem.PERSISTENT]);
+ }
+ };
+
+ xhr.onerror = function () {
+ if (errorCallback) {
+ errorCallback(FileError.NOT_READABLE_ERR);
+ }
+ };
+
+ xhr.send();
+ } else {
+ if (errorCallback) {
+ errorCallback(FileError.NOT_FOUND_ERR);
+ }
+ }
+
+ function writeFile (entry) {
+ entry.createWriter(function (fileWriter) {
+ fileWriter.onwriteend = function (evt) {
+ if (!evt.target.error) {
+ entry.filesystemName = location.hostname; // eslint-disable-line no-undef
+ successCallback(entry);
+ }
+ };
+ fileWriter.onerror = function () {
+ if (errorCallback) {
+ errorCallback(FileError.NOT_READABLE_ERR);
+ }
+ };
+ fileWriter.write(new Blob([xhr.response])); // eslint-disable-line no-undef
+ }, errorCallback); // eslint-disable-line no-undef
+ }
+ };
+
+ exports.requestAllPaths = function (successCallback) {
+ successCallback(pathsPrefix);
+ };
+
+ /** * Helpers ***/
+
+ /**
+ * Interface to wrap the native File interface.
+ *
+ * This interface is necessary for creating zero-length (empty) files,
+ * something the Filesystem API allows you to do. Unfortunately, File's
+ * constructor cannot be called directly, making it impossible to instantiate
+ * an empty File in JS.
+ *
+ * @param {Object} opts Initial values.
+ * @constructor
+ */
+ function MyFile (opts) {
+ var blob_ = new Blob(); // eslint-disable-line no-undef
+
+ this.size = opts.size || 0;
+ this.name = opts.name || '';
+ this.type = opts.type || '';
+ this.lastModifiedDate = opts.lastModifiedDate || null;
+ this.storagePath = opts.storagePath || '';
+
+ // Need some black magic to correct the object's size/name/type based on the
+ // blob that is saved.
+ Object.defineProperty(this, 'blob_', {
+ enumerable: true,
+ get: function () {
+ return blob_;
+ },
+ set: function (val) {
+ blob_ = val;
+ this.size = blob_.size;
+ this.name = blob_.name;
+ this.type = blob_.type;
+ this.lastModifiedDate = blob_.lastModifiedDate;
+ }.bind(this)
+ });
+ }
+
+ MyFile.prototype.constructor = MyFile;
+
+ var MyFileHelper = {
+ toJson: function (myFile, success) {
+ /*
+ Safari private browse mode cannot store Blob object to indexeddb.
+ Then use pure json object instead of Blob object.
+ */
+ var fr = new FileReader();
+ fr.onload = function (ev) {
+ var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(fr.result)));
+ success({
+ opt: {
+ size: myFile.size,
+ name: myFile.name,
+ type: myFile.type,
+ lastModifiedDate: myFile.lastModifiedDate,
+ storagePath: myFile.storagePath
+ },
+ base64: base64
+ });
+ };
+ fr.readAsArrayBuffer(myFile.blob_);
+ },
+ setBase64: function (myFile, base64) {
+ if (base64) {
+ var arrayBuffer = (new Uint8Array(
+ [].map.call(atob(base64), function (c) { return c.charCodeAt(0); })
+ )).buffer;
+
+ myFile.blob_ = new Blob([arrayBuffer], { type: myFile.type });
+ } else {
+ myFile.blob_ = new Blob();
+ }
+ }
+ };
+
+ // When saving an entry, the fullPath should always lead with a slash and never
+ // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
+ // one. This method ensures path is legit!
+ function resolveToFullPath_ (cwdFullPath, path) {
+ path = path || '';
+ var fullPath = path;
+ var prefix = '';
+
+ cwdFullPath = cwdFullPath || DIR_SEPARATOR;
+ if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
+ prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
+ cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
+ }
+
+ var relativePath = path[0] !== DIR_SEPARATOR;
+ if (relativePath) {
+ fullPath = cwdFullPath;
+ if (cwdFullPath !== DIR_SEPARATOR) {
+ fullPath += DIR_SEPARATOR + path;
+ } else {
+ fullPath += path;
+ }
+ }
+
+ // Remove doubled separator substrings
+ var re = new RegExp(DIR_SEPARATOR + DIR_SEPARATOR, 'g');
+ fullPath = fullPath.replace(re, DIR_SEPARATOR);
+
+ // Adjust '..'s by removing parent directories when '..' flows in path.
+ var parts = fullPath.split(DIR_SEPARATOR);
+ for (var i = 0; i < parts.length; ++i) {
+ var part = parts[i];
+ if (part === '..') {
+ parts[i - 1] = '';
+ parts[i] = '';
+ }
+ }
+ fullPath = parts.filter(function (el) {
+ return el;
+ }).join(DIR_SEPARATOR);
+
+ // Add back in leading slash.
+ if (fullPath[0] !== DIR_SEPARATOR) {
+ fullPath = DIR_SEPARATOR + fullPath;
+ }
+
+ // Replace './' by current dir. ('./one/./two' -> one/two)
+ fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
+
+ // Replace '//' with '/'.
+ fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
+
+ // Replace '/.' with '/'.
+ fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
+
+ // Remove '/' if it appears on the end.
+ if (fullPath[fullPath.length - 1] === DIR_SEPARATOR &&
+ fullPath !== DIR_SEPARATOR) {
+ fullPath = fullPath.substring(0, fullPath.length - 1);
+ }
+
+ var storagePath = prefix + fullPath;
+ storagePath = decodeURI(storagePath);
+ fullPath = decodeURI(fullPath);
+
+ return {
+ storagePath: storagePath,
+ fullPath: fullPath,
+ fileName: fullPath.split(DIR_SEPARATOR).pop(),
+ fsName: prefix.split(DIR_SEPARATOR).pop()
+ };
+ }
+
+ function fileEntryFromIdbEntry (fileEntry) {
+ // IDB won't save methods, so we need re-create the FileEntry.
+ var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.filesystem);
+ clonedFileEntry.file_ = fileEntry.file_;
+
+ return clonedFileEntry;
+ }
+
+ function readAs (what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
+ exports.getFile(function (fileEntry) {
+ var fileReader = new FileReader(); // eslint-disable-line no-undef
+ var blob = fileEntry.file_.blob_.slice(startPos, endPos);
+
+ fileReader.onload = function (e) {
+ successCallback(e.target.result);
+ };
+
+ fileReader.onerror = errorCallback;
+
+ switch (what) {
+ case 'text':
+ fileReader.readAsText(blob, encoding);
+ break;
+ case 'dataURL':
+ fileReader.readAsDataURL(blob);
+ break;
+ case 'arrayBuffer':
+ fileReader.readAsArrayBuffer(blob);
+ break;
+ case 'binaryString':
+ fileReader.readAsBinaryString(blob);
+ break;
+ }
+
+ }, errorCallback, [fullPath, null]);
+ }
+
+ /** * Core logic to handle IDB operations ***/
+
+ idb_.open = function (dbName, successCallback, errorCallback) {
+ var self = this;
+
+ // TODO: FF 12.0a1 isn't liking a db name with : in it.
+ var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version */);
+
+ request.onerror = errorCallback || onError;
+
+ request.onupgradeneeded = function (e) {
+ // First open was called or higher db version was used.
+
+ // console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
+ // 'newVersion:' + e.newVersion);
+
+ self.db = e.target.result;
+ self.db.onerror = onError;
+
+ if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
+ self.db.createObjectStore(FILE_STORE_/*, {keyPath: 'id', autoIncrement: true} */);
+ }
+ };
+
+ request.onsuccess = function (e) {
+ self.db = e.target.result;
+ self.db.onerror = onError;
+ successCallback(e);
+ };
+
+ request.onblocked = errorCallback || onError;
+ };
+
+ idb_.close = function () {
+ this.db.close();
+ this.db = null;
+ };
+
+ idb_.get = function (fullPath, successCallback, errorCallback) {
+ if (!this.db) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ return;
+ }
+
+ var tx = this.db.transaction([FILE_STORE_], 'readonly');
+
+ var request = tx.objectStore(FILE_STORE_).get(fullPath);
+
+ tx.onabort = errorCallback || onError;
+ tx.oncomplete = function () {
+ var entry = request.result;
+ if (entry && entry.file_json) {
+ /*
+ Safari private browse mode cannot store Blob object to indexeddb.
+ Then use pure json object instead of Blob object.
+ */
+ entry.file_ = new MyFile(entry.file_json.opt);
+ MyFileHelper.setBase64(entry.file_, entry.file_json.base64);
+ delete entry.file_json;
+ }
+ successCallback(entry);
+ };
+ };
+
+ idb_.getAllEntries = function (fullPath, storagePath, successCallback, errorCallback) {
+ if (!this.db) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ return;
+ }
+
+ var results = [];
+
+ if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
+ storagePath = storagePath.substring(0, storagePath.length - 1);
+ }
+
+ var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ',
+ storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar));
+
+ var tx = this.db.transaction([FILE_STORE_], 'readonly');
+ tx.onabort = errorCallback || onError;
+ tx.oncomplete = function () {
+ results = results.filter(function (val) {
+ var pathWithoutSlash = val.fullPath;
+
+ if (val.fullPath[val.fullPath.length - 1] === DIR_SEPARATOR) {
+ pathWithoutSlash = pathWithoutSlash.substr(0, pathWithoutSlash.length - 1);
+ }
+
+ var valPartsLen = pathWithoutSlash.split(DIR_SEPARATOR).length;
+ var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
+
+ /* Input fullPath parameter equals '//' for root folder */
+ /* Entries in root folder has valPartsLen equals 2 (see below) */
+ if (fullPath[fullPath.length - 1] === DIR_SEPARATOR && fullPath.trim().length === 2) {
+ fullPathPartsLen = 1;
+ } else if (fullPath[fullPath.length - 1] === DIR_SEPARATOR) {
+ fullPathPartsLen = fullPath.substr(0, fullPath.length - 1).split(DIR_SEPARATOR).length;
+ } else {
+ fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
+ }
+
+ if (valPartsLen === fullPathPartsLen + 1) {
+ // If this a subfolder and entry is a direct child, include it in
+ // the results. Otherwise, it's not an entry of this folder.
+ return val;
+ } else return false;
+ });
+
+ successCallback(results);
+ };
+
+ var request = tx.objectStore(FILE_STORE_).openCursor(range);
+
+ request.onsuccess = function (e) {
+ var cursor = e.target.result;
+ if (cursor) {
+ var val = cursor.value;
+
+ results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.filesystem));
+ cursor['continue']();
+ }
+ };
+ };
+
+ idb_['delete'] = function (fullPath, successCallback, errorCallback, isDirectory) {
+ if (!idb_.db) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ return;
+ }
+
+ var tx = this.db.transaction([FILE_STORE_], 'readwrite');
+ tx.oncomplete = successCallback;
+ tx.onabort = errorCallback || onError;
+ tx.oncomplete = function () {
+ if (isDirectory) {
+ // We delete nested files and folders after deleting parent folder
+ // We use ranges: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange
+ fullPath = fullPath + DIR_SEPARATOR;
+
+ // Range contains all entries in the form fullPath<symbol> where
+ // symbol in the range from ' ' to symbol which has code `unicodeLastChar`
+ var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar));
+
+ var newTx = this.db.transaction([FILE_STORE_], 'readwrite');
+ newTx.oncomplete = successCallback;
+ newTx.onabort = errorCallback || onError;
+ newTx.objectStore(FILE_STORE_)['delete'](range);
+ } else {
+ successCallback();
+ }
+ };
+ tx.objectStore(FILE_STORE_)['delete'](fullPath);
+ };
+
+ idb_.put = function (entry, storagePath, successCallback, errorCallback, retry) {
+ if (!this.db) {
+ if (errorCallback) {
+ errorCallback(FileError.INVALID_MODIFICATION_ERR);
+ }
+ return;
+ }
+
+ var tx = this.db.transaction([FILE_STORE_], 'readwrite');
+ tx.onabort = errorCallback || onError;
+ tx.oncomplete = function () {
+ // TODO: Error is thrown if we pass the request event back instead.
+ successCallback(entry);
+ };
+
+ try {
+ tx.objectStore(FILE_STORE_).put(entry, storagePath);
+ } catch (e) {
+ if (e.name === 'DataCloneError') {
+ tx.oncomplete = null;
+ /*
+ Safari private browse mode cannot store Blob object to indexeddb.
+ Then use pure json object instead of Blob object.
+ */
+
+ var successCallback2 = function (entry) {
+ entry.file_ = new MyFile(entry.file_json.opt);
+ delete entry.file_json;
+ successCallback(entry);
+ };
+
+ if (!retry) {
+ if (entry.file_ && entry.file_ instanceof MyFile && entry.file_.blob_) {
+ MyFileHelper.toJson(entry.file_, function (json) {
+ entry.file_json = json;
+ delete entry.file_;
+ idb_.put(entry, storagePath, successCallback2, errorCallback, true);
+ });
+ return;
+ }
+ }
+ }
+ throw e;
+ }
+ };
+
+ // Global error handler. Errors bubble from request, to transaction, to db.
+ function onError (e) {
+ switch (e.target.errorCode) {
+ case 12:
+ console.log('Error - Attempt to open db with a lower version than the ' +
+ 'current one.');
+ break;
+ default:
+ console.log('errorCode: ' + e.target.errorCode);
+ }
+
+ console.log(e, e.code, e.message);
+ }
+
+ })(module.exports, window);
+
+ require('cordova/exec/proxy').add('File', module.exports);
+})();
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h
new file mode 100644
index 00000000..e09e2250
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.h
@@ -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.
+ */
+
+#import "CDVFile.h"
+
+extern NSString* const kCDVAssetsLibraryPrefix;
+extern NSString* const kCDVAssetsLibraryScheme;
+
+@interface CDVAssetLibraryFilesystem : NSObject<CDVFileSystem> {
+}
+
+- (id) initWithName:(NSString *)name;
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m
new file mode 100644
index 00000000..8486b7be
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVAssetLibraryFilesystem.m
@@ -0,0 +1,253 @@
+/*
+ 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 "CDVAssetLibraryFilesystem.h"
+#import <Cordova/CDV.h>
+#import <AssetsLibrary/ALAsset.h>
+#import <AssetsLibrary/ALAssetRepresentation.h>
+#import <AssetsLibrary/ALAssetsLibrary.h>
+#import <MobileCoreServices/MobileCoreServices.h>
+
+NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
+NSString* const kCDVAssetsLibraryScheme = @"assets-library";
+
+@implementation CDVAssetLibraryFilesystem
+@synthesize name=_name, urlTransformer;
+
+
+/*
+ The CDVAssetLibraryFilesystem works with resources which are identified
+ by iOS as
+ asset-library://<path>
+ and represents them internally as URLs of the form
+ cdvfile://localhost/assets-library/<path>
+ */
+
+- (NSURL *)assetLibraryURLForLocalURL:(CDVFilesystemURL *)url
+{
+ if ([url.url.scheme isEqualToString:kCDVFilesystemURLPrefix]) {
+ NSString *path = [[url.url absoluteString] substringFromIndex:[@"cdvfile://localhost/assets-library" length]];
+ return [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@", path]];
+ }
+ return url.url;
+}
+
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
+{
+ NSDictionary* entry = [self makeEntryForLocalURL:url];
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
+}
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
+ return [self makeEntryForPath:url.fullPath isDirectory:NO];
+}
+
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
+{
+ NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
+ NSString* lastPart = [fullPath lastPathComponent];
+ if (isDir && ![fullPath hasSuffix:@"/"]) {
+ fullPath = [fullPath stringByAppendingString:@"/"];
+ }
+ [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
+ [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
+ [dirEntry setObject:fullPath forKey:@"fullPath"];
+ [dirEntry setObject:lastPart forKey:@"name"];
+ [dirEntry setObject:self.name forKey: @"filesystemName"];
+
+ NSURL* nativeURL = [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@",fullPath]];
+ if (self.urlTransformer) {
+ nativeURL = self.urlTransformer(nativeURL);
+ }
+ dirEntry[@"nativeURL"] = [nativeURL absoluteString];
+
+ return dirEntry;
+}
+
+/* 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";
+ } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
+ mimeType = @"text/css";
+ }
+ }
+ CFRelease(typeId);
+ }
+ }
+ return mimeType;
+}
+
+- (id)initWithName:(NSString *)name
+{
+ if (self) {
+ self.name = name;
+ }
+ return self;
+}
+
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
+{
+ // return unsupported result for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."];
+}
+
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
+{
+ // we don't (yet?) support getting the parent of an asset
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR];
+}
+
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
+{
+ // setMetadata doesn't make sense for asset library files
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+}
+
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
+{
+ // return error for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+}
+
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
+{
+ // return error for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."];
+}
+
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
+{
+ // return unsupported result for assets-library URLs
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."];
+}
+
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
+{
+ // assets-library files can't be truncated
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+}
+
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
+{
+ // text can't be written into assets-library files
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+}
+
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
+{
+ // Copying to an assets library file is not doable, since we can't write it.
+ CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+ callback(result);
+}
+
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
+{
+ NSString *path = nil;
+ if ([[url.url scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
+ path = [url.url path];
+ } else {
+ path = url.fullPath;
+ }
+ if ([path hasSuffix:@"/"]) {
+ path = [path substringToIndex:([path length]-1)];
+ }
+ return path;
+}
+
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
+{
+ ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
+ if (asset) {
+ // We have the asset! Get the data and send it off.
+ ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
+ NSUInteger size = (end > start) ? (end - start) : [assetRepresentation size];
+ Byte* buffer = (Byte*)malloc(size);
+ NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:start length: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:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
+}
+
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
+{
+ // 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:localURL.fullPath forKey:@"fullPath"];
+ NSString* filename = [assetRepresentation filename];
+ [fileInfo setObject:filename forKey:@"name"];
+ [fileInfo setObject:[CDVAssetLibraryFilesystem getMimeTypeFromPath:filename] forKey:@"type"];
+ NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate];
+ NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000];
+ [fileInfo setObject:msDate forKey:@"lastModifiedDate"];
+
+ callback([CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]);
+ } else {
+ // We couldn't find the asset. Send the appropriate error.
+ callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]);
+ }
+ };
+ ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
+ // Retrieving the asset failed for some reason. Send the appropriate error.
+ callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]);
+ };
+
+ ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
+ [assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
+ return;
+}
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.h b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.h
new file mode 100644
index 00000000..987c66b5
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.h
@@ -0,0 +1,157 @@
+/*
+ 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>
+
+extern NSString* const kCDVAssetsLibraryPrefix;
+extern NSString* const kCDVFilesystemURLPrefix;
+
+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;
+
+@interface CDVFilesystemURL : NSObject {
+ NSURL *_url;
+ NSString *_fileSystemName;
+ NSString *_fullPath;
+}
+
+- (id) initWithString:(NSString*)strURL;
+- (id) initWithURL:(NSURL*)URL;
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
+
+- (NSString *)absoluteURL;
+
+@property (atomic) NSURL *url;
+@property (atomic) NSString *fileSystemName;
+@property (atomic) NSString *fullPath;
+
+@end
+
+@interface CDVFilesystemURLProtocol : NSURLProtocol
+@end
+
+@protocol CDVFileSystem
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
+- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
+
+@property (nonatomic,strong) NSString *name;
+@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*);
+
+@optional
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
+
+@end
+
+@interface CDVFile : CDVPlugin {
+ NSString* rootDocsPath;
+ NSString* appDocsPath;
+ NSString* appLibraryPath;
+ NSString* appTempPath;
+
+ NSMutableArray* fileSystems_;
+ BOOL userHasAllowed;
+}
+
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
+
+- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL;
+
+/* Native Registration API */
+- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs;
+- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName;
+
+/* Exec API */
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
+- (void)getDirectory:(CDVInvokedUrlCommand*)command;
+- (void)getFile:(CDVInvokedUrlCommand*)command;
+- (void)getParent:(CDVInvokedUrlCommand*)command;
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
+- (void)remove:(CDVInvokedUrlCommand*)command;
+- (void)copyTo:(CDVInvokedUrlCommand*)command;
+- (void)moveTo:(CDVInvokedUrlCommand*)command;
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
+- (void)readEntries:(CDVInvokedUrlCommand*)command;
+- (void)readAsText:(CDVInvokedUrlCommand*)command;
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
+- (void)write:(CDVInvokedUrlCommand*)command;
+- (void)testFileExists:(CDVInvokedUrlCommand*)command;
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
+- (void)truncate:(CDVInvokedUrlCommand*)command;
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
+
+/* Compatibilty with older File API */
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
+
+/* Conversion between filesystem paths and URLs */
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
+
+/* Internal methods for testing */
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
+
+@property (nonatomic, strong) NSString* rootDocsPath;
+@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 (nonatomic, strong) NSMutableArray* fileSystems;
+
+@property BOOL userHasAllowed;
+
+@end
+
+#define kW3FileTemporary @"temporary"
+#define kW3FilePersistent @"persistent"
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.m b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.m
new file mode 100644
index 00000000..59e7d640
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVFile.m
@@ -0,0 +1,1119 @@
+/*
+ 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 <Cordova/CDV.h>
+#import "CDVFile.h"
+#import "CDVLocalFilesystem.h"
+#import "CDVAssetLibraryFilesystem.h"
+#import <objc/message.h>
+
+static NSString* toBase64(NSData* data) {
+ SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
+ SEL s2 = NSSelectorFromString(@"base64EncodedString");
+ SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
+
+ if ([data respondsToSelector:s1]) {
+ NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
+ return func(data, s1);
+ } else if ([data respondsToSelector:s2]) {
+ NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s2];
+ return func(data, s2);
+ } else if ([data respondsToSelector:s3]) {
+ NSString* (*func)(id, SEL, NSUInteger) = (void *)[data methodForSelector:s3];
+ return func(data, s3, 0);
+ } else {
+ return nil;
+ }
+}
+
+CDVFile *filePlugin = nil;
+
+extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import));
+
+#ifndef __IPHONE_5_1
+ NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey";
+#endif
+
+NSString* const kCDVFilesystemURLPrefix = @"cdvfile";
+
+@implementation CDVFilesystemURL
+@synthesize url=_url;
+@synthesize fileSystemName=_fileSystemName;
+@synthesize fullPath=_fullPath;
+
+- (id) initWithString:(NSString *)strURL
+{
+ if ( self = [super init] ) {
+ NSURL *decodedURL = [NSURL URLWithString:strURL];
+ return [self initWithURL:decodedURL];
+ }
+ return nil;
+}
+
+-(id) initWithURL:(NSURL *)URL
+{
+ if ( self = [super init] ) {
+ self.url = URL;
+ self.fileSystemName = [self filesystemNameForLocalURI:URL];
+ self.fullPath = [self fullPathForLocalURI:URL];
+ }
+ return self;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString FileSystem Name for this URI, or nil if it is not recognized.
+ */
+- (NSString *)filesystemNameForLocalURI:(NSURL *)uri
+{
+ if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
+ NSArray *pathComponents = [uri pathComponents];
+ if (pathComponents != nil && pathComponents.count > 1) {
+ return [pathComponents objectAtIndex:1];
+ }
+ } else if ([[uri scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
+ return @"assets-library";
+ }
+ return nil;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString fullPath component suitable for an Entry object.
+ * The incoming URI should be properly escaped. The returned fullPath is unescaped.
+ */
+- (NSString *)fullPathForLocalURI:(NSURL *)uri
+{
+ if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
+ NSString *path = [uri path];
+ if ([uri query]) {
+ path = [NSString stringWithFormat:@"%@?%@", path, [uri query]];
+ }
+ NSRange slashRange = [path rangeOfString:@"/" options:0 range:NSMakeRange(1, path.length-1)];
+ if (slashRange.location == NSNotFound) {
+ return @"";
+ }
+ return [path substringFromIndex:slashRange.location];
+ } else if ([[uri scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
+ return [[uri absoluteString] substringFromIndex:[kCDVAssetsLibraryScheme length]+2];
+ }
+ return nil;
+}
+
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL
+{
+ return [[CDVFilesystemURL alloc] initWithString:strURL];
+}
+
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL
+{
+ return [[CDVFilesystemURL alloc] initWithURL:URL];
+}
+
+- (NSString *)absoluteURL
+{
+ return [NSString stringWithFormat:@"cdvfile://localhost/%@%@", self.fileSystemName, self.fullPath];
+}
+
+@end
+
+@implementation CDVFilesystemURLProtocol
+
++ (BOOL)canInitWithRequest:(NSURLRequest*)request
+{
+ NSURL* url = [request URL];
+ return [[url scheme] isEqualToString:kCDVFilesystemURLPrefix];
+}
+
++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
+{
+ return request;
+}
+
++ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB
+{
+ return [[[requestA URL] resourceSpecifier] isEqualToString:[[requestB URL] resourceSpecifier]];
+}
+
+- (void)startLoading
+{
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithURL:[[self request] URL]];
+ NSObject<CDVFileSystem> *fs = [filePlugin filesystemForURL:url];
+ __weak CDVFilesystemURLProtocol* weakSelf = self;
+
+ [fs readFileAtURL:url start:0 end:-1 callback:^void(NSData *data, NSString *mimetype, CDVFileError error) {
+ NSMutableDictionary* responseHeaders = [[NSMutableDictionary alloc] init];
+ responseHeaders[@"Cache-Control"] = @"no-cache";
+
+ if (!error) {
+ responseHeaders[@"Content-Type"] = mimetype;
+ NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:200 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
+ [[weakSelf client] URLProtocol:weakSelf didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [[weakSelf client] URLProtocol:weakSelf didLoadData:data];
+ [[weakSelf client] URLProtocolDidFinishLoading:weakSelf];
+ } else {
+ NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:404 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
+ [[weakSelf client] URLProtocol:weakSelf didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [[weakSelf client] URLProtocolDidFinishLoading:weakSelf];
+ }
+ }];
+}
+
+- (void)stopLoading
+{}
+
+- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
+ willCacheResponse:(NSCachedURLResponse*)cachedResponse {
+ return nil;
+}
+
+@end
+
+
+@implementation CDVFile
+
+@synthesize rootDocsPath, appDocsPath, appLibraryPath, appTempPath, userHasAllowed, fileSystems=fileSystems_;
+
+- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs {
+ __weak CDVFile* weakSelf = self;
+ SEL sel = NSSelectorFromString(@"urlTransformer");
+ // for backwards compatibility - we check if this property is there
+ // we create a wrapper block because the urlTransformer property
+ // on the commandDelegate might be set dynamically at a future time
+ // (and not dependent on plugin loading order)
+ if ([self.commandDelegate respondsToSelector:sel]) {
+ fs.urlTransformer = ^NSURL*(NSURL* urlToTransform) {
+ // grab the block from the commandDelegate
+ NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(weakSelf.commandDelegate, sel);
+ // if block is not null, we call it
+ if (urlTransformer) {
+ return urlTransformer(urlToTransform);
+ } else { // else we return the same url
+ return urlToTransform;
+ }
+ };
+ }
+ [fileSystems_ addObject:fs];
+}
+
+- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName
+{
+ if (self.fileSystems != nil) {
+ for (NSObject<CDVFileSystem> *fs in self.fileSystems) {
+ if ([fs.name isEqualToString:fsName]) {
+ return fs;
+ }
+ }
+ }
+ return nil;
+}
+
+- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL {
+ if (localURL.fileSystemName == nil) return nil;
+ @try {
+ return [self fileSystemByName:localURL.fileSystemName];
+ }
+ @catch (NSException *e) {
+ return nil;
+ }
+}
+
+- (NSArray *)getExtraFileSystemsPreference:(UIViewController *)vc
+{
+ NSString *filesystemsStr = nil;
+ if([self.viewController isKindOfClass:[CDVViewController class]]) {
+ CDVViewController *vc = (CDVViewController *)self.viewController;
+ NSDictionary *settings = [vc settings];
+ filesystemsStr = [settings[@"iosextrafilesystems"] lowercaseString];
+ }
+ if (!filesystemsStr) {
+ filesystemsStr = @"library,library-nosync,documents,documents-nosync,cache,bundle,root";
+ }
+ return [filesystemsStr componentsSeparatedByString:@","];
+}
+
+- (void)makeNonSyncable:(NSString*)path {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil];
+ NSURL* url = [NSURL fileURLWithPath:path];
+ [url setResourceValue: [NSNumber numberWithBool: YES]
+ forKey: NSURLIsExcludedFromBackupKey error:nil];
+
+}
+
+- (void)registerExtraFileSystems:(NSArray *)filesystems fromAvailableSet:(NSDictionary *)availableFileSystems
+{
+ NSMutableSet *installedFilesystems = [[NSMutableSet alloc] initWithCapacity:7];
+
+ /* Build non-syncable directories as necessary */
+ for (NSString *nonSyncFS in @[@"library-nosync", @"documents-nosync"]) {
+ if ([filesystems containsObject:nonSyncFS]) {
+ [self makeNonSyncable:availableFileSystems[nonSyncFS]];
+ }
+ }
+
+ /* Register filesystems in order */
+ for (NSString *fsName in filesystems) {
+ if (![installedFilesystems containsObject:fsName]) {
+ NSString *fsRoot = availableFileSystems[fsName];
+ if (fsRoot) {
+ [filePlugin registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:fsName root:fsRoot]];
+ [installedFilesystems addObject:fsName];
+ } else {
+ NSLog(@"Unrecognized extra filesystem identifier: %@", fsName);
+ }
+ }
+ }
+}
+
+- (NSDictionary *)getAvailableFileSystems
+{
+ NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+ NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+ return @{
+ @"library": libPath,
+ @"library-nosync": [libPath stringByAppendingPathComponent:@"NoCloud"],
+ @"documents": docPath,
+ @"documents-nosync": [docPath stringByAppendingPathComponent:@"NoCloud"],
+ @"cache": [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0],
+ @"bundle": [[NSBundle mainBundle] bundlePath],
+ @"root": @"/"
+ };
+}
+
+- (void)pluginInitialize
+{
+ filePlugin = self;
+ [NSURLProtocol registerClass:[CDVFilesystemURLProtocol class]];
+
+ fileSystems_ = [[NSMutableArray alloc] initWithCapacity:3];
+
+ // Get the Library directory path
+ NSArray* paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ self.appLibraryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"files"];
+
+ // Get the Temporary directory path
+ self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory()
+
+ // Get the Documents directory path
+ paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ self.rootDocsPath = [paths objectAtIndex:0];
+ self.appDocsPath = [self.rootDocsPath stringByAppendingPathComponent:@"files"];
+
+
+ NSString *location = nil;
+ if([self.viewController isKindOfClass:[CDVViewController class]]) {
+ CDVViewController *vc = (CDVViewController *)self.viewController;
+ NSMutableDictionary *settings = vc.settings;
+ location = [[settings objectForKey:@"iospersistentfilelocation"] lowercaseString];
+ }
+ if (location == nil) {
+ // Compatibilty by default (if the config preference is not set, or
+ // if we're not embedded in a CDVViewController somehow.)
+ location = @"compatibility";
+ }
+
+ NSError *error;
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:self.appTempPath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error]) {
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"temporary" root:self.appTempPath]];
+ } else {
+ NSLog(@"Unable to create temporary directory: %@", error);
+ }
+ if ([location isEqualToString:@"library"]) {
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:self.appLibraryPath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error]) {
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.appLibraryPath]];
+ } else {
+ NSLog(@"Unable to create library directory: %@", error);
+ }
+ } else if ([location isEqualToString:@"compatibility"]) {
+ /*
+ * Fall-back to compatibility mode -- this is the logic implemented in
+ * earlier versions of this plugin, and should be maintained here so
+ * that apps which were originally deployed with older versions of the
+ * plugin can continue to provide access to files stored under those
+ * versions.
+ */
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.rootDocsPath]];
+ } else {
+ NSAssert(false,
+ @"File plugin configuration error: Please set iosPersistentFileLocation in config.xml to one of \"library\" (for new applications) or \"compatibility\" (for compatibility with previous versions)");
+ }
+ [self registerFilesystem:[[CDVAssetLibraryFilesystem alloc] initWithName:@"assets-library"]];
+
+ [self registerExtraFileSystems:[self getExtraFileSystemsPreference:self.viewController]
+ fromAvailableSet:[self getAvailableFileSystems]];
+
+}
+
+- (CDVFilesystemURL *)fileSystemURLforArg:(NSString *)urlArg
+{
+ CDVFilesystemURL* ret = nil;
+ if ([urlArg hasPrefix:@"file://"]) {
+ /* This looks like a file url. Get the path, and see if any handlers recognize it. */
+ NSURL *fileURL = [NSURL URLWithString:urlArg];
+ NSURL *resolvedFileURL = [fileURL URLByResolvingSymlinksInPath];
+ NSString *path = [resolvedFileURL path];
+ ret = [self fileSystemURLforLocalPath:path];
+ } else {
+ ret = [CDVFilesystemURL fileSystemURLWithString:urlArg];
+ }
+ return ret;
+}
+
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath
+{
+ CDVFilesystemURL *localURL = nil;
+ NSUInteger shortestFullPath = 0;
+
+ // Try all installed filesystems, in order. Return the most match url.
+ for (id object in self.fileSystems) {
+ if ([object respondsToSelector:@selector(URLforFilesystemPath:)]) {
+ CDVFilesystemURL *url = [object URLforFilesystemPath:localPath];
+ if (url){
+ // A shorter fullPath would imply that the filesystem is a better match for the local path
+ if (!localURL || ([[url fullPath] length] < shortestFullPath)) {
+ localURL = url;
+ shortestFullPath = [[url fullPath] length];
+ }
+ }
+ }
+ }
+ return localURL;
+}
+
+- (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;
+}
+
+/* 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
+{
+ // arguments
+ NSString* strType = [command argumentAtIndex:0];
+ unsigned long long size = [[command argumentAtIndex:1] longLongValue];
+
+ int type = [strType intValue];
+ CDVPluginResult* result = nil;
+
+ if (type >= self.fileSystems.count) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"No filesystem of type requested");
+ } else {
+ NSString* fullPath = @"/";
+ // check for avail space for size request
+ NSNumber* pNumAvail = [self checkFreeDiskSpace:self.rootDocsPath];
+ // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]);
+ if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR];
+ } else {
+ NSObject<CDVFileSystem> *rootFs = [self.fileSystems objectAtIndex:type];
+ if (rootFs == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"No filesystem of type requested");
+ } else {
+ NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2];
+ [fileSystem setObject:rootFs.name forKey:@"name"];
+ NSDictionary* dirEntry = [self makeEntryForPath:fullPath fileSystemName:rootFs.name isDirectory:YES];
+ [fileSystem setObject:dirEntry forKey:@"root"];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
+ }
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+
+- (void)requestAllFileSystems:(CDVInvokedUrlCommand*)command
+{
+ NSMutableArray* ret = [[NSMutableArray alloc] init];
+ for (NSObject<CDVFileSystem>* root in fileSystems_) {
+ [ret addObject:[self makeEntryForPath:@"/" fileSystemName:root.name isDirectory:YES]];
+ }
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:ret];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)requestAllPaths:(CDVInvokedUrlCommand*)command
+{
+ NSString* libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];
+ NSString* libPathSync = [libPath stringByAppendingPathComponent:@"Cloud"];
+ NSString* libPathNoSync = [libPath stringByAppendingPathComponent:@"NoCloud"];
+ NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+ NSString* storagePath = [libPath stringByDeletingLastPathComponent];
+ NSString* cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
+
+ // Create the directories if necessary.
+ [[NSFileManager defaultManager] createDirectoryAtPath:libPathSync withIntermediateDirectories:YES attributes:nil error:nil];
+ [[NSFileManager defaultManager] createDirectoryAtPath:libPathNoSync withIntermediateDirectories:YES attributes:nil error:nil];
+ // Mark NoSync as non-iCloud.
+ [[NSURL fileURLWithPath:libPathNoSync] setResourceValue: [NSNumber numberWithBool: YES]
+ forKey: NSURLIsExcludedFromBackupKey error:nil];
+
+ NSDictionary* ret = @{
+ @"applicationDirectory": [[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]] absoluteString],
+ @"applicationStorageDirectory": [[NSURL fileURLWithPath:storagePath] absoluteString],
+ @"dataDirectory": [[NSURL fileURLWithPath:libPathNoSync] absoluteString],
+ @"syncedDataDirectory": [[NSURL fileURLWithPath:libPathSync] absoluteString],
+ @"documentsDirectory": [[NSURL fileURLWithPath:docPath] absoluteString],
+ @"cacheDirectory": [[NSURL fileURLWithPath:cachePath] absoluteString],
+ @"tempDirectory": [[NSURL fileURLWithPath:NSTemporaryDirectory()] absoluteString]
+ };
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:ret];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* Creates and returns a dictionary representing an Entry Object
+ *
+ * IN:
+ * NSString* fullPath of the entry
+ * int fsType - 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
+ * NSString* filesystemName - FileSystem name -- actual filesystem will be created on the JS side if necessary, to avoid
+ * creating circular reference (FileSystem contains DirectoryEntry which contains FileSystem.....!!)
+ */
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir
+{
+ NSObject<CDVFileSystem> *fs = [self fileSystemByName:fsName];
+ return [fs makeEntryForPath:fullPath isDirectory:isDir];
+}
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)localURL
+{
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURL];
+ return [fs makeEntryForLocalURL:localURL];
+}
+
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL
+{
+ CDVFilesystemURL* fsURL = [self fileSystemURLforArg:[URL absoluteString]];
+ return [self makeEntryForLocalURL:fsURL];
+}
+
+/*
+ * Given a URI determine the File System information associated with it and return an appropriate W3C entry object
+ * IN
+ * NSString* localURI: Should be an escaped local filesystem 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* localURIstr = [command argumentAtIndex:0];
+ CDVPluginResult* result;
+
+ localURIstr = [self encodePath:localURIstr]; //encode path before resolving
+ CDVFilesystemURL* inputURI = [self fileSystemURLforArg:localURIstr];
+
+ if (inputURI == nil || inputURI.fileSystemName == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else {
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:inputURI];
+ if (fs == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else {
+ result = [fs entryForLocalURI:inputURI];
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+//encode path with percent escapes
+-(NSString *)encodePath:(NSString *)path
+{
+ NSString *decodedPath = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //decode incase it's already encoded to avoid encoding twice
+ return [decodedPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+}
+
+
+/* Part of DirectoryEntry interface, creates or returns the specified directory
+ * IN:
+ * NSString* localURI - local filesystem URI 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 = [command argumentAtIndex: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* baseURI - local filesytem URI for the base directory to search
+ * NSString* requestedPath - file to be created/returned; may be absolute path or relative path
+ * NSDictionary* options - 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
+{
+ NSString* baseURIstr = [command argumentAtIndex:0];
+ CDVFilesystemURL* baseURI = [self fileSystemURLforArg:baseURIstr];
+ NSString* requestedPath = [command argumentAtIndex:1];
+ NSDictionary* options = [command argumentAtIndex:2 withDefault:nil];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:baseURI];
+ CDVPluginResult* result = [fs getFileForURL:baseURI requestedPath:requestedPath options:options];
+
+
+ [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* localURI
+ * NSMutableDictionary* options
+ * empty
+ */
+- (void)getParent:(CDVInvokedUrlCommand*)command
+{
+ // arguments are URL encoded
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult* result = [fs getParentForURL:localURI];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * set MetaData of entry
+ * Currently we only support "com.apple.MobileBackup" (boolean)
+ */
+- (void)setMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSDictionary* options = [command argumentAtIndex:1 withDefault:nil];
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult* result = [fs setMetadataForURL:localURI withObject:options];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* removes the directory or file entry
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ *
+ * 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
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ CDVPluginResult* result = nil;
+
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // error if try to remove top level (documents or tmp) dir
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ result = [fs removeFileAtURL:localURI];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* recursively removes the directory
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ *
+ * 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
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ CDVPluginResult* result = nil;
+
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // error if try to remove top level (documents or tmp) dir
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ result = [fs recursiveRemoveFileAtURL:localURI];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)copyTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:YES];
+}
+
+- (void)moveTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:NO];
+}
+
+/* Copy/move a file or directory to a new location
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* URL of entry to copy
+ * 1 - NSString* URL of the directory into which to copy/move the entry
+ * 2 - Optionally, the new name of the entry, defaults to the current name
+ * BOOL - bCopy YES if copy, NO if move
+ */
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy
+{
+ NSArray* arguments = command.arguments;
+
+ // arguments
+ NSString* srcURLstr = [command argumentAtIndex:0];
+ NSString* destURLstr = [command argumentAtIndex:1];
+
+ CDVPluginResult *result;
+
+ if (!srcURLstr || !destURLstr) {
+ // either no source or no destination provided
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVFilesystemURL* srcURL = [self fileSystemURLforArg:srcURLstr];
+ CDVFilesystemURL* destURL = [self fileSystemURLforArg:destURLstr];
+
+ NSObject<CDVFileSystem> *srcFs = [self filesystemForURL:srcURL];
+ NSObject<CDVFileSystem> *destFs = [self filesystemForURL:destURL];
+
+ // optional argument; use last component from srcFullPath if new name not provided
+ NSString* newName = ([arguments count] > 2) ? [command argumentAtIndex:2] : [srcURL.url lastPathComponent];
+ if ([newName rangeOfString:@":"].location != NSNotFound) {
+ // invalid chars in new name
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ __weak CDVFile* weakSelf = self;
+ [self.commandDelegate runInBackground:^ {
+ [destFs copyFileToURL:destURL withName:newName fromFileSystem:srcFs atURL:srcURL copy:bCopy callback:^(CDVPluginResult* result) {
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+
+}
+
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ __weak CDVFile* weakSelf = self;
+ [fs getFileMetadataForURL:localURI callback:^(CDVPluginResult* result) {
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+- (void)readEntries:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult *result = [fs readEntriesAtURL:localURI];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* 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
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSString* encoding = [command argumentAtIndex:1];
+ NSInteger start = [[command argumentAtIndex:2] integerValue];
+ NSInteger end = [[command argumentAtIndex:3] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ if (fs == nil) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ // 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;
+ }
+
+ __weak CDVFile* weakSelf = self;
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI 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];
+ }
+
+ [weakSelf.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
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ __weak CDVFile* weakSelf = self;
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* b64Str = toBase64(data);
+ NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, b64Str];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+
+ [weakSelf.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
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ __weak CDVFile* weakSelf = self;
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI 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];
+ }
+
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+}
+
+- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ __weak CDVFile* weakSelf = self;
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI 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];
+ }
+
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+ }];
+}
+
+
+- (void)truncate:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ unsigned long long pos = (unsigned long long)[[command argumentAtIndex:1] longLongValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult *result = [fs truncateFileAtURL:localURI atPosition:pos];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* write
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI of file to write to
+ * 1 - NSString* or NSData* data to write
+ * 2 - NSNumber* position to begin writing
+ */
+- (void)write:(CDVInvokedUrlCommand*)command
+{
+ __weak CDVFile* weakSelf = self;
+
+ [self.commandDelegate runInBackground:^ {
+ NSString* callbackId = command.callbackId;
+
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ id argData = [command argumentAtIndex:1];
+ unsigned long long pos = (unsigned long long)[[command argumentAtIndex:2] longLongValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+
+ [fs truncateFileAtURL:localURI atPosition:pos];
+ CDVPluginResult *result;
+ if ([argData isKindOfClass:[NSString class]]) {
+ NSData *encData = [argData dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+ result = [fs writeToFileAtURL:localURI withData:encData append:YES];
+ } else if ([argData isKindOfClass:[NSData class]]) {
+ result = [fs writeToFileAtURL:localURI withData:argData append:YES];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"];
+ }
+ [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }];
+}
+
+#pragma mark Methods for converting between URLs and paths
+
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURL
+{
+ for (NSObject<CDVFileSystem> *fs in self.fileSystems) {
+ if ([fs.name isEqualToString:localURL.fileSystemName]) {
+ if ([fs respondsToSelector:@selector(filesystemPathForURL:)]) {
+ return [fs filesystemPathForURL:localURL];
+ }
+ }
+ }
+ return nil;
+}
+
+#pragma mark Undocumented Filesystem API
+
+- (void)testFileExists:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command argumentAtIndex: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 argumentAtIndex: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.rootDocsPath];
+
+ 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];
+}
+
+#pragma mark Compatibility with older File API
+
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath
+{
+ return [CDVLocalFilesystem getMimeTypeFromPath:fullPath];
+}
+
+- (NSDictionary *)getDirectoryEntry:(NSString *)localPath isDirectory:(BOOL)bDirRequest
+{
+ CDVFilesystemURL *localURL = [self fileSystemURLforLocalPath:localPath];
+ return [self makeEntryForPath:localURL.fullPath fileSystemName:localURL.fileSystemName isDirectory:bDirRequest];
+}
+
+#pragma mark Internal methods for testing
+// Internal methods for testing: Get the on-disk location of a local filesystem url.
+// [Currently used for testing file-transfer]
+
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURL = [self fileSystemURLforArg:command.arguments[0]];
+
+ NSString* fsPath = [self filesystemPathForURL:localURL];
+ CDVPluginResult* result;
+ if (fsPath) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:fsPath];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot resolve URL to a file"];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h
new file mode 100644
index 00000000..a0186c85
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.h
@@ -0,0 +1,32 @@
+/*
+ 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"
+
+@interface CDVLocalFilesystem : NSObject<CDVFileSystem> {
+ NSString *_name;
+ NSString *_fsRoot;
+}
+
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+
+@property (nonatomic,strong) NSString *fsRoot;
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m
new file mode 100644
index 00000000..c340ce0c
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/ios/CDVLocalFilesystem.m
@@ -0,0 +1,750 @@
+/*
+ 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 "CDVLocalFilesystem.h"
+#import <Cordova/CDV.h>
+#import <MobileCoreServices/MobileCoreServices.h>
+#import <sys/xattr.h>
+
+@implementation CDVLocalFilesystem
+@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
+
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
+{
+ if (self) {
+ self.name = name;
+ self.fsRoot = fsRoot;
+ }
+ return self;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
+ * URI represents a non-existent path, or is unrecognized or otherwise malformed.
+ */
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
+{
+ CDVPluginResult* result = nil;
+ NSDictionary* entry = [self makeEntryForLocalURL:url];
+ if (entry) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
+ } else {
+ // return NOT_FOUND_ERR
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ return result;
+}
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
+ NSString *path = [self filesystemPathForURL:url];
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL isDir = NO;
+ // see if exists and is file or dir
+ BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
+ if (bExists) {
+ return [self makeEntryForPath:url.fullPath isDirectory:isDir];
+ } else {
+ return nil;
+ }
+}
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
+{
+ NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
+ NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
+ if (isDir && ![fullPath hasSuffix:@"/"]) {
+ fullPath = [fullPath stringByAppendingString:@"/"];
+ }
+ [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
+ [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
+ [dirEntry setObject:fullPath forKey:@"fullPath"];
+ [dirEntry setObject:lastPart forKey:@"name"];
+ [dirEntry setObject:self.name forKey: @"filesystemName"];
+
+ NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
+ if (self.urlTransformer) {
+ nativeURL = self.urlTransformer(nativeURL);
+ }
+
+ dirEntry[@"nativeURL"] = [nativeURL absoluteString];
+
+ return dirEntry;
+}
+
+- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
+{
+ NSRange questionMark = [fullPath rangeOfString:@"?"];
+ if (questionMark.location != NSNotFound) {
+ return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
+ }
+ return fullPath;
+}
+
+- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
+{
+ NSString *path = nil;
+ NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
+ path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
+ if ([path length] > 1 && [path hasSuffix:@"/"]) {
+ path = [path substringToIndex:([path length]-1)];
+ }
+ return path;
+}
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
+ * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
+ * or if the URL is malformed.
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
+ */
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
+{
+ return [self filesystemPathForFullPath:url.fullPath];
+}
+
+- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
+{
+ if (fullPath) {
+ NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ if ([fullPath hasPrefix:@"/"]) {
+ return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
+ }
+ return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
+ }
+ return nil;
+}
+
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
+{
+ return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
+
+}
+
+- (NSString *)normalizePath:(NSString *)rawPath
+{
+ // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
+ BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
+ if (isAbsolutePath) {
+ rawPath = [rawPath substringFromIndex:1];
+ }
+ NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
+ for (int index = 0; index < [components count]; ++index) {
+ if ([[components objectAtIndex:index] isEqualToString:@".."]) {
+ [components removeObjectAtIndex:index];
+ if (index > 0) {
+ [components removeObjectAtIndex:index-1];
+ --index;
+ }
+ }
+ }
+
+ if (isAbsolutePath) {
+ return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
+ } else {
+ return [components componentsJoinedByString:@"/"];
+ }
+
+
+}
+
+- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
+{
+ BOOL bNumber = NO;
+ NSObject* value = dict[key];
+ if (value) {
+ bNumber = [value isKindOfClass:[NSNumber class]];
+ }
+ return bNumber;
+}
+
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
+{
+ CDVPluginResult* result = nil;
+ BOOL bDirRequest = NO;
+ BOOL create = NO;
+ BOOL exclusive = NO;
+ int errorCode = 0; // !!! risky - no error code currently defined for 0
+
+ if ([self valueForKeyIsNumber:options key:@"create"]) {
+ create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
+ }
+ if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
+ exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
+ }
+ if ([self valueForKeyIsNumber:options key:@"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 {
+ // Build new fullPath for the requested resource.
+ // We concatenate the two paths together, and then scan the resulting string to remove
+ // parent ("..") references. Any parent references at the beginning of the string are
+ // silently removed.
+ NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
+ combinedPath = [self normalizePath:combinedPath];
+ CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
+ if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
+ // path exists and is not 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:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
+ } else {
+ // create the empty file
+ bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] 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 makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
+ }
+ } // are all possible conditions met?
+ }
+
+ if (errorCode > 0) {
+ // create error callback
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ return result;
+
+}
+
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
+{
+ CDVPluginResult* result = nil;
+ CDVFilesystemURL *newURI = nil;
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // return self
+ newURI = localURI;
+ } else {
+ newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
+ }
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
+ if (bExists) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
+ } else {
+ // invalid path or file does not exist
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ return result;
+}
+
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
+{
+ BOOL ok = NO;
+
+ NSString* filePath = [self filesystemPathForURL:localURI];
+ // 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) {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+ }
+}
+
+/* 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 removing filesystem entry at %@: %@", fullPath, [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;
+}
+
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
+{
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir = NO;
+ BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
+ if (!bExists) {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
+ // dir is not empty
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+ }
+ return [self doRemove:fileSystemPath];
+}
+
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
+{
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+ return [self doRemove:fileSystemPath];
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
+ * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
+ * or if the URL is malformed.
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
+ */
+- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
+{
+ if ([fsPath hasPrefix:self.fsRoot]) {
+ return [fsPath substringFromIndex:[self.fsRoot length]];
+ }
+ return nil;
+}
+
+
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
+{
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* __autoreleasing error = nil;
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+
+ NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath 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 = [fileSystemPath stringByAppendingPathComponent:name];
+ BOOL bIsDir = NO;
+ [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
+ NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
+ [entries addObject:entryDict];
+ }
+ }
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
+ } else {
+ // assume not found but could check error for more specific error conditions
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+}
+
+- (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;
+}
+
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
+{
+ unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
+}
+
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
+{
+ NSString *filePath = [self filesystemPathForURL:localURL];
+
+ 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];
+ if (len == 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
+ } else {
+ [fileStream open];
+
+ bytesWritten = (int)[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];
+ }
+ return result;
+}
+
+/**
+ * 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;
+}
+
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
+{
+ NSFileManager *fileMgr = [[NSFileManager alloc] init];
+ NSString *destRootPath = [self filesystemPathForURL:destURL];
+ BOOL bDestIsDir = NO;
+ BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
+
+ NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
+ NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
+
+ BOOL bNewIsDir = NO;
+ BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
+
+ CDVPluginResult *result = nil;
+ int errCode = 0;
+
+ if (!bDestExists) {
+ // the destination root does not exist
+ errCode = NOT_FOUND_ERR;
+ }
+
+ else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
+ /* Same FS, we can shortcut with NSFileManager operations */
+ NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
+
+ BOOL bSrcIsDir = NO;
+ BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
+
+ if (!bSrcExists) {
+ // the source does not exist
+ errCode = NOT_FOUND_ERR;
+ } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
+ // source and destination can not be the same
+ errCode = INVALID_MODIFICATION_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:newFileSystemPath]) {
+ // 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:newFileSystemPath 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:newFileSystemPath]) {
+ // can't move a dir into itself
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bNewExists) {
+ if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
+ // can't move dir to a dir that is not empty
+ errCode = INVALID_MODIFICATION_ERR;
+ newFileSystemPath = nil; // so we won't try to move
+ } else {
+ // remove destination so can perform the moveItemAtPath
+ bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
+ if (!bSuccess) {
+ errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
+ newFileSystemPath = nil;
+ }
+ }
+ } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
+ // can't move a directory inside itself or to any child at any depth;
+ errCode = INVALID_MODIFICATION_ERR;
+ newFileSystemPath = nil;
+ }
+
+ if (newFileSystemPath != nil) {
+ bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
+ }
+ }
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ 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;
+ }
+ }
+ }
+ }
+ } else {
+ // Need to copy the hard way
+ [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
+ }
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ callback(result);
+ }];
+ return; // Async IO; return without callback.
+ }
+ if (result == nil) {
+ if (!errCode) {
+ errCode = INVALID_MODIFICATION_ERR; // Catch-all default
+ }
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
+ }
+ callback(result);
+}
+
+/* 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";
+ } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
+ mimeType = @"text/css";
+ }
+ }
+ CFRelease(typeId);
+ }
+ }
+ return mimeType;
+}
+
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
+{
+ NSString *path = [self filesystemPathForURL:localURL];
+
+ NSString* mimeType = [CDVLocalFilesystem 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);
+}
+
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
+{
+ NSString *path = [self filesystemPathForURL:localURL];
+ CDVPluginResult *result;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ NSError* __autoreleasing error = nil;
+ NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
+
+ if (fileAttrs) {
+
+ // create dictionary of file info
+ NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
+
+ [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
+ [fileInfo setObject:[self mimeTypeForFileAtPath: path] forKey:@"type"];
+ [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
+
+ // Ensure that directories (and other non-regular files) report size of 0
+ unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
+ [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
+
+ NSDate* modDate = [fileAttrs fileModificationDate];
+ if (modDate) {
+ [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
+ }
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
+
+ } else {
+ // didn't get fileAttribs
+ CDVFileError errorCode = ABORT_ERR;
+ NSLog(@"error getting metadata: %@", [error localizedDescription]);
+ if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
+ errorCode = NOT_FOUND_ERR;
+ }
+ // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
+ }
+
+ callback(result);
+}
+
+// fix errors that base on Alexsander Akers from http://stackoverflow.com/a/5998683/2613194
+- (NSString*) mimeTypeForFileAtPath: (NSString *) path {
+ if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
+ return nil;
+ }
+
+ CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
+ CFStringRef mimeType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
+ CFRelease(UTI);
+
+ if (!mimeType) {
+ return @"application/octet-stream";
+ }
+ return (__bridge NSString *)mimeType;
+}
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.h b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.h
new file mode 100644
index 00000000..13a9360e
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.h
@@ -0,0 +1,189 @@
+/*
+ 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>
+
+NSString* const kCDVFilesystemURLPrefix;
+
+/**
+* The default filesystems if non are specified.
+*/
+#define CDV_FILESYSTEMS_DEFAULT @"documents,cache,bundle,root"
+
+/**
+* Preference name of the extra filesystems to be "mounted". the following are supported:
+* 'bundle' - mounts the application directory
+* 'documents' - mounts the users Documents directory (~/Documents)
+* 'root' - mounts the root file system
+* 'cache' - mounts the caches directory (~/Library/Caches/<bundle-id/)
+*/
+#define CDV_PREF_EXTRA_FILESYSTEM @"osxextrafilesystems"
+
+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;
+
+@interface CDVFilesystemURL : NSObject {
+ NSURL *_url;
+ NSString *_fileSystemName;
+ NSString *_fullPath;
+}
+
+- (id) initWithString:(NSString*)strURL;
+- (id) initWithURL:(NSURL*)URL;
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
+
+- (NSString *)absoluteURL;
+
+@property (atomic) NSURL *url;
+@property (atomic) NSString *fileSystemName;
+@property (atomic) NSString *fullPath;
+
+@end
+
+@interface CDVFilesystemURLProtocol : NSURLProtocol
+@end
+
+@protocol CDVFileSystem
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
+- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
+
+@property (nonatomic,strong) NSString *name;
+@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*);
+
+@optional
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
+
+@end
+
+@interface CDVFile : CDVPlugin {
+ NSString* rootDocsPath;
+ NSString* appDocsPath;
+ NSString* appLibraryPath;
+ NSString* appTempPath;
+
+ NSMutableArray* fileSystems_;
+ BOOL userHasAllowed;
+}
+
+- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
+
+- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL;
+
+/* Native Registration API */
+- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs;
+- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName;
+
+/* Exec API */
+- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
+- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
+- (void)getDirectory:(CDVInvokedUrlCommand*)command;
+- (void)getFile:(CDVInvokedUrlCommand*)command;
+- (void)getParent:(CDVInvokedUrlCommand*)command;
+- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
+- (void)remove:(CDVInvokedUrlCommand*)command;
+- (void)copyTo:(CDVInvokedUrlCommand*)command;
+- (void)moveTo:(CDVInvokedUrlCommand*)command;
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
+- (void)readEntries:(CDVInvokedUrlCommand*)command;
+- (void)readAsText:(CDVInvokedUrlCommand*)command;
+- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
+- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
+- (void)write:(CDVInvokedUrlCommand*)command;
+- (void)testFileExists:(CDVInvokedUrlCommand*)command;
+- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
+- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
+- (void)truncate:(CDVInvokedUrlCommand*)command;
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
+
+/* Compatibilty with older File API */
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
+
+/* Conversion between filesystem paths and URLs */
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
+
+/* Internal methods for testing */
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
+
+/**
+ * local path of the 'documents' file system (~/Documents)
+ */
+@property (nonatomic, strong) NSString* appDocsPath;
+
+/**
+* local path of the 'applicationStorageDirectory' file system (~/Library/Application Support/<bundle-id>)
+*/
+@property (nonatomic, strong) NSString* appSupportPath;
+
+/**
+* local path of the 'persistent' file system (~/Library/Application Support/<bundle-id>/files)
+*/
+@property (nonatomic, strong) NSString* appDataPath;
+
+/**
+* local path of the 'documents' file system (~/Documents)
+*/
+@property (nonatomic, strong) NSString* appTempPath;
+
+/**
+* local path of the 'cache' file system (~/Library/Caches/<bundle-id>)
+*/
+@property (nonatomic, strong) NSString* appCachePath;
+
+/**
+ * registered file systems
+ */
+@property (nonatomic, strong) NSMutableArray* fileSystems;
+
+@property BOOL userHasAllowed;
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.m b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.m
new file mode 100644
index 00000000..75a60019
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVFile.m
@@ -0,0 +1,1056 @@
+/*
+ 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 "CDVLocalFilesystem.h"
+#import <objc/message.h>
+
+static NSString* toBase64(NSData* data) {
+ SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
+ SEL s2 = NSSelectorFromString(@"base64EncodedString");
+ SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
+
+ if ([data respondsToSelector:s1]) {
+ NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s1];
+ return func(data, s1);
+ } else if ([data respondsToSelector:s2]) {
+ NSString* (*func)(id, SEL) = (void *)[data methodForSelector:s2];
+ return func(data, s2);
+ } else if ([data respondsToSelector:s3]) {
+ NSString* (*func)(id, SEL, NSUInteger) = (void *)[data methodForSelector:s3];
+ return func(data, s3, 0);
+ } else {
+ return nil;
+ }
+}
+
+CDVFile *filePlugin = nil;
+
+extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import));
+
+#ifndef __IPHONE_5_1
+ NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey";
+#endif
+
+NSString* const kCDVFilesystemURLPrefix = @"cdvfile";
+
+@implementation CDVFilesystemURL
+@synthesize url=_url;
+@synthesize fileSystemName=_fileSystemName;
+@synthesize fullPath=_fullPath;
+
+- (id) initWithString:(NSString *)strURL
+{
+ if ( self = [super init] ) {
+ NSURL *decodedURL = [NSURL URLWithString:strURL];
+ return [self initWithURL:decodedURL];
+ }
+ return nil;
+}
+
+-(id) initWithURL:(NSURL *)URL
+{
+ if ( self = [super init] ) {
+ _url = URL;
+ _fileSystemName = [self filesystemNameForLocalURI:URL];
+ _fullPath = [self fullPathForLocalURI:URL];
+ }
+ return self;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString FileSystem Name for this URI, or nil if it is not recognized.
+ */
+- (NSString *)filesystemNameForLocalURI:(NSURL *)uri
+{
+ if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
+ NSArray *pathComponents = [uri pathComponents];
+ if (pathComponents != nil && pathComponents.count > 1) {
+ return [pathComponents objectAtIndex:1];
+ }
+ }
+ return nil;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString fullPath component suitable for an Entry object.
+ * The incoming URI should be properly escaped. The returned fullPath is unescaped.
+ */
+- (NSString *)fullPathForLocalURI:(NSURL *)uri
+{
+ if ([[uri scheme] isEqualToString:kCDVFilesystemURLPrefix] && [[uri host] isEqualToString:@"localhost"]) {
+ NSString *path = [uri path];
+ if ([uri query]) {
+ path = [NSString stringWithFormat:@"%@?%@", path, [uri query]];
+ }
+ NSRange slashRange = [path rangeOfString:@"/" options:0 range:NSMakeRange(1, path.length-1)];
+ if (slashRange.location == NSNotFound) {
+ return @"";
+ }
+ return [path substringFromIndex:slashRange.location];
+ }
+ return nil;
+}
+
++ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL
+{
+ return [[CDVFilesystemURL alloc] initWithString:strURL];
+}
+
++ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL
+{
+ return [[CDVFilesystemURL alloc] initWithURL:URL];
+}
+
+- (NSString *)absoluteURL
+{
+ return [NSString stringWithFormat:@"cdvfile://localhost/%@%@", self.fileSystemName, self.fullPath];
+}
+
+@end
+
+@implementation CDVFilesystemURLProtocol
+
++ (BOOL)canInitWithRequest:(NSURLRequest*)request
+{
+ NSURL* url = [request URL];
+ return [[url scheme] isEqualToString:kCDVFilesystemURLPrefix];
+}
+
++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
+{
+ return request;
+}
+
++ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)requestA toRequest:(NSURLRequest*)requestB
+{
+ return [[[requestA URL] resourceSpecifier] isEqualToString:[[requestB URL] resourceSpecifier]];
+}
+
+- (void)startLoading
+{
+ CDVFilesystemURL* url = [CDVFilesystemURL fileSystemURLWithURL:[[self request] URL]];
+ NSObject<CDVFileSystem> *fs = [filePlugin filesystemForURL:url];
+ [fs readFileAtURL:url start:0 end:-1 callback:^void(NSData *data, NSString *mimetype, CDVFileError error) {
+ NSMutableDictionary* responseHeaders = [[NSMutableDictionary alloc] init];
+ responseHeaders[@"Cache-Control"] = @"no-cache";
+
+ if (!error) {
+ responseHeaders[@"Content-Type"] = mimetype;
+ NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:200 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
+ [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [[self client] URLProtocol:self didLoadData:data];
+ [[self client] URLProtocolDidFinishLoading:self];
+ } else {
+ NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url.url statusCode:404 HTTPVersion:@"HTTP/1.1"headerFields:responseHeaders];
+ [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [[self client] URLProtocolDidFinishLoading:self];
+ }
+ }];
+}
+
+- (void)stopLoading
+{}
+
+- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
+ willCacheResponse:(NSCachedURLResponse*)cachedResponse {
+ return nil;
+}
+
+@end
+
+
+@implementation CDVFile
+
+@synthesize appDocsPath, appDataPath, appSupportPath, appTempPath, appCachePath, userHasAllowed, fileSystems=fileSystems_;
+
+- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs {
+ __weak CDVFile* weakSelf = self;
+ SEL sel = NSSelectorFromString(@"urlTransformer");
+ // for backwards compatibility - we check if this property is there
+ // we create a wrapper block because the urlTransformer property
+ // on the commandDelegate might be set dynamically at a future time
+ // (and not dependent on plugin loading order)
+ if ([self.commandDelegate respondsToSelector:sel]) {
+ fs.urlTransformer = ^NSURL*(NSURL* urlToTransform) {
+ // grab the block from the commandDelegate
+ NSURL* (^urlTransformer)(NSURL*) = ((id(*)(id, SEL))objc_msgSend)(weakSelf.commandDelegate, sel);
+ // if block is not null, we call it
+ if (urlTransformer) {
+ return urlTransformer(urlToTransform);
+ } else { // else we return the same url
+ return urlToTransform;
+ }
+ };
+ }
+ [fileSystems_ addObject:fs];
+}
+
+- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName
+{
+ if (self.fileSystems != nil) {
+ for (NSObject<CDVFileSystem> *fs in self.fileSystems) {
+ if ([fs.name isEqualToString:fsName]) {
+ return fs;
+ }
+ }
+ }
+ return nil;
+
+}
+
+- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL {
+ if (localURL.fileSystemName == nil) return nil;
+ @try {
+ return [self fileSystemByName:localURL.fileSystemName];
+ }
+ @catch (NSException *e) {
+ return nil;
+ }
+}
+
+- (NSArray *)getExtraFileSystemsPreference:(NSWindowController *)vc
+{
+ NSString *filesystemsStr = nil;
+ if([self.viewController isKindOfClass:[CDVViewController class]]) {
+ CDVViewController *vc = self.viewController;
+ NSDictionary *settings = [vc settings];
+ filesystemsStr = [settings[CDV_PREF_EXTRA_FILESYSTEM] lowercaseString];
+ }
+ if (!filesystemsStr) {
+ filesystemsStr = CDV_FILESYSTEMS_DEFAULT;
+ }
+ return [filesystemsStr componentsSeparatedByString:@","];
+}
+
+- (void)makeNonSyncable:(NSString*)path {
+ [[NSFileManager defaultManager] createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil];
+ NSURL* url = [NSURL fileURLWithPath:path];
+ [url setResourceValue: [NSNumber numberWithBool: YES]
+ forKey: NSURLIsExcludedFromBackupKey error:nil];
+
+}
+
+- (void)registerExtraFileSystems:(NSArray *)filesystems fromAvailableSet:(NSDictionary *)availableFileSystems
+{
+ NSMutableSet *installedFilesystems = [[NSMutableSet alloc] initWithCapacity:7];
+
+ /* Register filesystems in order */
+ for (NSString *fsName in filesystems) {
+ if (![installedFilesystems containsObject:fsName]) {
+ NSString *fsRoot = availableFileSystems[fsName];
+ if (fsRoot) {
+ [filePlugin registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:fsName root:fsRoot]];
+ [installedFilesystems addObject:fsName];
+ } else {
+ NSLog(@"Unrecognized extra filesystem identifier: %@", fsName);
+ }
+ }
+ }
+}
+
+- (NSDictionary *)getAvailableFileSystems
+{
+ return @{
+ @"documents": self.appDocsPath,
+ @"cache": self.appCachePath,
+ @"bundle": [[NSBundle mainBundle] bundlePath],
+ @"root": @"/"
+ };
+}
+
+- (void)pluginInitialize
+{ filePlugin = self;
+ [NSURLProtocol registerClass:[CDVFilesystemURLProtocol class]];
+
+ fileSystems_ = [[NSMutableArray alloc] initWithCapacity:3];
+
+ // Get the Temporary directory path
+ self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory()
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"temporary" root:self.appTempPath]];
+
+ // ~/Library/Application Support/<bundle-id>
+ self.appSupportPath = [self getSupportDirectoryFor:NSApplicationSupportDirectory pathComponents:nil];
+
+ // ~/Library/Application Support/<bundle-id>/files
+ self.appDataPath = [self getSupportDirectoryFor:NSApplicationSupportDirectory pathComponents:@[@"files"]];
+ [self registerFilesystem:[[CDVLocalFilesystem alloc] initWithName:@"persistent" root:self.appDataPath]];
+
+ // ~/Documents/
+ self.appDocsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+
+ // ~/Library/Caches/<bundle-id>/files
+ self.appCachePath = [self getSupportDirectoryFor:NSCachesDirectory pathComponents:nil];
+
+ [self registerExtraFileSystems:[self getExtraFileSystemsPreference:self.viewController]
+ fromAvailableSet:[self getAvailableFileSystems]];
+}
+
+- (NSString*) getSupportDirectoryFor: (NSSearchPathDirectory) directory pathComponents: (NSArray*) components {
+ NSError* error;
+ NSFileManager* fm = [NSFileManager defaultManager];
+ NSURL* supportDir = [fm URLForDirectory:directory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
+ if (supportDir == nil) {
+ NSLog(@"unable to get support directory: %@", error);
+ return nil;
+ }
+
+ NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier];
+ NSURL *dirPath = [supportDir URLByAppendingPathComponent:bundleID];
+ for (NSString* pathComponent in components) {
+ dirPath = [dirPath URLByAppendingPathComponent:pathComponent];
+ }
+
+ if (![fm fileExistsAtPath:dirPath.path]) {
+ if (![fm createDirectoryAtURL:dirPath withIntermediateDirectories:YES attributes:nil error:&error]) {
+ NSLog(@"unable to create support directory: %@", error);
+ return nil;
+ }
+ }
+ return dirPath.path;
+}
+
+
+- (CDVFilesystemURL *)fileSystemURLforArg:(NSString *)urlArg
+{
+ CDVFilesystemURL* ret = nil;
+ if ([urlArg hasPrefix:@"file://"]) {
+ /* This looks like a file url. Get the path, and see if any handlers recognize it. */
+ NSURL *fileURL = [NSURL URLWithString:urlArg];
+ NSURL *resolvedFileURL = [fileURL URLByResolvingSymlinksInPath];
+ NSString *path = [resolvedFileURL path];
+ ret = [self fileSystemURLforLocalPath:path];
+ } else {
+ ret = [CDVFilesystemURL fileSystemURLWithString:urlArg];
+ }
+ return ret;
+}
+
+- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath
+{
+ CDVFilesystemURL *localURL = nil;
+ NSUInteger shortestFullPath = 0;
+
+ // Try all installed filesystems, in order. Return the most match url.
+ for (id object in self.fileSystems) {
+ if ([object respondsToSelector:@selector(URLforFilesystemPath:)]) {
+ CDVFilesystemURL *url = [object URLforFilesystemPath:localPath];
+ if (url){
+ // A shorter fullPath would imply that the filesystem is a better match for the local path
+ if (!localURL || ([[url fullPath] length] < shortestFullPath)) {
+ localURL = url;
+ shortestFullPath = [[url fullPath] length];
+ }
+ }
+ }
+ }
+ return localURL;
+}
+
+- (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;
+}
+
+/* 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
+{
+ // arguments
+ NSString* strType = [command argumentAtIndex:0];
+ unsigned long long size = [[command argumentAtIndex:1] longLongValue];
+
+ int type = [strType intValue];
+ CDVPluginResult* result = nil;
+
+ if (type > self.fileSystems.count) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"No filesystem of type requested");
+ } else {
+ NSString* fullPath = @"/";
+ // check for avail space for size request
+ NSNumber* pNumAvail = [self checkFreeDiskSpace:self.appSupportPath];
+ // NSLog(@"Free space: %@", [NSString stringWithFormat:@"%qu", [ pNumAvail unsignedLongLongValue ]]);
+ if (pNumAvail && ([pNumAvail unsignedLongLongValue] < size)) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR];
+ } else {
+ NSObject<CDVFileSystem> *rootFs = [self.fileSystems objectAtIndex:type];
+ if (rootFs == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:NOT_FOUND_ERR];
+ NSLog(@"No filesystem of type requested");
+ } else {
+ NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2];
+ [fileSystem setObject:rootFs.name forKey:@"name"];
+ NSDictionary* dirEntry = [self makeEntryForPath:fullPath fileSystemName:rootFs.name isDirectory:YES];
+ [fileSystem setObject:dirEntry forKey:@"root"];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem];
+ }
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+
+- (void)requestAllFileSystems:(CDVInvokedUrlCommand*)command
+{
+ NSMutableArray* ret = [[NSMutableArray alloc] init];
+ for (NSObject<CDVFileSystem>* root in fileSystems_) {
+ [ret addObject:[self makeEntryForPath:@"/" fileSystemName:root.name isDirectory:YES]];
+ }
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:ret];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)requestAllPaths:(CDVInvokedUrlCommand*)command
+{
+ NSDictionary* ret = @{
+ @"applicationDirectory": [[NSURL fileURLWithPath:[[NSBundle mainBundle] resourcePath]] absoluteString],
+ @"applicationStorageDirectory": [[NSURL fileURLWithPath:self.appSupportPath] absoluteString],
+ @"dataDirectory": [[NSURL fileURLWithPath:self.appDataPath] absoluteString],
+ @"documentsDirectory": [[NSURL fileURLWithPath:self.appDocsPath] absoluteString],
+ @"cacheDirectory": [[NSURL fileURLWithPath:self.appCachePath] absoluteString],
+ @"tempDirectory":[[NSURL fileURLWithPath:self.appTempPath] absoluteString],
+ @"rootDirectory": @"file:///",
+ };
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:ret];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* Creates and returns a dictionary representing an Entry Object
+ *
+ * IN:
+ * NSString* fullPath of the entry
+ * int fsType - 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
+ * NSString* filesystemName - FileSystem name -- actual filesystem will be created on the JS side if necessary, to avoid
+ * creating circular reference (FileSystem contains DirectoryEntry which contains FileSystem.....!!)
+ */
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir
+{
+ NSObject<CDVFileSystem> *fs = [self fileSystemByName:fsName];
+ return [fs makeEntryForPath:fullPath isDirectory:isDir];
+}
+
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)localURL
+{
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURL];
+ return [fs makeEntryForLocalURL:localURL];
+}
+
+- (NSDictionary *)makeEntryForURL:(NSURL *)URL
+{
+ CDVFilesystemURL* fsURL = [self fileSystemURLforArg:[URL absoluteString]];
+ return [self makeEntryForLocalURL:fsURL];
+}
+
+/*
+ * Given a URI determine the File System information associated with it and return an appropriate W3C entry object
+ * IN
+ * NSString* localURI: Should be an escaped local filesystem 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* localURIstr = [command argumentAtIndex:0];
+ CDVPluginResult* result;
+
+ localURIstr = [self encodePath:localURIstr]; //encode path before resolving
+ CDVFilesystemURL* inputURI = [self fileSystemURLforArg:localURIstr];
+
+ if (inputURI == nil || inputURI.fileSystemName == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else {
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:inputURI];
+ if (fs == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR];
+ } else {
+ result = [fs entryForLocalURI:inputURI];
+ }
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+//encode path with percent escapes
+-(NSString *)encodePath:(NSString *)path
+{
+ NSString *decodedPath = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //decode incase it's already encoded to avoid encoding twice
+ return [decodedPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+}
+
+
+/* Part of DirectoryEntry interface, creates or returns the specified directory
+ * IN:
+ * NSString* localURI - local filesystem URI 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 = [command argumentAtIndex: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* baseURI - local filesytem URI for the base directory to search
+ * NSString* requestedPath - file to be created/returned; may be absolute path or relative path
+ * NSDictionary* options - 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
+{
+ NSString* baseURIstr = [command argumentAtIndex:0];
+ CDVFilesystemURL* baseURI = [self fileSystemURLforArg:baseURIstr];
+ NSString* requestedPath = [command argumentAtIndex:1];
+ NSDictionary* options = [command argumentAtIndex:2 withDefault:nil];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:baseURI];
+ CDVPluginResult* result = [fs getFileForURL:baseURI requestedPath:requestedPath options:options];
+
+
+ [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* localURI
+ * NSMutableDictionary* options
+ * empty
+ */
+- (void)getParent:(CDVInvokedUrlCommand*)command
+{
+ // arguments are URL encoded
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult* result = [fs getParentForURL:localURI];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/*
+ * set MetaData of entry
+ * Currently we only support "com.apple.MobileBackup" (boolean)
+ */
+- (void)setMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSDictionary* options = [command argumentAtIndex:1 withDefault:nil];
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult* result = [fs setMetadataForURL:localURI withObject:options];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* removes the directory or file entry
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ *
+ * 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
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ CDVPluginResult* result = nil;
+
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // error if try to remove top level (documents or tmp) dir
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ result = [fs removeFileAtURL:localURI];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* recursively removes the directory
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI
+ *
+ * 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
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ CDVPluginResult* result = nil;
+
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // error if try to remove top level (documents or tmp) dir
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
+ } else {
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ result = [fs recursiveRemoveFileAtURL:localURI];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+- (void)copyTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:YES];
+}
+
+- (void)moveTo:(CDVInvokedUrlCommand*)command
+{
+ [self doCopyMove:command isCopy:NO];
+}
+
+/* Copy/move a file or directory to a new location
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* URL of entry to copy
+ * 1 - NSString* URL of the directory into which to copy/move the entry
+ * 2 - Optionally, the new name of the entry, defaults to the current name
+ * BOOL - bCopy YES if copy, NO if move
+ */
+- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy
+{
+ NSArray* arguments = command.arguments;
+
+ // arguments
+ NSString* srcURLstr = [command argumentAtIndex:0];
+ NSString* destURLstr = [command argumentAtIndex:1];
+
+ CDVPluginResult *result;
+
+ if (!srcURLstr || !destURLstr) {
+ // either no source or no destination provided
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ CDVFilesystemURL* srcURL = [self fileSystemURLforArg:srcURLstr];
+ CDVFilesystemURL* destURL = [self fileSystemURLforArg:destURLstr];
+
+ NSObject<CDVFileSystem> *srcFs = [self filesystemForURL:srcURL];
+ NSObject<CDVFileSystem> *destFs = [self filesystemForURL:destURL];
+
+ // optional argument; use last component from srcFullPath if new name not provided
+ NSString* newName = ([arguments count] > 2) ? [command argumentAtIndex:2] : [srcURL.url lastPathComponent];
+ if ([newName rangeOfString:@":"].location != NSNotFound) {
+ // invalid chars in new name
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ [destFs copyFileToURL:destURL withName:newName fromFileSystem:srcFs atURL:srcURL copy:bCopy callback:^(CDVPluginResult* result) {
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+
+}
+
+- (void)getFileMetadata:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ [fs getFileMetadataForURL:localURI callback:^(CDVPluginResult* result) {
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ }];
+}
+
+- (void)readEntries:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult *result = [fs readEntriesAtURL:localURI];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* 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
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSString* encoding = [command argumentAtIndex:1];
+ NSInteger start = [[command argumentAtIndex:2] integerValue];
+ NSInteger end = [[command argumentAtIndex:3] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ if (fs == nil) {
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+ return;
+ }
+
+ // 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.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI 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
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ NSString* b64Str = toBase64(data);
+ NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, b64Str];
+ 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
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI 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
+{
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ NSInteger start = [[command argumentAtIndex:1] integerValue];
+ NSInteger end = [[command argumentAtIndex:2] integerValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+ [self.commandDelegate runInBackground:^ {
+ [fs readFileAtURL:localURI 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];
+ }];
+ }];
+}
+
+
+- (void)truncate:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ unsigned long long pos = (unsigned long long)[[command argumentAtIndex:1] longLongValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+ CDVPluginResult *result = [fs truncateFileAtURL:localURI atPosition:pos];
+
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+/* write
+ * IN:
+ * NSArray* arguments
+ * 0 - NSString* localURI of file to write to
+ * 1 - NSString* or NSData* data to write
+ * 2 - NSNumber* position to begin writing
+ */
+- (void)write:(CDVInvokedUrlCommand*)command
+{
+ [self.commandDelegate runInBackground:^ {
+ NSString* callbackId = command.callbackId;
+
+ // arguments
+ CDVFilesystemURL* localURI = [self fileSystemURLforArg:command.arguments[0]];
+ id argData = [command argumentAtIndex:1];
+ unsigned long long pos = (unsigned long long)[[command argumentAtIndex:2] longLongValue];
+
+ NSObject<CDVFileSystem> *fs = [self filesystemForURL:localURI];
+
+
+ [fs truncateFileAtURL:localURI atPosition:pos];
+ CDVPluginResult *result;
+ if ([argData isKindOfClass:[NSString class]]) {
+ NSData *encData = [argData dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+ result = [fs writeToFileAtURL:localURI withData:encData append:YES];
+ } else if ([argData isKindOfClass:[NSData class]]) {
+ result = [fs writeToFileAtURL:localURI withData:argData append:YES];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+ }];
+}
+
+#pragma mark Methods for converting between URLs and paths
+
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURL
+{
+ for (NSObject<CDVFileSystem> *fs in self.fileSystems) {
+ if ([fs.name isEqualToString:localURL.fileSystemName]) {
+ if ([fs respondsToSelector:@selector(filesystemPathForURL:)]) {
+ return [fs filesystemPathForURL:localURL];
+ }
+ }
+ }
+ return nil;
+}
+
+#pragma mark Undocumented Filesystem API
+
+- (void)testFileExists:(CDVInvokedUrlCommand*)command
+{
+ // arguments
+ NSString* argPath = [command argumentAtIndex: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 argumentAtIndex: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];
+}
+
+#pragma mark Compatibility with older File API
+
+- (NSString*)getMimeTypeFromPath:(NSString*)fullPath
+{
+ return [CDVLocalFilesystem getMimeTypeFromPath:fullPath];
+}
+
+- (NSDictionary *)getDirectoryEntry:(NSString *)localPath isDirectory:(BOOL)bDirRequest
+{
+ CDVFilesystemURL *localURL = [self fileSystemURLforLocalPath:localPath];
+ return [self makeEntryForPath:localURL.fullPath fileSystemName:localURL.fileSystemName isDirectory:bDirRequest];
+}
+
+#pragma mark Internal methods for testing
+// Internal methods for testing: Get the on-disk location of a local filesystem url.
+// [Currently used for testing file-transfer]
+
+- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command
+{
+ CDVFilesystemURL* localURL = [self fileSystemURLforArg:command.arguments[0]];
+
+ NSString* fsPath = [self filesystemPathForURL:localURL];
+ CDVPluginResult* result;
+ if (fsPath) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:fsPath];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Cannot resolve URL to a file"];
+ }
+ [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+}
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.h b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.h
new file mode 100644
index 00000000..a0186c85
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.h
@@ -0,0 +1,32 @@
+/*
+ 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"
+
+@interface CDVLocalFilesystem : NSObject<CDVFileSystem> {
+ NSString *_name;
+ NSString *_fsRoot;
+}
+
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
++ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
+
+@property (nonatomic,strong) NSString *fsRoot;
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m
new file mode 100644
index 00000000..016e3999
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/osx/CDVLocalFilesystem.m
@@ -0,0 +1,733 @@
+/*
+ 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 "CDVLocalFilesystem.h"
+#import <sys/xattr.h>
+
+@implementation CDVLocalFilesystem
+@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
+
+- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
+{
+ if (self) {
+ _name = name;
+ _fsRoot = fsRoot;
+ }
+ return self;
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
+ * URI represents a non-existent path, or is unrecognized or otherwise malformed.
+ */
+- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
+{
+ CDVPluginResult* result = nil;
+ NSDictionary* entry = [self makeEntryForLocalURL:url];
+ if (entry) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
+ } else {
+ // return NOT_FOUND_ERR
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ return result;
+}
+- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
+ NSString *path = [self filesystemPathForURL:url];
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL isDir = NO;
+ // see if exists and is file or dir
+ BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
+ if (bExists) {
+ return [self makeEntryForPath:url.fullPath isDirectory:isDir];
+ } else {
+ return nil;
+ }
+}
+- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
+{
+ NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
+ NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
+ if (isDir && ![fullPath hasSuffix:@"/"]) {
+ fullPath = [fullPath stringByAppendingString:@"/"];
+ }
+ [dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
+ [dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
+ [dirEntry setObject:fullPath forKey:@"fullPath"];
+ [dirEntry setObject:lastPart forKey:@"name"];
+ [dirEntry setObject:self.name forKey: @"filesystemName"];
+
+ NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
+ if (self.urlTransformer) {
+ nativeURL = self.urlTransformer(nativeURL);
+ }
+
+ dirEntry[@"nativeURL"] = [nativeURL absoluteString];
+
+ return dirEntry;
+}
+
+- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
+{
+ NSRange questionMark = [fullPath rangeOfString:@"?"];
+ if (questionMark.location != NSNotFound) {
+ return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
+ }
+ return fullPath;
+}
+
+- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
+{
+ NSString *path = nil;
+ NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
+ path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
+ if ([path length] > 1 && [path hasSuffix:@"/"]) {
+ path = [path substringToIndex:([path length]-1)];
+ }
+ return path;
+}
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
+ * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
+ * or if the URL is malformed.
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
+ */
+- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
+{
+ return [self filesystemPathForFullPath:url.fullPath];
+}
+
+- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
+{
+ if (fullPath) {
+ NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ if ([fullPath hasPrefix:@"/"]) {
+ return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
+ }
+ return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
+ }
+ return nil;
+}
+
+- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
+{
+ return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
+
+}
+
+- (NSString *)normalizePath:(NSString *)rawPath
+{
+ // If this is an absolute path, the first path component will be '/'. Skip it if that's the case
+ BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
+ if (isAbsolutePath) {
+ rawPath = [rawPath substringFromIndex:1];
+ }
+ NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
+ for (int index = 0; index < [components count]; ++index) {
+ if ([[components objectAtIndex:index] isEqualToString:@".."]) {
+ [components removeObjectAtIndex:index];
+ if (index > 0) {
+ [components removeObjectAtIndex:index-1];
+ --index;
+ }
+ }
+ }
+
+ if (isAbsolutePath) {
+ return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
+ } else {
+ return [components componentsJoinedByString:@"/"];
+ }
+
+
+}
+
+- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
+{
+ BOOL bNumber = NO;
+ NSObject* value = dict[key];
+ if (value) {
+ bNumber = [value isKindOfClass:[NSNumber class]];
+ }
+ return bNumber;
+}
+
+- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
+{
+ CDVPluginResult* result = nil;
+ BOOL bDirRequest = NO;
+ BOOL create = NO;
+ BOOL exclusive = NO;
+ int errorCode = 0; // !!! risky - no error code currently defined for 0
+
+ if ([self valueForKeyIsNumber:options key:@"create"]) {
+ create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
+ }
+ if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
+ exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
+ }
+ if ([self valueForKeyIsNumber:options key:@"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 {
+ // Build new fullPath for the requested resource.
+ // We concatenate the two paths together, and then scan the resulting string to remove
+ // parent ("..") references. Any parent references at the beginning of the string are
+ // silently removed.
+ NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
+ combinedPath = [self normalizePath:combinedPath];
+ CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
+ if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
+ // path exists and is not 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:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
+ } else {
+ // create the empty file
+ bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] 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 makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
+ }
+ } // are all possible conditions met?
+ }
+
+ if (errorCode > 0) {
+ // create error callback
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ return result;
+
+}
+
+- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
+{
+ CDVPluginResult* result = nil;
+ CDVFilesystemURL *newURI = nil;
+ if ([localURI.fullPath isEqualToString:@""]) {
+ // return self
+ newURI = localURI;
+ } else {
+ newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
+ }
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir;
+ BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
+ if (bExists) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
+ } else {
+ // invalid path or file does not exist
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ return result;
+}
+
+- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
+{
+ BOOL ok = NO;
+
+ NSString* filePath = [self filesystemPathForURL:localURI];
+ // 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]]) {
+// todo: fix me
+// 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) {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+ }
+}
+
+/* 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 removing filesystem entry at %@: %@", fullPath, [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;
+}
+
+- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
+{
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ BOOL bIsDir = NO;
+ BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
+ if (!bExists) {
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+ if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
+ // dir is not empty
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
+ }
+ return [self doRemove:fileSystemPath];
+}
+
+- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
+{
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+ return [self doRemove:fileSystemPath];
+}
+
+/*
+ * IN
+ * NSString localURI
+ * OUT
+ * NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
+ * The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
+ * or if the URL is malformed.
+ * The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
+ */
+- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
+{
+ if ([fsPath hasPrefix:self.fsRoot]) {
+ return [fsPath substringFromIndex:[self.fsRoot length]];
+ }
+ return nil;
+}
+
+
+- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
+{
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* __autoreleasing error = nil;
+ NSString *fileSystemPath = [self filesystemPathForURL:localURI];
+
+ NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath 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 = [fileSystemPath stringByAppendingPathComponent:name];
+ BOOL bIsDir = NO;
+ [fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
+ NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
+ [entries addObject:entryDict];
+ }
+ }
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
+ } else {
+ // assume not found but could check error for more specific error conditions
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
+ }
+}
+
+- (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;
+}
+
+- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
+{
+ unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
+ return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
+}
+
+- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
+{
+ NSString *filePath = [self filesystemPathForURL:localURL];
+
+ 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];
+ if (len == 0) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
+ } else {
+ [fileStream open];
+
+ bytesWritten = (int)[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];
+ }
+ return result;
+}
+
+/**
+ * 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;
+}
+
+- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
+{
+ NSFileManager *fileMgr = [[NSFileManager alloc] init];
+ NSString *destRootPath = [self filesystemPathForURL:destURL];
+ BOOL bDestIsDir = NO;
+ BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
+
+ NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
+ NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
+
+ BOOL bNewIsDir = NO;
+ BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
+
+ CDVPluginResult *result = nil;
+ int errCode = 0;
+
+ if (!bDestExists) {
+ // the destination root does not exist
+ errCode = NOT_FOUND_ERR;
+ }
+
+ else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
+ /* Same FS, we can shortcut with NSFileManager operations */
+ NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
+
+ BOOL bSrcIsDir = NO;
+ BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
+
+ if (!bSrcExists) {
+ // the source does not exist
+ errCode = NOT_FOUND_ERR;
+ } else if ([newFileSystemPath isEqualToString:srcFullPath]) {
+ // source and destination can not be the same
+ errCode = INVALID_MODIFICATION_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:newFileSystemPath]) {
+ // 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:newFileSystemPath 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:newFileSystemPath]) {
+ // can't move a dir into itself
+ errCode = INVALID_MODIFICATION_ERR;
+ } else if (bNewExists) {
+ if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
+ // can't move dir to a dir that is not empty
+ errCode = INVALID_MODIFICATION_ERR;
+ newFileSystemPath = nil; // so we won't try to move
+ } else {
+ // remove destination so can perform the moveItemAtPath
+ bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
+ if (!bSuccess) {
+ errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
+ newFileSystemPath = nil;
+ }
+ }
+ } else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
+ // can't move a directory inside itself or to any child at any depth;
+ errCode = INVALID_MODIFICATION_ERR;
+ newFileSystemPath = nil;
+ }
+
+ if (newFileSystemPath != nil) {
+ bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
+ }
+ }
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ 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;
+ }
+ }
+ }
+ }
+ } else {
+ // Need to copy the hard way
+ [srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
+ CDVPluginResult* result = nil;
+ if (data != nil) {
+ BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
+ if (bSuccess) {
+ // should verify it is there and of the correct type???
+ NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
+ }
+ } else {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
+ }
+ callback(result);
+ }];
+ return; // Async IO; return without callback.
+ }
+ if (result == nil) {
+ if (!errCode) {
+ errCode = INVALID_MODIFICATION_ERR; // Catch-all default
+ }
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
+ }
+ callback(result);
+}
+
+/* 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";
+ } else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
+ mimeType = @"text/css";
+ }
+ }
+ CFRelease(typeId);
+ }
+ }
+ return mimeType;
+}
+
+- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
+{
+ NSString *path = [self filesystemPathForURL:localURL];
+
+ NSString* mimeType = [CDVLocalFilesystem 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);
+}
+
+- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
+{
+ NSString *path = [self filesystemPathForURL:localURL];
+ CDVPluginResult *result;
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+
+ NSError* __autoreleasing error = nil;
+ NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
+
+ if (fileAttrs) {
+
+ // create dictionary of file info
+ NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
+
+ [fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
+ [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping
+ [fileInfo setObject:[path lastPathComponent] forKey:@"name"];
+
+ // Ensure that directories (and other non-regular files) report size of 0
+ unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
+ [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
+
+ NSDate* modDate = [fileAttrs fileModificationDate];
+ if (modDate) {
+ [fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
+ }
+
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
+
+ } else {
+ // didn't get fileAttribs
+ CDVFileError errorCode = ABORT_ERR;
+ NSLog(@"error getting metadata: %@", [error localizedDescription]);
+ if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
+ errorCode = NOT_FOUND_ERR;
+ }
+ // log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
+ }
+
+ callback(result);
+}
+
+@end
diff --git a/StoneIsland/plugins/cordova-plugin-file/src/windows/FileProxy.js b/StoneIsland/plugins/cordova-plugin-file/src/windows/FileProxy.js
new file mode 100644
index 00000000..d829d775
--- /dev/null
+++ b/StoneIsland/plugins/cordova-plugin-file/src/windows/FileProxy.js
@@ -0,0 +1,1190 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/* global Windows, WinJS, MSApp */
+
+var File = require('./File');
+var FileError = require('./FileError');
+var Flags = require('./Flags');
+var FileSystem = require('./FileSystem');
+var LocalFileSystem = require('./LocalFileSystem');
+var utils = require('cordova/utils');
+
+function Entry (isFile, isDirectory, name, fullPath, filesystemName, nativeURL) {
+ this.isFile = !!isFile;
+ this.isDirectory = !!isDirectory;
+ this.name = name || '';
+ this.fullPath = fullPath || '';
+ this.filesystemName = filesystemName || null;
+ this.nativeURL = nativeURL || null;
+}
+
+var FileEntry = function (name, fullPath, filesystemName, nativeURL) {
+ FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, filesystemName, nativeURL]);
+};
+
+utils.extend(FileEntry, Entry);
+
+var DirectoryEntry = function (name, fullPath, filesystemName, nativeURL) {
+ DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, filesystemName, nativeURL);
+};
+
+utils.extend(DirectoryEntry, Entry);
+
+var getFolderFromPathAsync = Windows.Storage.StorageFolder.getFolderFromPathAsync;
+var getFileFromPathAsync = Windows.Storage.StorageFile.getFileFromPathAsync;
+
+function writeBytesAsync (storageFile, data, position) {
+ return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
+ .then(function (output) {
+ output.seek(position);
+ var dataWriter = new Windows.Storage.Streams.DataWriter(output);
+ dataWriter.writeBytes(data);
+ return dataWriter.storeAsync().then(function (size) {
+ output.size = position + size;
+ return dataWriter.flushAsync().then(function () {
+ output.close();
+ return size;
+ });
+ });
+ });
+}
+
+function writeTextAsync (storageFile, data, position) {
+ return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
+ .then(function (output) {
+ output.seek(position);
+ var dataWriter = new Windows.Storage.Streams.DataWriter(output);
+ dataWriter.writeString(data);
+ return dataWriter.storeAsync().then(function (size) {
+ output.size = position + size;
+ return dataWriter.flushAsync().then(function () {
+ output.close();
+ return size;
+ });
+ });
+ });
+}
+
+function writeBlobAsync (storageFile, data, position) {
+ return storageFile.openAsync(Windows.Storage.FileAccessMode.readWrite)
+ .then(function (output) {
+ output.seek(position);
+ var dataSize = data.size;
+ var input = (data.detachStream || data.msDetachStream).call(data);
+
+ // Copy the stream from the blob to the File stream
+ return Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output)
+ .then(function () {
+ output.size = position + dataSize;
+ return output.flushAsync().then(function () {
+ input.close();
+ output.close();
+
+ return dataSize;
+ });
+ });
+ });
+}
+
+function writeArrayBufferAsync (storageFile, data, position) {
+ return writeBlobAsync(storageFile, new Blob([data]), position); // eslint-disable-line no-undef
+}
+
+function cordovaPathToNative (path) {
+ // turn / into \\
+ var cleanPath = path.replace(/\//g, '\\');
+ // turn \\ into \
+ cleanPath = cleanPath.replace(/\\+/g, '\\');
+ return cleanPath;
+}
+
+function nativePathToCordova (path) {
+ var cleanPath = path.replace(/\\/g, '/');
+ return cleanPath;
+}
+
+var driveRE = new RegExp('^[/]*([A-Z]:)');
+var invalidNameRE = /[\\?*|"<>:]/;
+function validName (name) {
+ return !invalidNameRE.test(name.replace(driveRE, ''));
+}
+
+function sanitize (path) {
+ var slashesRE = new RegExp('/{2,}', 'g');
+ var components = path.replace(slashesRE, '/').split(/\/+/);
+ // Remove double dots, use old school array iteration instead of RegExp
+ // since it is impossible to debug them
+ for (var index = 0; index < components.length; ++index) {
+ if (components[index] === '..') {
+ components.splice(index, 1);
+ if (index > 0) {
+ // if we're not in the start of array then remove preceeding path component,
+ // In case if relative path points above the root directory, just ignore double dots
+ // See file.spec.111 should not traverse above above the root directory for test case
+ components.splice(index - 1, 1);
+ --index;
+ }
+ }
+ }
+ return components.join('/');
+}
+
+var WinFS = function (name, root) {
+ this.winpath = root.winpath;
+ if (this.winpath && !/\/$/.test(this.winpath)) {
+ this.winpath += '/';
+ }
+ this.makeNativeURL = function (path) {
+ // CB-11848: This RE supposed to match all leading slashes in sanitized path.
+ // Removing leading slash to avoid duplicating because this.root.nativeURL already has trailing slash
+ var regLeadingSlashes = /^\/*/;
+ var sanitizedPath = sanitize(path.replace(':', '%3A')).replace(regLeadingSlashes, '');
+ return FileSystem.encodeURIPath(this.root.nativeURL + sanitizedPath);
+ };
+ root.fullPath = '/';
+ if (!root.nativeURL) { root.nativeURL = 'file://' + sanitize(this.winpath + root.fullPath).replace(':', '%3A'); }
+ WinFS.__super__.constructor.call(this, name, root);
+};
+
+utils.extend(WinFS, FileSystem);
+
+WinFS.prototype.__format__ = function (fullPath) {
+ var path = sanitize('/' + this.name + (fullPath[0] === '/' ? '' : '/') + FileSystem.encodeURIPath(fullPath));
+ return 'cdvfile://localhost' + path;
+};
+
+var windowsPaths = {
+ dataDirectory: 'ms-appdata:///local/',
+ cacheDirectory: 'ms-appdata:///temp/',
+ tempDirectory: 'ms-appdata:///temp/',
+ syncedDataDirectory: 'ms-appdata:///roaming/',
+ applicationDirectory: 'ms-appx:///',
+ applicationStorageDirectory: 'ms-appx:///'
+};
+
+var AllFileSystems;
+
+function getAllFS () {
+ if (!AllFileSystems) {
+ AllFileSystems = {
+ 'persistent':
+ Object.freeze(new WinFS('persistent', {
+ name: 'persistent',
+ nativeURL: 'ms-appdata:///local',
+ winpath: nativePathToCordova(Windows.Storage.ApplicationData.current.localFolder.path)
+ })),
+ 'temporary':
+ Object.freeze(new WinFS('temporary', {
+ name: 'temporary',
+ nativeURL: 'ms-appdata:///temp',
+ winpath: nativePathToCordova(Windows.Storage.ApplicationData.current.temporaryFolder.path)
+ })),
+ 'application':
+ Object.freeze(new WinFS('application', {
+ name: 'application',
+ nativeURL: 'ms-appx:///',
+ winpath: nativePathToCordova(Windows.ApplicationModel.Package.current.installedLocation.path)
+ })),
+ 'root':
+ Object.freeze(new WinFS('root', {
+ name: 'root',
+ // nativeURL: 'file:///'
+ winpath: ''
+ }))
+ };
+ }
+ return AllFileSystems;
+}
+
+function getFS (name) {
+ return getAllFS()[name];
+}
+
+FileSystem.prototype.__format__ = function (fullPath) {
+ return getFS(this.name).__format__(fullPath);
+};
+
+require('./fileSystems').getFs = function (name, callback) {
+ setTimeout(function () { callback(getFS(name)); });
+};
+
+function getFilesystemFromPath (path) {
+ var res;
+ var allfs = getAllFS();
+ Object.keys(allfs).some(function (fsn) {
+ var fs = allfs[fsn];
+ if (path.indexOf(fs.winpath) === 0) { res = fs; }
+ return res;
+ });
+ return res;
+}
+
+var msapplhRE = new RegExp('^ms-appdata://localhost/');
+function pathFromURL (url) {
+ url = url.replace(msapplhRE, 'ms-appdata:///');
+ var path = decodeURIComponent(url);
+ // support for file name with parameters
+ if (/\?/g.test(path)) {
+ path = String(path).split('?')[0];
+ }
+ if (path.indexOf('file:/') === 0) {
+ if (path.indexOf('file://') !== 0) {
+ url = 'file:///' + url.substr(6);
+ }
+ }
+
+ ['file://', 'ms-appdata:///', 'ms-appx://', 'cdvfile://localhost/'].every(function (p) {
+ if (path.indexOf(p) !== 0) { return true; }
+ var thirdSlash = path.indexOf('/', p.length);
+ if (thirdSlash < 0) {
+ path = '';
+ } else {
+ path = sanitize(path.substr(thirdSlash));
+ }
+ });
+
+ return path.replace(driveRE, '$1');
+}
+
+function getFilesystemFromURL (url) {
+ url = url.replace(msapplhRE, 'ms-appdata:///');
+ var res;
+ if (url.indexOf('file:/') === 0) { res = getFilesystemFromPath(pathFromURL(url)); } else {
+ var allfs = getAllFS();
+ Object.keys(allfs).every(function (fsn) {
+ var fs = allfs[fsn];
+ if (url.indexOf(fs.root.nativeURL) === 0 ||
+ url.indexOf('cdvfile://localhost/' + fs.name + '/') === 0) {
+ res = fs;
+ return false;
+ }
+ return true;
+ });
+ }
+ return res;
+}
+
+function getFsPathForWinPath (fs, wpath) {
+ var path = nativePathToCordova(wpath);
+ if (path.indexOf(fs.winpath) !== 0) { return null; }
+ return path.replace(fs.winpath, '/');
+}
+
+var WinError = {
+ invalidArgument: -2147024809,
+ fileNotFound: -2147024894,
+ accessDenied: -2147024891
+};
+
+function openPath (path, ops) {
+ ops = ops || {};
+ return new WinJS.Promise(function (complete, failed) {
+ getFileFromPathAsync(path).done(
+ function (file) {
+ complete({file: file});
+ },
+ function (err) {
+ if (err.number !== WinError.fileNotFound && err.number !== WinError.invalidArgument) { failed(FileError.NOT_READABLE_ERR); }
+ getFolderFromPathAsync(path)
+ .done(
+ function (dir) {
+ if (!ops.getContent) { complete({folder: dir}); } else {
+ WinJS.Promise.join({
+ files: dir.getFilesAsync(),
+ folders: dir.getFoldersAsync()
+ }).done(
+ function (a) {
+ complete({
+ folder: dir,
+ files: a.files,
+ folders: a.folders
+ });
+ },
+ function (err) { // eslint-disable-line handle-callback-err
+ failed(FileError.NOT_READABLE_ERR);
+ }
+ );
+ }
+ },
+ function (err) {
+ if (err.number === WinError.fileNotFound || err.number === WinError.invalidArgument) { complete({}); } else { failed(FileError.NOT_READABLE_ERR); }
+ }
+ );
+ }
+ );
+ });
+}
+
+function copyFolder (src, dst, name) {
+ name = name || src.name;
+ return new WinJS.Promise(function (complete, failed) {
+ WinJS.Promise.join({
+ fld: dst.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists),
+ files: src.getFilesAsync(),
+ folders: src.getFoldersAsync()
+ }).done(
+ function (the) {
+ if (!(the.files.length || the.folders.length)) {
+ complete();
+ return;
+ }
+ var todo = the.files.length;
+ var copyfolders = function () {
+ if (!(todo--)) {
+ complete();
+ return;
+ }
+ copyFolder(the.folders[todo], dst)
+ .done(function () { copyfolders(); }, failed);
+ };
+ var copyfiles = function () {
+ if (!(todo--)) {
+ todo = the.folders.length;
+ copyfolders();
+ return;
+ }
+ the.files[todo].copyAsync(the.fld)
+ .done(function () { copyfiles(); }, failed);
+ };
+ copyfiles();
+ },
+ failed
+ );
+ });
+}
+
+function moveFolder (src, dst, name) {
+ name = name || src.name;
+ return new WinJS.Promise(function (complete, failed) {
+ WinJS.Promise.join({
+ fld: dst.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists),
+ files: src.getFilesAsync(),
+ folders: src.getFoldersAsync()
+ }).done(
+ function (the) {
+ if (!(the.files.length || the.folders.length)) {
+ complete();
+ return;
+ }
+ var todo = the.files.length;
+ var movefolders = function () {
+ if (!(todo--)) {
+ src.deleteAsync().done(complete, failed);
+ return;
+ }
+ moveFolder(the.folders[todo], the.fld)
+ .done(movefolders, failed);
+ };
+ var movefiles = function () {
+ if (!(todo--)) {
+ todo = the.folders.length;
+ movefolders();
+ return;
+ }
+ the.files[todo].moveAsync(the.fld)
+ .done(function () { movefiles(); }, failed);
+ };
+ movefiles();
+ },
+ failed
+ );
+ });
+}
+
+function transport (success, fail, args, ops) { // ["fullPath","parent", "newName"]
+ var src = args[0];
+ var parent = args[1];
+ var name = args[2];
+
+ var srcFS = getFilesystemFromURL(src);
+ var dstFS = getFilesystemFromURL(parent);
+ var srcPath = pathFromURL(src);
+ var dstPath = pathFromURL(parent);
+ if (!(srcFS && dstFS && validName(name))) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+
+ var srcWinPath = cordovaPathToNative(sanitize(srcFS.winpath + srcPath));
+ var dstWinPath = cordovaPathToNative(sanitize(dstFS.winpath + dstPath));
+ var tgtFsPath = sanitize(dstPath + '/' + name);
+ var tgtWinPath = cordovaPathToNative(sanitize(dstFS.winpath + dstPath + '/' + name));
+ if (srcWinPath === dstWinPath || srcWinPath === tgtWinPath) {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+
+ WinJS.Promise.join({
+ src: openPath(srcWinPath),
+ dst: openPath(dstWinPath),
+ tgt: openPath(tgtWinPath, {getContent: true})
+ })
+ .done(
+ function (the) {
+ if ((!the.dst.folder) || !(the.src.folder || the.src.file)) {
+ fail(FileError.NOT_FOUND_ERR);
+ return;
+ }
+ if ((the.src.folder && the.tgt.file) ||
+ (the.src.file && the.tgt.folder) ||
+ (the.tgt.folder && (the.tgt.files.length || the.tgt.folders.length))) {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ return;
+ }
+ if (the.src.file) {
+ ops.fileOp(the.src.file, the.dst.folder, name, Windows.Storage.NameCollisionOption.replaceExisting)
+ .done(
+ function (storageFile) {
+ success(new FileEntry(
+ name,
+ tgtFsPath,
+ dstFS.name,
+ dstFS.makeNativeURL(tgtFsPath)
+ ));
+ },
+ function (err) { // eslint-disable-line handle-callback-err
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ } else {
+ ops.folderOp(the.src.folder, the.dst.folder, name).done(
+ function () {
+ success(new DirectoryEntry(
+ name,
+ tgtFsPath,
+ dstFS.name,
+ dstFS.makeNativeURL(tgtFsPath)
+ ));
+ },
+ function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ }
+ },
+ function (err) { // eslint-disable-line handle-callback-err
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+}
+
+module.exports = {
+ requestAllFileSystems: function () {
+ return getAllFS();
+ },
+ requestAllPaths: function (success) {
+ success(windowsPaths);
+ },
+ getFileMetadata: function (success, fail, args) {
+ module.exports.getMetadata(success, fail, args);
+ },
+
+ getMetadata: function (success, fail, args) {
+ var fs = getFilesystemFromURL(args[0]);
+ var path = pathFromURL(args[0]);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var fullPath = cordovaPathToNative(fs.winpath + path);
+
+ var getMetadataForFile = function (storageFile) {
+ storageFile.getBasicPropertiesAsync().then(
+ function (basicProperties) {
+ success(new File(storageFile.name, storageFile.path, storageFile.fileType, basicProperties.dateModified, basicProperties.size));
+ }, function () {
+ fail(FileError.NOT_READABLE_ERR);
+ }
+ );
+ };
+
+ var getMetadataForFolder = function (storageFolder) {
+ storageFolder.getBasicPropertiesAsync().then(
+ function (basicProperties) {
+ var metadata = {
+ size: basicProperties.size,
+ lastModifiedDate: basicProperties.dateModified
+ };
+ success(metadata);
+ },
+ function () {
+ fail(FileError.NOT_READABLE_ERR);
+ }
+ );
+ };
+
+ getFileFromPathAsync(fullPath).then(getMetadataForFile,
+ function () {
+ getFolderFromPathAsync(fullPath).then(getMetadataForFolder,
+ function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ },
+
+ getParent: function (win, fail, args) { // ["fullPath"]
+ var fs = getFilesystemFromURL(args[0]);
+ var path = pathFromURL(args[0]);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ if (!path || (new RegExp('/[^/]*/?$')).test(path)) {
+ win(new DirectoryEntry(fs.root.name, fs.root.fullPath, fs.name, fs.makeNativeURL(fs.root.fullPath)));
+ return;
+ }
+
+ var parpath = path.replace(new RegExp('/[^/]+/?$', 'g'), '');
+ var parname = path.substr(parpath.length);
+ var fullPath = cordovaPathToNative(fs.winpath + parpath);
+
+ var result = new DirectoryEntry(parname, parpath, fs.name, fs.makeNativeURL(parpath));
+ getFolderFromPathAsync(fullPath).done(
+ function () { win(result); },
+ function () { fail(FileError.INVALID_STATE_ERR); }
+ );
+ },
+
+ readAsText: function (win, fail, args) {
+
+ var url = args[0];
+ var enc = args[1];
+ var startPos = args[2];
+ var endPos = args[3];
+
+ var fs = getFilesystemFromURL(url);
+ var path = pathFromURL(url);
+ if (!fs) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
+
+ var encoding = Windows.Storage.Streams.UnicodeEncoding.utf8;
+ if (enc === 'Utf16LE' || enc === 'utf16LE') {
+ encoding = Windows.Storage.Streams.UnicodeEncoding.utf16LE;
+ } else if (enc === 'Utf16BE' || enc === 'utf16BE') {
+ encoding = Windows.Storage.Streams.UnicodeEncoding.utf16BE;
+ }
+
+ getFileFromPathAsync(wpath).then(function (file) {
+ return file.openReadAsync();
+ }).then(function (stream) {
+ startPos = (startPos < 0) ? Math.max(stream.size + startPos, 0) : Math.min(stream.size, startPos);
+ endPos = (endPos < 0) ? Math.max(endPos + stream.size, 0) : Math.min(stream.size, endPos);
+ stream.seek(startPos);
+
+ var readSize = endPos - startPos;
+ var buffer = new Windows.Storage.Streams.Buffer(readSize);
+
+ return stream.readAsync(buffer, readSize, Windows.Storage.Streams.InputStreamOptions.none);
+ }).done(function (buffer) {
+ try {
+ win(Windows.Security.Cryptography.CryptographicBuffer.convertBinaryToString(encoding, buffer));
+ } catch (e) {
+ fail(FileError.ENCODING_ERR);
+ }
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ });
+ },
+
+ readAsBinaryString: function (win, fail, args) {
+ var url = args[0];
+ var startPos = args[1];
+ var endPos = args[2];
+
+ var fs = getFilesystemFromURL(url);
+ var path = pathFromURL(url);
+ if (!fs) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
+
+ getFileFromPathAsync(wpath).then(
+ function (storageFile) {
+ Windows.Storage.FileIO.readBufferAsync(storageFile).done(
+ function (buffer) {
+ var dataReader = Windows.Storage.Streams.DataReader.fromBuffer(buffer);
+ // var fileContent = dataReader.readString(buffer.length);
+ var byteArray = new Uint8Array(buffer.length);
+ var byteString = '';
+ dataReader.readBytes(byteArray);
+ dataReader.close();
+ for (var i = 0; i < byteArray.length; i++) {
+ var charByte = byteArray[i];
+ // var charRepresentation = charByte <= 127 ? String.fromCharCode(charByte) : charByte.toString(16);
+ var charRepresentation = String.fromCharCode(charByte);
+ byteString += charRepresentation;
+ }
+ win(byteString.slice(startPos, endPos));
+ }
+ );
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ readAsArrayBuffer: function (win, fail, args) {
+ var url = args[0];
+ var fs = getFilesystemFromURL(url);
+ var path = pathFromURL(url);
+ if (!fs) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
+
+ getFileFromPathAsync(wpath).then(
+ function (storageFile) {
+ var blob = MSApp.createFileFromStorageFile(storageFile);
+ var url = URL.createObjectURL(blob, { oneTimeOnly: true }); // eslint-disable-line no-undef
+ var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
+ xhr.open('GET', url, true);
+ xhr.responseType = 'arraybuffer';
+ xhr.onload = function () {
+ var resultArrayBuffer = xhr.response;
+ // get start and end position of bytes in buffer to be returned
+ var startPos = args[1] || 0;
+ var endPos = args[2] || resultArrayBuffer.length;
+ // if any of them is specified, we'll slice output array
+ if (startPos !== 0 || endPos !== resultArrayBuffer.length) {
+ // slice method supported only on Windows 8.1, so we need to check if it's available
+ // see http://msdn.microsoft.com/en-us/library/ie/dn641192(v=vs.94).aspx
+ if (resultArrayBuffer.slice) {
+ resultArrayBuffer = resultArrayBuffer.slice(startPos, endPos);
+ } else {
+ // if slice isn't available, we'll use workaround method
+ var tempArray = new Uint8Array(resultArrayBuffer);
+ var resBuffer = new ArrayBuffer(endPos - startPos);
+ var resArray = new Uint8Array(resBuffer);
+
+ for (var i = 0; i < resArray.length; i++) {
+ resArray[i] = tempArray[i + startPos];
+ }
+ resultArrayBuffer = resBuffer;
+ }
+ }
+ win(resultArrayBuffer);
+ };
+ xhr.send();
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ readAsDataURL: function (win, fail, args) {
+ var url = args[0];
+ var fs = getFilesystemFromURL(url);
+ var path = pathFromURL(url);
+ if (!fs) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var wpath = cordovaPathToNative(sanitize(fs.winpath + path));
+
+ getFileFromPathAsync(wpath).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(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ getDirectory: function (win, fail, args) {
+ var dirurl = args[0];
+ var path = args[1];
+ var options = args[2];
+
+ var fs = getFilesystemFromURL(dirurl);
+ var dirpath = pathFromURL(dirurl);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var fspath = sanitize(dirpath + '/' + path);
+ var completePath = sanitize(fs.winpath + fspath);
+
+ var name = completePath.substring(completePath.lastIndexOf('/') + 1);
+
+ var wpath = cordovaPathToNative(completePath.substring(0, completePath.lastIndexOf('/')));
+
+ var flag = '';
+ if (options) {
+ flag = new Flags(options.create, options.exclusive);
+ } else {
+ flag = new Flags(false, false);
+ }
+
+ getFolderFromPathAsync(wpath).done(
+ function (storageFolder) {
+ if (flag.create === true && flag.exclusive === true) {
+ storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.failIfExists).done(
+ function (storageFolder) {
+ win(new DirectoryEntry(storageFolder.name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }, function (err) { // eslint-disable-line handle-callback-err
+ fail(FileError.PATH_EXISTS_ERR);
+ }
+ );
+ } else if (flag.create === true && flag.exclusive === false) {
+ storageFolder.createFolderAsync(name, Windows.Storage.CreationCollisionOption.openIfExists).done(
+ function (storageFolder) {
+ win(new DirectoryEntry(storageFolder.name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }, function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ } else if (flag.create === false) {
+ storageFolder.getFolderAsync(name).done(
+ function (storageFolder) {
+ win(new DirectoryEntry(storageFolder.name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ },
+ function () {
+ // check if path actually points to a file
+ storageFolder.getFileAsync(name).done(
+ function () {
+ fail(FileError.TYPE_MISMATCH_ERR);
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ }
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ remove: function (win, fail, args) {
+ var fs = getFilesystemFromURL(args[0]);
+ var path = pathFromURL(args[0]);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+
+ // FileSystem root can't be removed!
+ if (!path || path === '/') {
+ fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ var fullPath = cordovaPathToNative(fs.winpath + path);
+
+ getFileFromPathAsync(fullPath).then(
+ function (storageFile) {
+ storageFile.deleteAsync().done(win, function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ });
+ },
+ function () {
+ getFolderFromPathAsync(fullPath).done(
+ function (sFolder) {
+ sFolder.getFilesAsync()
+ // check for files
+ .then(function (fileList) {
+ if (fileList) {
+ if (fileList.length === 0) {
+ return sFolder.getFoldersAsync();
+ } else {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ }
+ })
+ // check for folders
+ .done(function (folderList) {
+ if (folderList) {
+ if (folderList.length === 0) {
+ sFolder.deleteAsync().done(
+ win,
+ function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ } else {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ }
+ });
+ },
+ function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ },
+
+ removeRecursively: function (successCallback, fail, args) {
+
+ var fs = getFilesystemFromURL(args[0]);
+ var path = pathFromURL(args[0]);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+
+ // FileSystem root can't be removed!
+ if (!path || path === '/') {
+ fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ var fullPath = cordovaPathToNative(fs.winpath + path);
+
+ getFolderFromPathAsync(fullPath).done(function (storageFolder) {
+ storageFolder.deleteAsync().done(function (res) {
+ successCallback(res);
+ }, function (err) {
+ fail(err);
+ });
+
+ }, function () {
+ fail(FileError.FILE_NOT_FOUND_ERR);
+ });
+ },
+
+ getFile: function (win, fail, args) {
+
+ var dirurl = args[0];
+ var path = args[1];
+ var options = args[2];
+
+ var fs = getFilesystemFromURL(dirurl);
+ var dirpath = pathFromURL(dirurl);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var fspath = sanitize(dirpath + '/' + path);
+ var completePath = sanitize(fs.winpath + fspath);
+
+ var fileName = completePath.substring(completePath.lastIndexOf('/') + 1);
+
+ var wpath = cordovaPathToNative(completePath.substring(0, completePath.lastIndexOf('/')));
+
+ var flag = '';
+ if (options !== null) {
+ flag = new Flags(options.create, options.exclusive);
+ } else {
+ flag = new Flags(false, false);
+ }
+
+ getFolderFromPathAsync(wpath).done(
+ function (storageFolder) {
+ if (flag.create === true && flag.exclusive === true) {
+ storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.failIfExists).done(
+ function (storageFile) {
+ win(new FileEntry(storageFile.name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }, function () {
+ fail(FileError.PATH_EXISTS_ERR);
+ }
+ );
+ } else if (flag.create === true && flag.exclusive === false) {
+ storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.openIfExists).done(
+ function (storageFile) {
+ win(new FileEntry(storageFile.name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }, function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ } else if (flag.create === false) {
+ storageFolder.getFileAsync(fileName).done(
+ function (storageFile) {
+ win(new FileEntry(storageFile.name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }, function () {
+ // check if path actually points to a folder
+ storageFolder.getFolderAsync(fileName).done(
+ function () {
+ fail(FileError.TYPE_MISMATCH_ERR);
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ });
+ }
+ );
+ }
+ }, function (err) {
+ fail(
+ err.number === WinError.accessDenied ?
+ FileError.SECURITY_ERR :
+ FileError.NOT_FOUND_ERR
+ );
+ }
+ );
+ },
+
+ readEntries: function (win, fail, args) { // ["fullPath"]
+ var fs = getFilesystemFromURL(args[0]);
+ var path = pathFromURL(args[0]);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var fullPath = cordovaPathToNative(fs.winpath + path);
+
+ var result = [];
+
+ getFolderFromPathAsync(fullPath).done(function (storageFolder) {
+ var promiseArr = [];
+ var index = 0;
+ promiseArr[index++] = storageFolder.getFilesAsync().then(function (fileList) {
+ if (fileList !== null) {
+ for (var i = 0; i < fileList.length; i++) {
+ var fspath = getFsPathForWinPath(fs, fileList[i].path);
+ if (!fspath) {
+ fail(FileError.NOT_FOUND_ERR);
+ return;
+ }
+ result.push(new FileEntry(fileList[i].name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }
+ }
+ });
+ promiseArr[index++] = storageFolder.getFoldersAsync().then(function (folderList) {
+ if (folderList !== null) {
+ for (var j = 0; j < folderList.length; j++) {
+ var fspath = getFsPathForWinPath(fs, folderList[j].path);
+ if (!fspath) {
+ fail(FileError.NOT_FOUND_ERR);
+ return;
+ }
+ result.push(new DirectoryEntry(folderList[j].name, fspath, fs.name, fs.makeNativeURL(fspath)));
+ }
+ }
+ });
+ WinJS.Promise.join(promiseArr).then(function () {
+ win(result);
+ });
+
+ }, function () { fail(FileError.NOT_FOUND_ERR); });
+ },
+
+ write: function (win, fail, args) {
+
+ var url = args[0];
+ var data = args[1];
+ var position = args[2];
+ var isBinary = args[3];
+
+ var fs = getFilesystemFromURL(url);
+ var path = pathFromURL(url);
+ if (!fs) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var completePath = sanitize(fs.winpath + path);
+ var fileName = completePath.substring(completePath.lastIndexOf('/') + 1);
+ var dirpath = completePath.substring(0, completePath.lastIndexOf('/'));
+ var wpath = cordovaPathToNative(dirpath);
+
+ function getWriteMethodForData (data, isBinary) {
+
+ if (data instanceof Blob) { // eslint-disable-line no-undef
+ return writeBlobAsync;
+ }
+
+ if (data instanceof ArrayBuffer) {
+ return writeArrayBufferAsync;
+ }
+
+ if (isBinary) {
+ return writeBytesAsync;
+ }
+
+ if (typeof data === 'string') {
+ return writeTextAsync;
+ }
+
+ throw new Error('Unsupported data type for write method');
+ }
+
+ var writePromise = getWriteMethodForData(data, isBinary);
+
+ getFolderFromPathAsync(wpath).done(
+ function (storageFolder) {
+ storageFolder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.openIfExists).done(
+ function (storageFile) {
+ writePromise(storageFile, data, position).done(
+ function (bytesWritten) {
+ var written = bytesWritten || data.length;
+ win(written);
+ },
+ function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+ },
+ function () {
+ fail(FileError.INVALID_MODIFICATION_ERR);
+ }
+ );
+
+ },
+ function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ },
+
+ truncate: function (win, fail, args) { // ["fileName","size"]
+ var url = args[0];
+ var size = args[1];
+
+ var fs = getFilesystemFromURL(url);
+ var path = pathFromURL(url);
+ if (!fs) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ var completePath = sanitize(fs.winpath + path);
+ var wpath = cordovaPathToNative(completePath);
+ var dirwpath = cordovaPathToNative(completePath.substring(0, completePath.lastIndexOf('/')));
+
+ getFileFromPathAsync(wpath).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 name = storageFile.name;
+ storageFile.deleteAsync().then(function () {
+ return getFolderFromPathAsync(dirwpath);
+ }).done(function (storageFolder) {
+ storageFolder.createFileAsync(name).then(function (newStorageFile) {
+ Windows.Storage.FileIO.writeTextAsync(newStorageFile, fileContent).done(function () {
+ win(String(fileContent).length);
+ }, function () {
+ fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ });
+ }, function () {
+ fail(FileError.NO_MODIFICATION_ALLOWED_ERR);
+ });
+ });
+ }, function () { fail(FileError.NOT_FOUND_ERR); });
+ }
+ });
+ }, function () { fail(FileError.NOT_FOUND_ERR); });
+ },
+
+ copyTo: function (success, fail, args) { // ["fullPath","parent", "newName"]
+ transport(success, fail, args,
+ {
+ fileOp: function (file, folder, name, coll) {
+ return file.copyAsync(folder, name, coll);
+ },
+ folderOp: function (src, dst, name) {
+ return copyFolder(src, dst, name);
+ }}
+ );
+ },
+
+ moveTo: function (success, fail, args) {
+ transport(success, fail, args,
+ {
+ fileOp: function (file, folder, name, coll) {
+ return file.moveAsync(folder, name, coll);
+ },
+ folderOp: function (src, dst, name) {
+ return moveFolder(src, dst, name);
+ }}
+ );
+ },
+ tempFileSystem: null,
+
+ persistentFileSystem: null,
+
+ requestFileSystem: function (win, fail, args) {
+
+ var type = args[0];
+ var size = args[1];
+ var MAX_SIZE = 10000000000;
+ if (size > MAX_SIZE) {
+ fail(FileError.QUOTA_EXCEEDED_ERR);
+ return;
+ }
+
+ var fs;
+ switch (type) {
+ case LocalFileSystem.TEMPORARY:
+ fs = getFS('temporary');
+ break;
+ case LocalFileSystem.PERSISTENT:
+ fs = getFS('persistent');
+ break;
+ }
+ if (fs) { win(fs); } else { fail(FileError.NOT_FOUND_ERR); }
+ },
+
+ resolveLocalFileSystemURI: function (success, fail, args) {
+
+ var uri = args[0];
+
+ var path = pathFromURL(uri);
+ var fs = getFilesystemFromURL(uri);
+ if (!fs || !validName(path)) {
+ fail(FileError.ENCODING_ERR);
+ return;
+ }
+ if (path.indexOf(fs.winpath) === 0) { path = path.substr(fs.winpath.length); }
+ var abspath = cordovaPathToNative(fs.winpath + path);
+
+ getFileFromPathAsync(abspath).done(
+ function (storageFile) {
+ success(new FileEntry(storageFile.name, path, fs.name, fs.makeNativeURL(path)));
+ }, function () {
+ getFolderFromPathAsync(abspath).done(
+ function (storageFolder) {
+ success(new DirectoryEntry(storageFolder.name, path, fs.name, fs.makeNativeURL(path)));
+ }, function () {
+ fail(FileError.NOT_FOUND_ERR);
+ }
+ );
+ }
+ );
+ }
+
+};
+
+require('cordova/exec/proxy').add('File', module.exports);