diff --git a/framework/assets/js/contact.js b/framework/assets/js/contact.js index 9da9ab3b..56dc0be6 100755 --- a/framework/assets/js/contact.js +++ b/framework/assets/js/contact.js @@ -72,8 +72,9 @@ Contact.prototype.remove = function(successCB, errorCB) { errorObj.code = ContactError.NOT_FOUND_ERROR; errorCB(errorObj); } - - PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); + else { + PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); + } }; /** diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js index 4fdc75dd..d748de24 100755 --- a/framework/assets/js/file.js +++ b/framework/assets/js/file.js @@ -12,19 +12,14 @@ */ /** - * List of files + * This class provides some useful information about a file. + * This is the fields returned when navigator.fileMgr.getFileProperties() + * is called. */ -function FileList() { - this.files = {}; -}; - -/** - * Describes a single file in a FileList - */ -function File() { - this.name = null; - this.type = null; - this.urn = null; +function FileProperties(filePath) { + this.filePath = filePath; + this.size = 0; + this.lastModifiedDate = null; }; /** @@ -44,22 +39,22 @@ File._createEvent = function(type, target) { }; function FileError() { - // File error codes - // Found in DOMException - this.NOT_FOUND_ERR = 1; - this.SECURITY_ERR = 2; - this.ABORT_ERR = 3; - - // Added by this specification - this.NOT_READABLE_ERR = 4; - this.ENCODING_ERR = 5; - this.NO_MODIFICATION_ALLOWED_ERR = 6; - this.INVALID_STATE_ERR = 7; - this.SYNTAX_ERR = 8; - this.code = null; }; +// File error codes +// Found in DOMException +FileError.NOT_FOUND_ERR = 1; +FileError.SECURITY_ERR = 2; +FileError.ABORT_ERR = 3; + +// Added by this specification +FileError.NOT_READABLE_ERR = 4; +FileError.ENCODING_ERR = 5; +FileError.NO_MODIFICATION_ALLOWED_ERR = 6; +FileError.INVALID_STATE_ERR = 7; +FileError.SYNTAX_ERR = 8; + //----------------------------------------------------------------------------- // File manager //----------------------------------------------------------------------------- @@ -67,41 +62,53 @@ function FileError() { function FileMgr() { }; +FileMgr.prototype.getFileProperties = function(filePath) { + return PhoneGap.exec(null, null, "File", "getFile", [filePath]); +}; + FileMgr.prototype.getFileBasePaths = function() { }; +FileMgr.prototype.getRootPaths = function() { + return PhoneGap.exec(null, null, "File", "getRootPaths", []); +}; + FileMgr.prototype.testSaveLocationExists = function(successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []); + return PhoneGap.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []); }; FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testFileExists", [fileName]); + return PhoneGap.exec(successCallback, errorCallback, "File", "testFileExists", [fileName]); }; FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); + return PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); }; FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "createDirectory", [dirName]); + return PhoneGap.exec(successCallback, errorCallback, "File", "createDirectory", [dirName]); }; FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); + return PhoneGap.exec(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); }; FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "deleteFile", [fileName]); + return PhoneGap.exec(successCallback, errorCallback, "File", "deleteFile", [fileName]); }; FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []); + return PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []); }; FileMgr.prototype.writeAsText = function(fileName, data, append, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "writeAsText", [fileName, data, append]); }; +FileMgr.prototype.write = function(fileName, data, position, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "write", [fileName, data, position]); +}; + FileMgr.prototype.truncate = function(fileName, size, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "truncate", [fileName, size]); }; @@ -378,9 +385,20 @@ FileReader.prototype.readAsArrayBuffer = function(file) { * For Android: * 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 append if true write to the end of the file, otherwise overwrite the file */ -function FileWriter() { +function FileWriter(filePath, append) { this.fileName = ""; + this.length = 0; + if (filePath) { + var f = navigator.fileMgr.getFileProperties(filePath); + this.fileName = f.name; + this.length = f.size; + } + // default is to write at the beginning of the file + this.position = (append !== true) ? 0 : this.length; this.readyState = 0; // EMPTY @@ -432,6 +450,13 @@ FileWriter.prototype.abort = function() { } }; +/** + * @Deprecated: use write instead + * + * @param file to write the data to + * @param text to be written + * @param bAppend if true write to end of file, otherwise overwrite the file + */ FileWriter.prototype.writeAsText = function(file, text, bAppend) { // Throw an exception if we are already writing a file if (this.readyState == FileWriter.WRITING) { @@ -515,14 +540,17 @@ FileWriter.prototype.writeAsText = function(file, text, bAppend) { }; -FileWriter.prototype.truncate = function(file, size) { +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { // Throw an exception if we are already writing a file if (this.readyState == FileWriter.WRITING) { throw FileError.INVALID_STATE_ERR; } - this.fileName = file; - // WRITING state this.readyState = FileWriter.WRITING; @@ -535,7 +563,7 @@ FileWriter.prototype.truncate = function(file, size) { } // Write file - navigator.fileMgr.truncate(file, size, + navigator.fileMgr.write(this.fileName, text, this.position, // Success callback function(r) { @@ -545,8 +573,128 @@ FileWriter.prototype.truncate = function(file, size) { return; } - // Save result - me.result = r; + // So if the user wants to keep appending to the file + me.length = Math.max(me.length, me.position + r); + // position always increases by bytes written because file would be extended + me.position += r; + + // If onwrite callback + if (typeof me.onwrite == "function") { + var evt = File._createEvent("write", me); + me.onwrite(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + } + ); + +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + if (!offset) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger then file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState == FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart == "function") { + var evt = File._createEvent("writestart", me); + me.onwritestart(evt); + } + + // Write file + navigator.fileMgr.truncate(this.fileName, size, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r);; // If onwrite callback if (typeof me.onwrite == "function") { diff --git a/framework/src/com/phonegap/DirectoryManager.java b/framework/src/com/phonegap/DirectoryManager.java index b1270475..d9ba47ca 100644 --- a/framework/src/com/phonegap/DirectoryManager.java +++ b/framework/src/com/phonegap/DirectoryManager.java @@ -8,6 +8,11 @@ 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; @@ -21,6 +26,8 @@ import android.util.Log; */ public class DirectoryManager { + private static final String LOG_TAG = "DirectoryManager"; + /** * Determine if a file or directory exists. * @@ -36,7 +43,6 @@ public class DirectoryManager { File newPath = constructFilePaths(path.toString(), name); status = newPath.exists(); } - // If no SD card else{ status = false; @@ -215,8 +221,52 @@ public class DirectoryManager { */ private static File constructFilePaths (String file1, String file2) { File newPath; - newPath = new File(file1+"/"+file2); + if (file2.startsWith(file1)) { + newPath = new File(file2); + } + else { + newPath = new File(file1+"/"+file2); + } 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(); + retVal.put(Environment.getExternalStorageDirectory().getAbsolutePath()); + 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 e316a5a8..72a33c9e 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -13,6 +13,7 @@ import java.nio.channels.FileChannel; import org.apache.commons.codec.binary.Base64; import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; @@ -82,6 +83,9 @@ public class FileUtils extends Plugin { 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 { @@ -118,9 +122,10 @@ public class FileUtils extends Plugin { return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); } } - else if (action.equals("truncate")) { + else if (action.equals("write")) { try { - this.truncateFile(args.getString(0), args.getLong(1)); + 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); @@ -129,6 +134,22 @@ public class FileUtils extends Plugin { 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 (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("getFile")) { + JSONObject obj = DirectoryManager.getFile(args.getString(0)); + return new PluginResult(status, obj); + } return new PluginResult(status, result); } catch (JSONException e) { e.printStackTrace(); @@ -169,15 +190,14 @@ public class FileUtils extends Plugin { * @throws FileNotFoundException, IOException */ public String readAsText(String filename, String encoding) throws FileNotFoundException, IOException { - StringBuilder data = new StringBuilder(); - FileInputStream fis = new FileInputStream(filename); - BufferedReader reader = new BufferedReader(new InputStreamReader(fis, encoding), 1024); - String line; - while ((line = reader.readLine()) != null) { - data.append(line); - data.append('\n'); - } - return data.toString(); + byte[] bytes = new byte[1000]; + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(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); } /** @@ -225,6 +245,23 @@ public class FileUtils extends Plugin { out.close(); } + /** + * 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. + * @throws FileNotFoundException, IOException + */ + public long write(String filename, String data, long offset) throws FileNotFoundException, IOException { + RandomAccessFile file = new RandomAccessFile(filename, "rw"); + file.seek(offset); + file.writeBytes(data); + file.close(); + + return data.length(); + } + /** * Truncate the file to size @@ -233,13 +270,16 @@ public class FileUtils extends Plugin { * @param size * @throws FileNotFoundException, IOException */ - private void truncateFile(String filename, long size) throws FileNotFoundException, IOException { + private long truncateFile(String filename, long size) throws FileNotFoundException, IOException { RandomAccessFile raf = new RandomAccessFile(filename, "rw"); - + if (raf.length() >= size) { FileChannel channel = raf.getChannel(); channel.truncate(size); + return size; } + + return raf.length(); } }