diff --git a/framework/src/com/phonegap/FileTransfer.java b/framework/src/com/phonegap/FileTransfer.java index 6bd6d4ee..d2a056a8 100644 --- a/framework/src/com/phonegap/FileTransfer.java +++ b/framework/src/com/phonegap/FileTransfer.java @@ -405,14 +405,9 @@ public class FileTransfer extends Plugin { Log.d(LOG_TAG, "Saved file: " + filePath); // create FileEntry object - JSONObject entry = new JSONObject(); + FileUtils fileUtil = new FileUtils(); - entry.put("isFile", file.isFile()); - entry.put("isDirectory", file.isDirectory()); - entry.put("name", file.getName()); - entry.put("fullPath", file.getAbsolutePath()); - - return entry; + return fileUtil.getEntry(file); } catch (Exception e) { Log.d(LOG_TAG, e.getMessage(), e); throw new IOException("Error while downloading"); diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index 00aa9ce3..0c64971d 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -50,214 +50,214 @@ import com.phonegap.file.TypeMismatchException; * Only files on the SD card can be accessed. */ public class FileUtils extends Plugin { - private static final String LOG_TAG = "FileUtils"; - private static final String _DATA = "_data"; // The column name where the file path is stored + private static final String LOG_TAG = "FileUtils"; + private static final String _DATA = "_data"; // The column name where the file path is stored - public static int NOT_FOUND_ERR = 1; - public static int SECURITY_ERR = 2; - public static int ABORT_ERR = 3; + 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 NOT_READABLE_ERR = 4; + public static int ENCODING_ERR = 5; + public static int NO_MODIFICATION_ALLOWED_ERR = 6; + public static int INVALID_STATE_ERR = 7; + public static int SYNTAX_ERR = 8; + public static int INVALID_MODIFICATION_ERR = 9; public static int QUOTA_EXCEEDED_ERR = 10; public static int TYPE_MISMATCH_ERR = 11; public static int PATH_EXISTS_ERR = 12; - public static int TEMPORARY = 0; - public static int PERSISTENT = 1; - public static int RESOURCE = 2; - public static int APPLICATION = 3; - - FileReader f_in; - FileWriter f_out; - - /** - * Constructor. - */ - public FileUtils() { - } + public static int TEMPORARY = 0; + public static int PERSISTENT = 1; + public static int RESOURCE = 2; + public static int APPLICATION = 3; - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackId The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - //System.out.println("FileUtils.execute("+action+")"); - - try { - try { - if (action.equals("testSaveLocationExists")) { - boolean b = DirectoryManager.testSaveLocationExists(); - return new PluginResult(status, b); - } - else if (action.equals("getFreeDiskSpace")) { - long l = DirectoryManager.getFreeDiskSpace(); - return new PluginResult(status, l); - } - else if (action.equals("testFileExists")) { - boolean b = DirectoryManager.testFileExists(args.getString(0)); - return new PluginResult(status, b); - } - else if (action.equals("testDirectoryExists")) { - boolean b = DirectoryManager.testFileExists(args.getString(0)); - return new PluginResult(status, b); - } - else if (action.equals("readAsText")) { - String s = this.readAsText(args.getString(0), args.getString(1)); - return new PluginResult(status, s); - } - else if (action.equals("readAsDataURL")) { - String s = this.readAsDataURL(args.getString(0)); - return new PluginResult(status, s); - } - else if (action.equals("write")) { - long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2)); - return new PluginResult(status, fileSize); - } - else if (action.equals("truncate")) { - long fileSize = this.truncateFile(args.getString(0), args.getLong(1)); - return new PluginResult(status, fileSize); - } - else if (action.equals("requestFileSystem")) { - long size = args.optLong(1); - if (size != 0) { - if (size > (DirectoryManager.getFreeDiskSpace()*1024)) { - JSONObject error = new JSONObject().put("code", FileUtils.QUOTA_EXCEEDED_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } - } - JSONObject obj = requestFileSystem(args.getInt(0)); - return new PluginResult(status, obj, "window.localFileSystem._castFS"); - } - else if (action.equals("resolveLocalFileSystemURI")) { - JSONObject obj = resolveLocalFileSystemURI(args.getString(0)); - return new PluginResult(status, obj, "window.localFileSystem._castEntry"); - } - else if (action.equals("getMetadata")) { - JSONObject obj = getMetadata(args.getString(0)); - return new PluginResult(status, obj, "window.localFileSystem._castDate"); - } - else if (action.equals("getFileMetadata")) { - JSONObject obj = getFileMetadata(args.getString(0)); - return new PluginResult(status, obj, "window.localFileSystem._castDate"); - } - else if (action.equals("getParent")) { - JSONObject obj = getParent(args.getString(0)); - return new PluginResult(status, obj, "window.localFileSystem._castEntry"); - } - else if (action.equals("getDirectory")) { - JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), true); - return new PluginResult(status, obj, "window.localFileSystem._castEntry"); - } - else if (action.equals("getFile")) { - JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), false); - return new PluginResult(status, obj, "window.localFileSystem._castEntry"); - } - else if (action.equals("remove")) { - boolean success; - - success = remove(args.getString(0)); - - if (success) { - notifyDelete(args.getString(0)); - return new PluginResult(status); - } else { - JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } - } - else if (action.equals("removeRecursively")) { - boolean success = removeRecursively(args.getString(0)); - if (success) { - return new PluginResult(status); - } else { - JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } - } - else if (action.equals("moveTo")) { - JSONObject entry = transferTo(args.getString(0), args.getJSONObject(1), args.optString(2), true); - return new PluginResult(status, entry, "window.localFileSystem._castEntry"); - } - else if (action.equals("copyTo")) { - JSONObject entry = transferTo(args.getString(0), args.getJSONObject(1), args.optString(2), false); - return new PluginResult(status, entry, "window.localFileSystem._castEntry"); - } - else if (action.equals("readEntries")) { - JSONArray entries = readEntries(args.getString(0)); - return new PluginResult(status, entries, "window.localFileSystem._castEntries"); - } - return new PluginResult(status, result); - } catch (FileNotFoundException e) { - JSONObject error = new JSONObject().put("code", FileUtils.NOT_FOUND_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (FileExistsException e) { - JSONObject error = new JSONObject().put("code", FileUtils.PATH_EXISTS_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (NoModificationAllowedException e) { - JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (JSONException e) { - JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (InvalidModificationException e) { - JSONObject error = new JSONObject().put("code", FileUtils.INVALID_MODIFICATION_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (MalformedURLException e) { - JSONObject error = new JSONObject().put("code", FileUtils.ENCODING_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (IOException e) { - JSONObject error = new JSONObject().put("code", FileUtils.INVALID_MODIFICATION_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (EncodingException e) { - JSONObject error = new JSONObject().put("code", FileUtils.ENCODING_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } catch (TypeMismatchException e) { - JSONObject error = new JSONObject().put("code", FileUtils.TYPE_MISMATCH_ERR); - return new PluginResult(PluginResult.Status.ERROR, error); - } - } catch (JSONException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - } - - /** - * Need to check to see if we need to clean up the content store - * - * @param filePath the path to check - */ - private void notifyDelete(String filePath) { - int result = this.ctx.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - MediaStore.Images.Media.DATA + " = ?", - new String[] {filePath}); - } + FileReader f_in; + FileWriter f_out; /** - * Allows the user to look up the Entry for a file or directory referred to by a local URI. - * - * @param url of the file/directory to look up - * @return a JSONObject representing a Entry from the filesystem - * @throws MalformedURLException if the url is not valid - * @throws FileNotFoundException if the file does not exist - * @throws IOException if the user can't read the file - * @throws JSONException - */ - private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException { + * Constructor. + */ + public FileUtils() { + } + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + //System.out.println("FileUtils.execute("+action+")"); + + try { + try { + if (action.equals("testSaveLocationExists")) { + boolean b = DirectoryManager.testSaveLocationExists(); + return new PluginResult(status, b); + } + else if (action.equals("getFreeDiskSpace")) { + long l = DirectoryManager.getFreeDiskSpace(); + return new PluginResult(status, l); + } + else if (action.equals("testFileExists")) { + boolean b = DirectoryManager.testFileExists(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("testDirectoryExists")) { + boolean b = DirectoryManager.testFileExists(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("readAsText")) { + String s = this.readAsText(args.getString(0), args.getString(1)); + return new PluginResult(status, s); + } + else if (action.equals("readAsDataURL")) { + String s = this.readAsDataURL(args.getString(0)); + return new PluginResult(status, s); + } + else if (action.equals("write")) { + long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2)); + return new PluginResult(status, fileSize); + } + else if (action.equals("truncate")) { + long fileSize = this.truncateFile(args.getString(0), args.getLong(1)); + return new PluginResult(status, fileSize); + } + else if (action.equals("requestFileSystem")) { + long size = args.optLong(1); + if (size != 0) { + if (size > (DirectoryManager.getFreeDiskSpace()*1024)) { + JSONObject error = new JSONObject().put("code", FileUtils.QUOTA_EXCEEDED_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } + } + JSONObject obj = requestFileSystem(args.getInt(0)); + return new PluginResult(status, obj, "window.localFileSystem._castFS"); + } + else if (action.equals("resolveLocalFileSystemURI")) { + JSONObject obj = resolveLocalFileSystemURI(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castEntry"); + } + else if (action.equals("getMetadata")) { + JSONObject obj = getMetadata(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castDate"); + } + else if (action.equals("getFileMetadata")) { + JSONObject obj = getFileMetadata(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castDate"); + } + else if (action.equals("getParent")) { + JSONObject obj = getParent(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castEntry"); + } + else if (action.equals("getDirectory")) { + JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), true); + return new PluginResult(status, obj, "window.localFileSystem._castEntry"); + } + else if (action.equals("getFile")) { + JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), false); + return new PluginResult(status, obj, "window.localFileSystem._castEntry"); + } + else if (action.equals("remove")) { + boolean success; + + success = remove(args.getString(0)); + + if (success) { + notifyDelete(args.getString(0)); + return new PluginResult(status); + } else { + JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } + } + else if (action.equals("removeRecursively")) { + boolean success = removeRecursively(args.getString(0)); + if (success) { + return new PluginResult(status); + } else { + JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } + } + else if (action.equals("moveTo")) { + JSONObject entry = transferTo(args.getString(0), args.getJSONObject(1), args.optString(2), true); + return new PluginResult(status, entry, "window.localFileSystem._castEntry"); + } + else if (action.equals("copyTo")) { + JSONObject entry = transferTo(args.getString(0), args.getJSONObject(1), args.optString(2), false); + return new PluginResult(status, entry, "window.localFileSystem._castEntry"); + } + else if (action.equals("readEntries")) { + JSONArray entries = readEntries(args.getString(0)); + return new PluginResult(status, entries, "window.localFileSystem._castEntries"); + } + return new PluginResult(status, result); + } catch (FileNotFoundException e) { + JSONObject error = new JSONObject().put("code", FileUtils.NOT_FOUND_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (FileExistsException e) { + JSONObject error = new JSONObject().put("code", FileUtils.PATH_EXISTS_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (NoModificationAllowedException e) { + JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (JSONException e) { + JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (InvalidModificationException e) { + JSONObject error = new JSONObject().put("code", FileUtils.INVALID_MODIFICATION_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (MalformedURLException e) { + JSONObject error = new JSONObject().put("code", FileUtils.ENCODING_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (IOException e) { + JSONObject error = new JSONObject().put("code", FileUtils.INVALID_MODIFICATION_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (EncodingException e) { + JSONObject error = new JSONObject().put("code", FileUtils.ENCODING_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } catch (TypeMismatchException e) { + JSONObject error = new JSONObject().put("code", FileUtils.TYPE_MISMATCH_ERR); + return new PluginResult(PluginResult.Status.ERROR, error); + } + } catch (JSONException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Need to check to see if we need to clean up the content store + * + * @param filePath the path to check + */ + private void notifyDelete(String filePath) { + int result = this.ctx.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + MediaStore.Images.Media.DATA + " = ?", + new String[] {filePath}); + } + + /** + * Allows the user to look up the Entry for a file or directory referred to by a local URI. + * + * @param url of the file/directory to look up + * @return a JSONObject representing a Entry from the filesystem + * @throws MalformedURLException if the url is not valid + * @throws FileNotFoundException if the file does not exist + * @throws IOException if the user can't read the file + * @throws JSONException + */ + private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException { String decoded = URLDecoder.decode(url, "UTF-8"); - + File fp = null; - + // Handle the special case where you get an Android content:// uri. if (decoded.startsWith("content:")) { Cursor cursor = this.ctx.managedQuery(Uri.parse(decoded), new String[] { MediaStore.Images.Media.DATA }, null, null, null); @@ -266,15 +266,15 @@ public class FileUtils extends Plugin { cursor.moveToFirst(); fp = new File(cursor.getString(column_index)); } else { - // Test to see if this is a valid URL first - @SuppressWarnings("unused") - URL testUrl = new URL(decoded); - - if (decoded.startsWith("file://")) { - fp = new File(decoded.substring(7, decoded.length())); - } else { - fp = new File(decoded); - } + // Test to see if this is a valid URL first + @SuppressWarnings("unused") + URL testUrl = new URL(decoded); + + if (decoded.startsWith("file://")) { + fp = new File(decoded.substring(7, decoded.length())); + } else { + fp = new File(decoded); + } } if (!fp.exists()) { @@ -283,614 +283,614 @@ public class FileUtils extends Plugin { if (!fp.canRead()) { throw new IOException(); } - return getEntry(fp); - } + return getEntry(fp); + } - /** - * Read the list of files from this directory. - * - * @param fileName the directory to read from - * @return a JSONArray containing JSONObjects that represent Entry objects. - * @throws FileNotFoundException if the directory is not found. - * @throws JSONException - */ - private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException { - File fp = new File(fileName); + /** + * Read the list of files from this directory. + * + * @param fileName the directory to read from + * @return a JSONArray containing JSONObjects that represent Entry objects. + * @throws FileNotFoundException if the directory is not found. + * @throws JSONException + */ + private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException { + File fp = new File(fileName); - if (!fp.exists()) { - // The directory we are listing doesn't exist so we should fail. - throw new FileNotFoundException(); - } + if (!fp.exists()) { + // The directory we are listing doesn't exist so we should fail. + throw new FileNotFoundException(); + } - JSONArray entries = new JSONArray(); - - if (fp.isDirectory()) { - File[] files = fp.listFiles(); - for (int i=0; i 0) { - throw new InvalidModificationException("directory is not empty"); - } - } - - // Try to rename the directory - if (!srcDir.renameTo(destinationDir)) { - // Trying to rename the directory failed. Possibly because we moved across file system on the device. - // Now we have to do things the hard way - // 1) Copy all the old files - // 2) delete the src directory - } - - return getEntry(destinationDir); - } + // Try to rename the file + if (!srcFile.renameTo(destFile)) { + // Trying to rename the file failed. Possibly because we moved across file system on the device. + // Now we have to do things the hard way + // 1) Copy all the old file + // 2) delete the src file + } - /** - * Deletes a directory and all of its contents, if any. In the event of an error - * [e.g. trying to delete a directory that contains a file that cannot be removed], - * some of the contents of the directory may be deleted. - * It is an error to attempt to delete the root directory of a filesystem. - * - * @param filePath the directory to be removed - * @return a boolean representing success of failure - * @throws FileExistsException - */ - private boolean removeRecursively(String filePath) throws FileExistsException { - File fp = new File(filePath); + return getEntry(destFile); + } - // You can't delete the root directory. - if (atRootDirectory(filePath)) { - return false; - } + /** + * Move a directory + * + * @param srcDir directory to be copied + * @param destinationDir destination to be copied to + * @return a DirectoryEntry object + * @throws JSONException + * @throws IOException + * @throws NoModificationAllowedException + * @throws InvalidModificationException + */ + private JSONObject moveDirectory(File srcDir, File destinationDir) throws JSONException, FileExistsException, NoModificationAllowedException, InvalidModificationException { + // Renaming a file to an existing directory should fail + if (destinationDir.exists() && destinationDir.isFile()) { + throw new InvalidModificationException("Can't rename a file to a directory"); + } - return removeDirRecursively(fp); - } - - /** - * Loops through a directory deleting all the files. - * - * @param directory to be removed - * @return a boolean representing success of failure - * @throws FileExistsException - */ - private boolean removeDirRecursively(File directory) throws FileExistsException { - if (directory.isDirectory()) { - for (File file : directory.listFiles()) { - removeDirRecursively(file); - } - } + // Check to make sure we are not copying the directory into itself + if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { + throw new InvalidModificationException("Can't move itself into itself"); + } - if (!directory.delete()) { - throw new FileExistsException("could not delete: " + directory.getName()); - } else { - return true; - } - } + // If the destination directory already exists and is empty then delete it. This is according to spec. + if (destinationDir.exists()) { + if (destinationDir.list().length > 0) { + throw new InvalidModificationException("directory is not empty"); + } + } - /** - * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty. - * It is an error to attempt to delete the root directory of a filesystem. - * - * @param filePath file or directory to be removed - * @return a boolean representing success of failure - * @throws NoModificationAllowedException - * @throws InvalidModificationException - */ - private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException { - File fp = new File(filePath); - - // You can't delete the root directory. - if (atRootDirectory(filePath)) { - throw new NoModificationAllowedException("You can't delete the root directory"); - } - - // You can't delete a directory that is not empty - if (fp.isDirectory() && fp.list().length > 0) { - throw new InvalidModificationException("You can't delete a directory that is not empty."); - } - - return fp.delete(); - } + // Try to rename the directory + if (!srcDir.renameTo(destinationDir)) { + // Trying to rename the directory failed. Possibly because we moved across file system on the device. + // Now we have to do things the hard way + // 1) Copy all the old files + // 2) delete the src directory + } - /** - * Creates or looks up a file. - * - * @param dirPath base directory - * @param fileName file/directory to lookup or create - * @param options specify whether to create or not - * @param directory if true look up directory, if false look up file - * @return a Entry object - * @throws FileExistsException - * @throws IOException - * @throws TypeMismatchException - * @throws EncodingException - * @throws JSONException - */ - private JSONObject getFile(String dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { - boolean create = false; - boolean exclusive = false; - if (options != null) { - create = options.optBoolean("create"); - if (create) { - exclusive = options.optBoolean("exclusive"); - } - } - - // Check for a ":" character in the file to line up with BB and iOS - if (fileName.contains(":")) { - throw new EncodingException("This file has a : in it's name"); - } - - File fp = createFileObject(dirPath, fileName); - - if (create) { - if (exclusive && fp.exists()) { - throw new FileExistsException("create/exclusive fails"); - } - if (directory) { - fp.mkdir(); - } else { - fp.createNewFile(); - } - if (!fp.exists()) { - throw new FileExistsException("create fails"); - } - } - else { - if (!fp.exists()) { - throw new FileNotFoundException("path does not exist"); - } - if (directory) { - if (fp.isFile()) { - throw new TypeMismatchException("path doesn't exist or is file"); - } - } else { - if (fp.isDirectory()) { - throw new TypeMismatchException("path doesn't exist or is directory"); - } - } - } + return getEntry(destinationDir); + } - // Return the directory - return getEntry(fp); - } + /** + * Deletes a directory and all of its contents, if any. In the event of an error + * [e.g. trying to delete a directory that contains a file that cannot be removed], + * some of the contents of the directory may be deleted. + * It is an error to attempt to delete the root directory of a filesystem. + * + * @param filePath the directory to be removed + * @return a boolean representing success of failure + * @throws FileExistsException + */ + private boolean removeRecursively(String filePath) throws FileExistsException { + File fp = new File(filePath); - /** - * If the path starts with a '/' just return that file object. If not construct the file - * object from the path passed in and the file name. - * - * @param dirPath root directory - * @param fileName new file name - * @return - */ - private File createFileObject(String dirPath, String fileName) { - File fp = null; - if (fileName.startsWith("/")) { - fp = new File(fileName); - } else { - fp = new File(dirPath + File.separator + fileName); - } - return fp; - } + // You can't delete the root directory. + if (atRootDirectory(filePath)) { + return false; + } - /** - * Look up the parent DirectoryEntry containing this Entry. - * If this Entry is the root of its filesystem, its parent is itself. - * - * @param filePath - * @return - * @throws JSONException - */ - private JSONObject getParent(String filePath) throws JSONException { - if (atRootDirectory(filePath)) { - return getEntry(filePath); - } - return getEntry(new File(filePath).getParent()); - } + return removeDirRecursively(fp); + } - /** - * Checks to see if we are at the root directory. Useful since we are - * not allow to delete this directory. - * - * @param filePath to directory - * @return true if we are at the root, false otherwise. - */ - private boolean atRootDirectory(String filePath) { - if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + ctx.getPackageName() + "/cache") || - filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { - return true; - } - return false; - } + /** + * Loops through a directory deleting all the files. + * + * @param directory to be removed + * @return a boolean representing success of failure + * @throws FileExistsException + */ + private boolean removeDirRecursively(File directory) throws FileExistsException { + if (directory.isDirectory()) { + for (File file : directory.listFiles()) { + removeDirRecursively(file); + } + } - /** - * Look up metadata about this entry. - * - * @param filePath to entry - * @return a Metadata object - * @throws FileNotFoundException - * @throws JSONException - */ - private JSONObject getMetadata(String filePath) throws FileNotFoundException, JSONException { - File file = new File(filePath); - - if (!file.exists()) { - throw new FileNotFoundException("Failed to find file in getMetadata"); - } - - JSONObject metadata = new JSONObject(); - metadata.put("modificationTime", file.lastModified()); + if (!directory.delete()) { + throw new FileExistsException("could not delete: " + directory.getName()); + } else { + return true; + } + } - return metadata; - } + /** + * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty. + * It is an error to attempt to delete the root directory of a filesystem. + * + * @param filePath file or directory to be removed + * @return a boolean representing success of failure + * @throws NoModificationAllowedException + * @throws InvalidModificationException + */ + private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException { + File fp = new File(filePath); - /** - * Returns a File that represents the current state of the file that this FileEntry represents. - * - * @param filePath to entry - * @return returns a JSONObject represent a W3C File object - * @throws FileNotFoundException - * @throws JSONException - */ - private JSONObject getFileMetadata(String filePath) throws FileNotFoundException, JSONException { - File file = new File(filePath); - - if (!file.exists()) { - throw new FileNotFoundException("File: " + filePath + " does not exist."); - } - - JSONObject metadata = new JSONObject(); - metadata.put("size", file.length()); - metadata.put("type", getMimeType(filePath)); - metadata.put("name", file.getName()); - metadata.put("fullPath", file.getAbsolutePath()); - metadata.put("lastModifiedDate", file.lastModified()); + // You can't delete the root directory. + if (atRootDirectory(filePath)) { + throw new NoModificationAllowedException("You can't delete the root directory"); + } - return metadata; - } + // 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."); + } - /** - * Requests a filesystem in which to store application data. - * - * @param type of file system requested - * @return a JSONObject representing the file system - * @throws IOException - * @throws JSONException - */ - private JSONObject requestFileSystem(int type) throws IOException, JSONException { - JSONObject fs = new JSONObject(); - if (type == TEMPORARY) { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - fs.put("name", "temporary"); - fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() + - "/Android/data/" + ctx.getPackageName() + "/cache/")); - - // Create the cache dir if it doesn't exist. - File fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + - "/Android/data/" + ctx.getPackageName() + "/cache/"); - fp.mkdirs(); - } else { - throw new IOException("SD Card not mounted"); - } - } - else if (type == PERSISTENT) { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - fs.put("name", "persistent"); - fs.put("root", getEntry(Environment.getExternalStorageDirectory())); - } else { - throw new IOException("SD Card not mounted"); - } - } - else if (type == RESOURCE) { - fs.put("name", "resource"); - - } - else if (type == APPLICATION) { - fs.put("name", "application"); - - } - else { - throw new IOException("No filesystem of type requested"); - } + return fp.delete(); + } - return fs; - } + /** + * Creates or looks up a file. + * + * @param dirPath base directory + * @param fileName file/directory to lookup or create + * @param options specify whether to create or not + * @param directory if true look up directory, if false look up file + * @return a Entry object + * @throws FileExistsException + * @throws IOException + * @throws TypeMismatchException + * @throws EncodingException + * @throws JSONException + */ + private JSONObject getFile(String dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { + boolean create = false; + boolean exclusive = false; + if (options != null) { + create = options.optBoolean("create"); + if (create) { + exclusive = options.optBoolean("exclusive"); + } + } - /** - * Returns a JSON Object representing a directory on the device's file system - * - * @param path to the directory - * @return - * @throws JSONException - */ - private JSONObject getEntry(File file) throws JSONException { - JSONObject entry = new JSONObject(); + // Check for a ":" character in the file to line up with BB and iOS + if (fileName.contains(":")) { + throw new EncodingException("This file has a : in it's name"); + } - entry.put("isFile", file.isFile()); - entry.put("isDirectory", file.isDirectory()); - entry.put("name", file.getName()); - entry.put("fullPath", file.getAbsolutePath()); - // I can't add the next thing it as it would be an infinite loop - //entry.put("filesystem", null); + File fp = createFileObject(dirPath, fileName); - return entry; - } + 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"); + } + } + } - /** - * Returns a JSON Object representing a directory on the device's file system - * - * @param path to the directory - * @return - * @throws JSONException - */ - private JSONObject getEntry(String path) throws JSONException { - return getEntry(new File(path)); - } + // Return the directory + return getEntry(fp); + } - /** - * Identifies if action to be executed returns a value and should be run synchronously. - * - * @param action The action to execute - * @return T=returns value - */ - public boolean isSynch(String action) { - if (action.equals("testSaveLocationExists")) { - return true; - } - else if (action.equals("getFreeDiskSpace")) { - return true; - } - else if (action.equals("testFileExists")) { - return true; - } - else if (action.equals("testDirectoryExists")) { - return true; - } - return false; - } + /** + * If the path starts with a '/' just return that file object. If not construct the file + * object from the path passed in and the file name. + * + * @param dirPath root directory + * @param fileName new file name + * @return + */ + private File createFileObject(String dirPath, String fileName) { + File fp = null; + if (fileName.startsWith("/")) { + fp = new File(fileName); + } else { + fp = new File(dirPath + File.separator + fileName); + } + return fp; + } + + /** + * Look up the parent DirectoryEntry containing this Entry. + * If this Entry is the root of its filesystem, its parent is itself. + * + * @param filePath + * @return + * @throws JSONException + */ + private JSONObject getParent(String filePath) throws JSONException { + if (atRootDirectory(filePath)) { + return getEntry(filePath); + } + return getEntry(new File(filePath).getParent()); + } + + /** + * Checks to see if we are at the root directory. Useful since we are + * not allow to delete this directory. + * + * @param filePath to directory + * @return true if we are at the root, false otherwise. + */ + private boolean atRootDirectory(String filePath) { + if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + ctx.getPackageName() + "/cache") || + filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { + return true; + } + return false; + } + + /** + * Look up metadata about this entry. + * + * @param filePath to entry + * @return a Metadata object + * @throws FileNotFoundException + * @throws JSONException + */ + private JSONObject getMetadata(String filePath) throws FileNotFoundException, JSONException { + File file = new File(filePath); + + if (!file.exists()) { + throw new FileNotFoundException("Failed to find file in getMetadata"); + } + + JSONObject metadata = new JSONObject(); + metadata.put("modificationTime", file.lastModified()); + + return metadata; + } + + /** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param filePath to entry + * @return returns a JSONObject represent a W3C File object + * @throws FileNotFoundException + * @throws JSONException + */ + private JSONObject getFileMetadata(String filePath) throws FileNotFoundException, JSONException { + File file = new File(filePath); + + if (!file.exists()) { + throw new FileNotFoundException("File: " + filePath + " does not exist."); + } + + JSONObject metadata = new JSONObject(); + metadata.put("size", file.length()); + metadata.put("type", getMimeType(filePath)); + metadata.put("name", file.getName()); + metadata.put("fullPath", file.getAbsolutePath()); + metadata.put("lastModifiedDate", file.lastModified()); + + return metadata; + } + + /** + * Requests a filesystem in which to store application data. + * + * @param type of file system requested + * @return a JSONObject representing the file system + * @throws IOException + * @throws JSONException + */ + private JSONObject requestFileSystem(int type) throws IOException, JSONException { + JSONObject fs = new JSONObject(); + if (type == TEMPORARY) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + fs.put("name", "temporary"); + fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() + + "/Android/data/" + ctx.getPackageName() + "/cache/")); + + // Create the cache dir if it doesn't exist. + File fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + + "/Android/data/" + ctx.getPackageName() + "/cache/"); + fp.mkdirs(); + } else { + throw new IOException("SD Card not mounted"); + } + } + else if (type == PERSISTENT) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + fs.put("name", "persistent"); + fs.put("root", getEntry(Environment.getExternalStorageDirectory())); + } else { + throw new IOException("SD Card not mounted"); + } + } + else if (type == RESOURCE) { + fs.put("name", "resource"); + + } + else if (type == APPLICATION) { + fs.put("name", "application"); + + } + else { + throw new IOException("No filesystem of type requested"); + } + + return fs; + } + + /** + * Returns a JSON Object representing a directory on the device's file system + * + * @param path to the directory + * @return + * @throws JSONException + */ + public JSONObject getEntry(File file) throws JSONException { + JSONObject entry = new JSONObject(); + + entry.put("isFile", file.isFile()); + entry.put("isDirectory", file.isDirectory()); + entry.put("name", file.getName()); + entry.put("fullPath", file.getAbsolutePath()); + // I can't add the next thing it as it would be an infinite loop + //entry.put("filesystem", null); + + return entry; + } + + /** + * Returns a JSON Object representing a directory on the device's file system + * + * @param path to the directory + * @return + * @throws JSONException + */ + private JSONObject getEntry(String path) throws JSONException { + return getEntry(new File(path)); + } + + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + if (action.equals("testSaveLocationExists")) { + return true; + } + else if (action.equals("getFreeDiskSpace")) { + return true; + } + else if (action.equals("testFileExists")) { + return true; + } + else if (action.equals("testDirectoryExists")) { + return true; + } + return false; + } //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - + /** * Read content of text file. - * + * * @param filename The name of the file. * @param encoding The encoding to return contents as. Typical value is UTF-8. * (see http://www.iana.org/assignments/character-sets) @@ -898,125 +898,125 @@ public class FileUtils extends Plugin { * @throws FileNotFoundException, IOException */ public String readAsText(String filename, String encoding) throws FileNotFoundException, IOException { - byte[] bytes = new byte[1000]; - BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - int numRead = 0; - while ((numRead = bis.read(bytes, 0, 1000)) >= 0) { - bos.write(bytes, 0, numRead); - } + byte[] bytes = new byte[1000]; + BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int numRead = 0; + while ((numRead = bis.read(bytes, 0, 1000)) >= 0) { + bos.write(bytes, 0, numRead); + } return new String(bos.toByteArray(), encoding); } - + /** * Read content of text file and return as base64 encoded data url. - * + * * @param filename The name of the file. * @return Contents of file = data:;base64, * @throws FileNotFoundException, IOException */ public String readAsDataURL(String filename) throws FileNotFoundException, IOException { - byte[] bytes = new byte[1000]; - BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - int numRead = 0; - while ((numRead = bis.read(bytes, 0, 1000)) >= 0) { - bos.write(bytes, 0, numRead); - } - - // Determine content type from file name - String contentType = null; - if (filename.startsWith("content:")) { - Uri fileUri = Uri.parse(filename); - contentType = this.ctx.getContentResolver().getType(fileUri); - } - else { - contentType = getMimeType(filename); - } + byte[] bytes = new byte[1000]; + BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int numRead = 0; + while ((numRead = bis.read(bytes, 0, 1000)) >= 0) { + bos.write(bytes, 0, numRead); + } - byte[] base64 = Base64.encodeBase64(bos.toByteArray()); - String data = "data:" + contentType + ";base64," + new String(base64); - return data; + // Determine content type from file name + String contentType = null; + if (filename.startsWith("content:")) { + Uri fileUri = Uri.parse(filename); + contentType = this.ctx.getContentResolver().getType(fileUri); + } + else { + contentType = getMimeType(filename); + } + + byte[] base64 = Base64.encodeBase64(bos.toByteArray()); + String data = "data:" + contentType + ";base64," + new String(base64); + return data; } /** * Looks up the mime type of a given file name. - * + * * @param filename * @return a mime type */ - public static String getMimeType(String filename) { - MimeTypeMap map = MimeTypeMap.getSingleton(); - return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename)); - } - + public static String getMimeType(String filename) { + MimeTypeMap map = MimeTypeMap.getSingleton(); + return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename)); + } + /** * Write contents of file. - * + * * @param filename The name of the file. * @param data The contents of the file. - * @param offset The position to begin writing the file. + * @param offset The position to begin writing the file. * @throws FileNotFoundException, IOException */ /**/ public long write(String filename, String data, int offset) throws FileNotFoundException, IOException { - boolean append = false; - if (offset > 0) { - this.truncateFile(filename, offset); - append = true; - } - - byte [] rawData = data.getBytes(); - ByteArrayInputStream in = new ByteArrayInputStream(rawData); - FileOutputStream out = new FileOutputStream(filename, append); - byte buff[] = new byte[rawData.length]; - in.read(buff, 0, buff.length); - out.write(buff, 0, rawData.length); - out.flush(); - out.close(); - - return data.length(); + boolean append = false; + if (offset > 0) { + this.truncateFile(filename, offset); + append = true; + } + + byte [] rawData = data.getBytes(); + ByteArrayInputStream in = new ByteArrayInputStream(rawData); + FileOutputStream out = new FileOutputStream(filename, append); + byte buff[] = new byte[rawData.length]; + in.read(buff, 0, buff.length); + out.write(buff, 0, rawData.length); + out.flush(); + out.close(); + + return data.length(); } - + /** * Truncate the file to size - * + * * @param filename * @param size - * @throws FileNotFoundException, IOException + * @throws FileNotFoundException, IOException */ private long truncateFile(String filename, long size) throws FileNotFoundException, IOException { - RandomAccessFile raf = new RandomAccessFile(filename, "rw"); + RandomAccessFile raf = new RandomAccessFile(filename, "rw"); - if (raf.length() >= size) { - FileChannel channel = raf.getChannel(); - channel.truncate(size); - return size; - } - - return raf.length(); + if (raf.length() >= size) { + FileChannel channel = raf.getChannel(); + channel.truncate(size); + return size; + } + + return raf.length(); } - + /** * Get an input stream based on file path or content:// uri - * + * * @param path * @return an input stream - * @throws FileNotFoundException + * @throws FileNotFoundException */ - private InputStream getPathFromUri(String path) throws FileNotFoundException { - if (path.startsWith("content")) { - Uri uri = Uri.parse(path); - return ctx.getContentResolver().openInputStream(uri); - } - else { - return new FileInputStream(path); - } - } - + private InputStream getPathFromUri(String path) throws FileNotFoundException { + if (path.startsWith("content")) { + Uri uri = Uri.parse(path); + return ctx.getContentResolver().openInputStream(uri); + } + else { + return new FileInputStream(path); + } + } + /** * Queries the media store to find out what the file path is for the Uri we supply - * + * * @param contentUri the Uri of the audio/image/video * @param ctx the current applicaiton context * @return the full path to the file