diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js index ee853986..aa1c43e9 100755 --- a/framework/assets/js/file.js +++ b/framework/assets/js/file.js @@ -22,6 +22,23 @@ function FileProperties(filePath) { this.lastModifiedDate = null; } +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +function File(name, fullPath, type, lastModifiedDate, size) { + this.name = name || null; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; +} + /** * Create an event object since we can't set target on DOM event. * @@ -54,6 +71,10 @@ FileError.ENCODING_ERR = 5; FileError.NO_MODIFICATION_ALLOWED_ERR = 6; FileError.INVALID_STATE_ERR = 7; FileError.SYNTAX_ERR = 8; +FileError.INVALID_MODIFICATION_ERR = 9; +FileError.QUOTA_EXCEEDED_ERR = 10; +FileError.TYPE_MISMATCH_ERR = 11; +FileError.PATH_EXISTS_ERR = 12; //----------------------------------------------------------------------------- // File manager @@ -63,16 +84,12 @@ function FileMgr() { } FileMgr.prototype.getFileProperties = function(filePath) { - return PhoneGap.exec(null, null, "File", "getFile", [filePath]); + return PhoneGap.exec(null, null, "File", "getFileProperties", [filePath]); }; FileMgr.prototype.getFileBasePaths = function() { }; -FileMgr.prototype.getRootPaths = function() { - return PhoneGap.exec(null, null, "File", "getRootPaths", []); -}; - FileMgr.prototype.testSaveLocationExists = function(successCallback, errorCallback) { return PhoneGap.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []); }; @@ -85,18 +102,6 @@ FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, error return PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); }; -FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { - return PhoneGap.exec(successCallback, errorCallback, "File", "createDirectory", [dirName]); -}; - -FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { - return PhoneGap.exec(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); -}; - -FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { - return PhoneGap.exec(successCallback, errorCallback, "File", "deleteFile", [fileName]); -}; - FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { return PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []); }; @@ -198,11 +203,16 @@ FileReader.prototype.abort = function() { /** * Read text file. * - * @param file The name of the file + * @param file {File} File object containing file properties * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) */ FileReader.prototype.readAsText = function(file, encoding) { - this.fileName = file; + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } // LOADING state this.readyState = FileReader.LOADING; @@ -219,7 +229,7 @@ FileReader.prototype.readAsText = function(file, encoding) { var me = this; // Read file - navigator.fileMgr.readAsText(file, enc, + navigator.fileMgr.readAsText(this.fileName, enc, // Success callback function(r) { @@ -284,10 +294,15 @@ FileReader.prototype.readAsText = function(file, encoding) { * A data url is of the form: * data:[][;base64], * - * @param file The name of the file + * @param file {File} File object containing file properties */ FileReader.prototype.readAsDataURL = function(file) { - this.fileName = file; + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } // LOADING state this.readyState = FileReader.LOADING; @@ -301,7 +316,7 @@ FileReader.prototype.readAsDataURL = function(file) { var me = this; // Read file - navigator.fileMgr.readAsDataURL(file, + navigator.fileMgr.readAsDataURL(this.fileName, // Success callback function(r) { @@ -363,7 +378,7 @@ FileReader.prototype.readAsDataURL = function(file) { /** * Read file and return data as a binary data. * - * @param file The name of the file + * @param file {File} File object containing file properties */ FileReader.prototype.readAsBinaryString = function(file) { // TODO - Can't return binary data to browser. @@ -373,7 +388,7 @@ FileReader.prototype.readAsBinaryString = function(file) { /** * Read file and return data as a binary data. * - * @param file The name of the file + * @param file {File} File object containing file properties */ FileReader.prototype.readAsArrayBuffer = function(file) { // TODO - Can't return binary data to browser. @@ -391,19 +406,18 @@ FileReader.prototype.readAsArrayBuffer = function(file) { * The root directory is the root of the file system. * To write to the SD card, the file name is "sdcard/my_file.txt" * - * @param filePath the file to write to + * @param file {File} File object containing file properties * @param append if true write to the end of the file, otherwise overwrite the file */ -function FileWriter(filePath, append) { +function FileWriter(file) { this.fileName = ""; this.length = 0; - if (filePath) { - var f = navigator.fileMgr.getFileProperties(filePath); - this.fileName = f.name; - this.length = f.size; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; } // default is to write at the beginning of the file - this.position = (append !== true) ? 0 : this.length; + this.position = 0; this.readyState = 0; // EMPTY @@ -753,3 +767,418 @@ FileWriter.prototype.truncate = function(size) { ); }; +function LocalFileSystem() { +}; + +// File error codes +LocalFileSystem.TEMPORARY = 0; +LocalFileSystem.PERSISTENT = 1; +LocalFileSystem.RESOURCE = 2; +LocalFileSystem.APPLICATION = 3; + +/** + * Requests a filesystem in which to store application data. + * + * @param {int} type of file system being requested + * @param {Function} successCallback is called with the new FileSystem + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) { + if (type < 0 || type > 3) { + if (typeof errorCallback == "function") { + errorCallback({ + "code": FileError.SYNTAX_ERR + }); + } + } + else { + PhoneGap.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]); + } +}; + +/** + * + * @param {DOMString} uri referring to a local file in a filesystem + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]); +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.service.contacts.find but before the find methods success call back. +* +* @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects. +* @returns an entry +*/ +LocalFileSystem.prototype._castFS = function(pluginResult) { + var entry = null; + entry = new DirectoryEntry(); + entry.isDirectory = pluginResult.message.root.isDirectory; + entry.isFile = pluginResult.message.root.isFile; + entry.name = pluginResult.message.root.name; + entry.fullPath = pluginResult.message.root.fullPath; + pluginResult.message.root = entry; + return pluginResult; +} + +LocalFileSystem.prototype._castEntry = function(pluginResult) { + var entry = null; + if (pluginResult.message.isDirectory) { + console.log("This is a dir"); + entry = new DirectoryEntry(); + } + else if (pluginResult.message.isFile) { + console.log("This is a file"); + entry = new FileEntry(); + } + entry.isDirectory = pluginResult.message.isDirectory; + entry.isFile = pluginResult.message.isFile; + entry.name = pluginResult.message.name; + entry.fullPath = pluginResult.message.fullPath; + pluginResult.message = entry; + return pluginResult; +} + +LocalFileSystem.prototype._castEntries = function(pluginResult) { + var entries = pluginResult.message; + var retVal = []; + for (i=0; i 0) { - s = s + ","; - } - type = typeof args[i]; - if ((type === "number") || (type === "boolean")) { - s = s + args[i]; - } else if (args[i] instanceof Array) { - s = s + "[" + args[i] + "]"; - } else if (args[i] instanceof Object) { - start = true; - s = s + '{'; - for (name in args[i]) { - if (args[i][name] !== null) { - if (!start) { - s = s + ','; - } - s = s + '"' + name + '":'; - nameType = typeof args[i][name]; - if ((nameType === "number") || (nameType === "boolean")) { - s = s + args[i][name]; - } else if ((typeof args[i][name]) === 'function') { - // don't copy the functions - s = s + '""'; - } else if (args[i][name] instanceof Object) { - s = s + this.stringify(args[i][name]); - } else { - s = s + '"' + args[i][name] + '"'; - } - start = false; - } + if (args[i] != null) { + if (i > 0) { + s = s + ","; + } + type = typeof args[i]; + if ((type === "number") || (type === "boolean")) { + s = s + args[i]; + } else if (args[i] instanceof Array) { + s = s + "[" + args[i] + "]"; + } else if (args[i] instanceof Object) { + start = true; + s = s + '{'; + for (name in args[i]) { + if (args[i][name] !== null) { + if (!start) { + s = s + ','; + } + s = s + '"' + name + '":'; + nameType = typeof args[i][name]; + if ((nameType === "number") || (nameType === "boolean")) { + s = s + args[i][name]; + } else if ((typeof args[i][name]) === 'function') { + // don't copy the functions + s = s + '""'; + } else if (args[i][name] instanceof Object) { + s = s + this.stringify(args[i][name]); + } else { + s = s + '"' + args[i][name] + '"'; + } + start = false; + } + } + s = s + '}'; + } else { + a = args[i].replace(/\\/g, '\\\\'); + a = a.replace(/"/g, '\\"'); + s = s + '"' + a + '"'; } - s = s + '}'; - } else { - a = args[i].replace(/\\/g, '\\\\'); - a = a.replace(/"/g, '\\"'); - s = s + '"' + a + '"'; } } s = s + "]"; diff --git a/framework/src/com/phonegap/DirectoryManager.java b/framework/src/com/phonegap/DirectoryManager.java index b452c2b3..a659527d 100644 --- a/framework/src/com/phonegap/DirectoryManager.java +++ b/framework/src/com/phonegap/DirectoryManager.java @@ -8,15 +8,9 @@ package com.phonegap; import java.io.File; -import java.util.Date; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import android.os.Environment; import android.os.StatFs; -import android.util.Log; /** * This class provides file directory utilities. @@ -78,29 +72,6 @@ public class DirectoryManager { return (freeSpace); } - /** - * Create directory on SD card. - * - * @param directoryName The name of the directory to create. - * @return T=successful, F=failed - */ - protected static boolean createDirectory(String directoryName) { - boolean status; - - // Make sure SD card exists - if ((testSaveLocationExists()) && (!directoryName.equals(""))) { - File path = Environment.getExternalStorageDirectory(); - File newPath = constructFilePaths(path.toString(), directoryName); - status = newPath.mkdir(); - status = true; - } - - // If no SD card or invalid dir name - else { - status = false; - } - return status; - } /** * Determine if SD card exists. @@ -123,95 +94,6 @@ public class DirectoryManager { return status; } - /** - * Delete directory. - * - * @param fileName The name of the directory to delete - * @return T=deleted, F=could not delete - */ - protected static boolean deleteDirectory(String fileName) { - boolean status; - SecurityManager checker = new SecurityManager(); - - // Make sure SD card exists - if ((testSaveLocationExists()) && (!fileName.equals(""))) { - File path = Environment.getExternalStorageDirectory(); - File newPath = constructFilePaths(path.toString(), fileName); - checker.checkDelete(newPath.toString()); - - // If dir to delete is really a directory - if (newPath.isDirectory()) { - String[] listfile = newPath.list(); - - // Delete all files within the specified directory and then delete the directory - try{ - for (int i=0; i < listfile.length; i++){ - File deletedFile = new File (newPath.toString()+"/"+listfile[i].toString()); - deletedFile.delete(); - } - newPath.delete(); - Log.i("DirectoryManager deleteDirectory", fileName); - status = true; - } - catch (Exception e){ - e.printStackTrace(); - status = false; - } - } - - // If dir not a directory, then error - else { - status = false; - } - } - - // If no SD card - else { - status = false; - } - return status; - } - - /** - * Delete file. - * - * @param fileName The name of the file to delete - * @return T=deleted, F=not deleted - */ - protected static boolean deleteFile(String fileName) { - boolean status; - SecurityManager checker = new SecurityManager(); - - // Make sure SD card exists - if ((testSaveLocationExists()) && (!fileName.equals(""))) { - File path = Environment.getExternalStorageDirectory(); - File newPath = constructFilePaths(path.toString(), fileName); - checker.checkDelete(newPath.toString()); - - // If file to delete is really a file - if (newPath.isFile()){ - try { - Log.i("DirectoryManager deleteFile", fileName); - newPath.delete(); - status = true; - }catch (SecurityException se){ - se.printStackTrace(); - status = false; - } - } - // If not a file, then error - else { - status = false; - } - } - - // If no SD card - else { - status = false; - } - return status; - } - /** * Create a new file object from two file paths. * @@ -229,45 +111,4 @@ public class DirectoryManager { } return newPath; } - - /** - * This method will determine the file properties of the file specified - * by the filePath. Creates a JSONObject with name, lastModifiedDate and - * size properties. - * - * @param filePath the file to get the properties of - * @return a JSONObject with the files properties - */ - protected static JSONObject getFile(String filePath) { - File fp = new File(filePath); - - JSONObject obj = new JSONObject(); - try { - obj.put("name", fp.getAbsolutePath()); - obj.put("lastModifiedDate", new Date(fp.lastModified()).toString()); - obj.put("size", fp.length()); - } - catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - - return obj; - } - - /** - * This method returns a JSONArray of file paths. Android's default - * location where files can be written is Environment.getExternalStorageDirectory(). - * We are returning a array with one element so the interface can remain - * consistent with BlackBerry as they have two areas where files can be - * written. - * - * @return an array of file paths - */ - protected static JSONArray getRootPaths() { - JSONArray retVal = new JSONArray(); - String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/"; - retVal.put(path); - return retVal; - } - } \ No newline at end of file diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index 37fa0fea..b88fc962 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -8,6 +8,8 @@ package com.phonegap; import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.channels.FileChannel; import org.apache.commons.codec.binary.Base64; @@ -16,16 +18,25 @@ import org.json.JSONException; import org.json.JSONObject; import android.net.Uri; +import android.os.Environment; +import android.util.Log; import android.webkit.MimeTypeMap; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; +import com.phonegap.file.EncodingException; +import com.phonegap.file.FileExistsException; +import com.phonegap.file.InvalidModificationException; +import com.phonegap.file.NoModificationAllowedException; +import com.phonegap.file.TypeMismatchException; /** * This class provides SD card file and directory services to JavaScript. * Only files on the SD card can be accessed. */ public class FileUtils extends Plugin { + 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; @@ -35,7 +46,16 @@ public class FileUtils extends Plugin { 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; @@ -59,114 +79,763 @@ public class FileUtils extends Plugin { //System.out.println("FileUtils.execute("+action+")"); 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("deleteDirectory")) { - boolean b = DirectoryManager.deleteDirectory(args.getString(0)); - return new PluginResult(status, b); - } - else if (action.equals("deleteFile")) { - boolean b = DirectoryManager.deleteFile(args.getString(0)); - return new PluginResult(status, b); - } - else if (action.equals("createDirectory")) { - boolean b = DirectoryManager.createDirectory(args.getString(0)); - return new PluginResult(status, b); - } - else if (action.equals("getRootPaths")) { - return new PluginResult(status, DirectoryManager.getRootPaths()); - } - else if (action.equals("readAsText")) { - try { - String s = this.readAsText(args.getString(0), args.getString(1)); - return new PluginResult(status, s); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); - } catch (IOException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + 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")) { + try { + String s = this.readAsText(args.getString(0), args.getString(1)); + return new PluginResult(status, s); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("readAsDataURL")) { + try { + String s = this.readAsDataURL(args.getString(0)); + return new PluginResult(status, s); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("writeAsText")) { + try { + this.writeAsText(args.getString(0), args.getString(1), args.getBoolean(2)); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("write")) { + try { + long fileSize = this.write(args.getString(0), args.getString(1), args.getLong(2)); + return new PluginResult(status, fileSize); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("truncate")) { + try { + long fileSize = this.truncateFile(args.getString(0), args.getLong(1)); + return new PluginResult(status, fileSize); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("requestFileSystem")) { + long size = args.optLong(1); + if (size != 0) { + if (size > DirectoryManager.getFreeDiskSpace()) { + 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("readAsDataURL")) { - try { - String s = this.readAsDataURL(args.getString(0)); - return new PluginResult(status, s); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); - } catch (IOException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + else if (action.equals("resolveLocalFileSystemURI")) { + JSONObject obj = resolveLocalFileSystemURI(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castEntry"); } - } - else if (action.equals("writeAsText")) { - try { - this.writeAsText(args.getString(0), args.getString(1), args.getBoolean(2)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); - } catch (IOException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + else if (action.equals("getMetadata")) { + JSONObject obj = getMetadata(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castDate"); } - } - else if (action.equals("write")) { - try { - long fileSize = this.write(args.getString(0), args.getString(1), args.getLong(2)); - return new PluginResult(status, fileSize); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); - } catch (IOException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + else if (action.equals("getFileMetadata")) { + JSONObject obj = getFileMetadata(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castDate"); } - } - else if (action.equals("truncate")) { - try { - long fileSize = this.truncateFile(args.getString(0), args.getLong(1)); - return new PluginResult(status, fileSize); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); - } catch (IOException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + else if (action.equals("getParent")) { + JSONObject obj = getParent(args.getString(0)); + return new PluginResult(status, obj, "window.localFileSystem._castEntry"); } - } - else if (action.equals("getFile")) { - JSONObject obj = DirectoryManager.getFile(args.getString(0)); - return new PluginResult(status, obj); + 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) { + 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); } - return new PluginResult(status, result); } catch (JSONException e) { e.printStackTrace(); return new PluginResult(PluginResult.Status.JSON_EXCEPTION); } } + /** + * 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 { + // Test to see if this is a valid URL first + @SuppressWarnings("unused") + URL testUrl = new URL(url); + + File fp = null; + if (url.startsWith("file://")) { + fp = new File(url.substring(7, url.length())); + } else { + fp = new File(url); + } + if (!fp.exists()) { + throw new FileNotFoundException(); + } + if (!fp.canRead()) { + throw new IOException(); + } + return getEntry(fp); + } + + /** + * Read the list of files from this directory. + * + * @param fileName the directory to read from + * @return a JSONArray containing JSONObjects that represent Entry objects. + * @throws FileNotFoundException if the directory is not found. + * @throws JSONException + */ + private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException { + File fp = new File(fileName); + + if (!fp.exists()) { + // The directory we are listing doesn't exist so we should fail. + throw new FileNotFoundException(); + } + + JSONArray entries = new JSONArray(); + + if (fp.isDirectory()) { + File[] files = fp.listFiles(); + for (int i=0; i 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); + } + + /** + * 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); + + // You can't delete the root directory. + if (atRootDirectory(filePath)) { + return false; + } + + return removeDirRecursively(fp); + } + + /** + * Loops through a directory deleting all the files. + * + * @param directory to be removed + * @return a boolean representing success of failure + * @throws FileExistsException + */ + private boolean removeDirRecursively(File directory) throws FileExistsException { + if (directory.isDirectory()) { + for (File file : directory.listFiles()) { + removeDirRecursively(file); + } + } + + if (!directory.delete()) { + throw new FileExistsException("could not delete: " + directory.getName()); + } else { + return true; + } + } + + /** + * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty. + * It is an error to attempt to delete the root directory of a filesystem. + * + * @param filePath file or directory to be removed + * @return a boolean representing success of failure + * @throws NoModificationAllowedException + * @throws InvalidModificationException + */ + private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException { + File fp = 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(); + } + + /** + * Creates or looks up a file. + * + * @param dirPath base directory + * @param fileName file/directory to lookup or create + * @param options specify whether to create or not + * @param directory if true look up directory, if false look up file + * @return a Entry object + * @throws FileExistsException + * @throws IOException + * @throws TypeMismatchException + * @throws EncodingException + * @throws JSONException + */ + private JSONObject getFile(String dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException { + boolean create = false; + boolean exclusive = false; + if (options != null) { + create = options.optBoolean("create"); + if (create) { + exclusive = options.optBoolean("exclusive"); + } + } + + // Check for a ":" character in the file to line up with BB and iOS + if (fileName.contains(":")) { + throw new EncodingException("This file has a : in it's name"); + } + + File fp = createFileObject(dirPath, fileName); + + if (create) { + if (exclusive && fp.exists()) { + throw new FileExistsException("create/exclusive fails"); + } + if (directory) { + fp.mkdir(); + } else { + fp.createNewFile(); + } + if (!fp.exists()) { + throw new FileExistsException("create fails"); + } + } + else { + if (!fp.exists()) { + throw new FileNotFoundException("path does not exist"); + } + if (directory) { + if (fp.isFile()) { + throw new TypeMismatchException("path doesn't exist or is file"); + } + } else { + if (fp.isDirectory()) { + throw new TypeMismatchException("path doesn't exist or is directory"); + } + } + } + + // Return the directory + return getEntry(fp); + } + + /** + * If the path starts with a '/' just return that file object. If not construct the file + * object from the path passed in and the file name. + * + * @param dirPath root directory + * @param fileName new file name + * @return + */ + private File createFileObject(String dirPath, String fileName) { + File fp = null; + if (fileName.startsWith("/")) { + fp = new File(fileName); + } else { + 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 + */ + private 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) { + public boolean isSynch(String action) { if (action.equals("readAsText")) { return false; } @@ -176,6 +845,39 @@ public class FileUtils extends Plugin { else if (action.equals("writeAsText")) { return false; } + else if (action.equals("requestFileSystem")) { + return false; + } + else if (action.equals("getMetadata")) { + return false; + } + else if (action.equals("toURI")) { + return false; + } + else if (action.equals("getParent")) { + return false; + } + else if (action.equals("getFile")) { + return false; + } + else if (action.equals("getDirectory")) { + return false; + } + else if (action.equals("remove")) { + return false; + } + else if (action.equals("removeRecursively")) { + return false; + } + else if (action.equals("readEntries")) { + return false; + } + else if (action.equals("getFileMetadata")) { + return false; + } + else if (action.equals("resolveLocalFileSystemURI")) { + return false; + } return true; } @@ -226,14 +928,24 @@ public class FileUtils extends Plugin { contentType = this.ctx.getContentResolver().getType(fileUri); } else { - MimeTypeMap map = MimeTypeMap.getSingleton(); - contentType = map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename)); + 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 + */ + private String getMimeType(String filename) { + MimeTypeMap map = MimeTypeMap.getSingleton(); + return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename)); + } /** * Write contents of file. @@ -309,3 +1021,4 @@ public class FileUtils extends Plugin { } } } + diff --git a/framework/src/com/phonegap/api/PluginResult.java b/framework/src/com/phonegap/api/PluginResult.java index d042d64c..41371702 100755 --- a/framework/src/com/phonegap/api/PluginResult.java +++ b/framework/src/com/phonegap/api/PluginResult.java @@ -10,6 +10,8 @@ package com.phonegap.api; import org.json.JSONArray; import org.json.JSONObject; +import android.util.Log; + public class PluginResult { private final int status; private final String message; @@ -32,6 +34,12 @@ public class PluginResult { this.cast = cast; } + public PluginResult(Status status, JSONObject message, String cast) { + this.status = status.ordinal(); + this.message = message.toString(); + this.cast = cast; + } + public PluginResult(Status status, JSONArray message) { this.status = status.ordinal(); this.message = message.toString(); diff --git a/framework/src/com/phonegap/file/EncodingException.java b/framework/src/com/phonegap/file/EncodingException.java new file mode 100644 index 00000000..7dcf7af0 --- /dev/null +++ b/framework/src/com/phonegap/file/EncodingException.java @@ -0,0 +1,9 @@ +package com.phonegap.file; + +public class EncodingException extends Exception { + + public EncodingException(String message) { + super(message); + } + +} diff --git a/framework/src/com/phonegap/file/FileExistsException.java b/framework/src/com/phonegap/file/FileExistsException.java new file mode 100644 index 00000000..22c40ab2 --- /dev/null +++ b/framework/src/com/phonegap/file/FileExistsException.java @@ -0,0 +1,9 @@ +package com.phonegap.file; + +public class FileExistsException extends Exception { + + public FileExistsException(String msg) { + super(msg); + } + +} diff --git a/framework/src/com/phonegap/file/InvalidModificationException.java b/framework/src/com/phonegap/file/InvalidModificationException.java new file mode 100644 index 00000000..bd706299 --- /dev/null +++ b/framework/src/com/phonegap/file/InvalidModificationException.java @@ -0,0 +1,9 @@ +package com.phonegap.file; + +public class InvalidModificationException extends Exception { + + public InvalidModificationException(String message) { + super(message); + } + +} diff --git a/framework/src/com/phonegap/file/NoModificationAllowedException.java b/framework/src/com/phonegap/file/NoModificationAllowedException.java new file mode 100644 index 00000000..d120fb8f --- /dev/null +++ b/framework/src/com/phonegap/file/NoModificationAllowedException.java @@ -0,0 +1,9 @@ +package com.phonegap.file; + +public class NoModificationAllowedException extends Exception { + + public NoModificationAllowedException(String message) { + super(message); + } + +} diff --git a/framework/src/com/phonegap/file/TypeMismatchException.java b/framework/src/com/phonegap/file/TypeMismatchException.java new file mode 100644 index 00000000..7af86698 --- /dev/null +++ b/framework/src/com/phonegap/file/TypeMismatchException.java @@ -0,0 +1,9 @@ +package com.phonegap.file; + +public class TypeMismatchException extends Exception { + + public TypeMismatchException(String message) { + super(message); + } + +}