You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by de...@apache.org on 2012/02/08 21:14:02 UTC
[11/19] CB-226 Rename to Cordova.
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/file/FileManager.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/file/FileManager.java b/framework/ext/src/org/apache/cordova/file/FileManager.java
new file mode 100644
index 0000000..b59dfcb
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/file/FileManager.java
@@ -0,0 +1,1001 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.file;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+
+import javax.microedition.io.Connector;
+import javax.microedition.io.file.FileConnection;
+import javax.microedition.io.file.FileSystemRegistry;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.io.Base64OutputStream;
+import net.rim.device.api.io.FileNotFoundException;
+import net.rim.device.api.io.MIMETypeAssociations;
+import net.rim.device.api.system.Application;
+
+public class FileManager extends Plugin {
+
+ /**
+ * File related errors.
+ */
+ public static int NOT_FOUND_ERR = 1;
+ public static int SECURITY_ERR = 2;
+ public static int ABORT_ERR = 3;
+ public static int NOT_READABLE_ERR = 4;
+ public static int ENCODING_ERR = 5;
+ public static int NO_MODIFICATION_ALLOWED_ERR = 6;
+ public static int INVALID_STATE_ERR = 7;
+ public static int SYNTAX_ERR = 8;
+ public static int INVALID_MODIFICATION_ERR = 9;
+ public static int QUOTA_EXCEEDED_ERR = 10;
+ public static int TYPE_MISMATCH_ERR = 11;
+ public static int PATH_EXISTS_ERR = 12;
+
+ /**
+ * File system for storing information on a temporary basis (no guaranteed persistence).
+ */
+ public static final short FS_TEMPORARY = 0;
+
+ /**
+ * File system for storing information on a permanent basis.
+ */
+ public static final short FS_PERSISTENT = 1;
+
+ /**
+ * Possible actions.
+ */
+ protected static String ACTION_READ_AS_TEXT = "readAsText";
+ protected static String ACTION_READ_AS_DATA_URL = "readAsDataURL";
+ protected static String ACTION_WRITE = "write";
+ protected static String ACTION_TRUNCATE = "truncate";
+ protected static String ACTION_REQUEST_FILE_SYSTEM = "requestFileSystem";
+ protected static String ACTION_RESOLVE_FILE_SYSTEM_URI = "resolveLocalFileSystemURI";
+ protected static String ACTION_GET_METADATA = "getMetadata";
+ protected static String ACTION_LIST_DIRECTORY = "readEntries";
+ protected static String ACTION_COPY_TO = "copyTo";
+ protected static String ACTION_MOVE_TO = "moveTo";
+ protected static String ACTION_IS_FILE_SYSTEM_ROOT = "isFileSystemRoot";
+
+ /**
+ * Executes the requested action and returns a PluginResult.
+ *
+ * @param action
+ * The action to execute.
+ * @param callbackId
+ * The callback ID to be invoked upon action completion
+ * @param args
+ * JSONArry of arguments for the action.
+ * @return A PluginResult object with a status and message.
+ */
+ public PluginResult execute(String action, JSONArray args, String callbackId) {
+
+ // perform specified action
+ if (ACTION_READ_AS_TEXT.equals(action)) {
+ // get file path
+ String filePath = null;
+ try {
+ filePath = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return readAsText(filePath, args.optString(1));
+ }
+ else if (ACTION_READ_AS_DATA_URL.equals(action)) {
+ // get file path
+ String filePath = null;
+ try {
+ filePath = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return readAsDataURL(filePath);
+ }
+ else if (ACTION_WRITE.equals(action)) {
+ // file path
+ String filePath = null;
+ try {
+ filePath = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+
+ // file data
+ String data = null;
+ try {
+ data = args.getString(1);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Unable to parse file data: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+
+ // position
+ int position = 0;
+ try {
+ position = Integer.parseInt(args.optString(2));
+ }
+ catch (NumberFormatException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid position parameter: " + e);
+ return new PluginResult(
+ PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return writeFile(filePath, data, position);
+ }
+ else if (ACTION_TRUNCATE.equals(action)) {
+ // file path
+ String filePath = null;
+ try {
+ filePath = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+
+ // file size
+ long fileSize = 0;
+ try {
+ // retrieve new file size
+ fileSize = Long.parseLong(args.getString(1));
+ }
+ catch (Exception e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid file size parameter: " + e);
+ return new PluginResult(
+ PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return truncateFile(filePath, fileSize);
+ }
+ else if (ACTION_REQUEST_FILE_SYSTEM.equals(action)) {
+ int fileSystemType = -1;
+ long fileSystemSize = 0;
+ try {
+ fileSystemType = args.getInt(0);
+ fileSystemSize = (args.isNull(1) == true) ? 0 : args.getLong(1);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid file system type: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return requestFileSystem(fileSystemType, fileSystemSize);
+ }
+ else if (ACTION_RESOLVE_FILE_SYSTEM_URI.equals(action)) {
+ String uri = null;
+ try {
+ uri = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing file URI: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return resolveFileSystemURI(uri);
+ }
+ else if (ACTION_GET_METADATA.equals(action)) {
+ String path = null;
+ try {
+ path = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing file URI: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return getMetadata(path);
+ }
+ else if (ACTION_LIST_DIRECTORY.equals(action)) {
+ String path = null;
+ try {
+ path = args.getString(0);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return listDirectory(path);
+ }
+ else if (ACTION_COPY_TO.equals(action)) {
+ String srcPath = null;
+ String parent = null;
+ String newName = null;
+ try {
+ srcPath = args.getString(0);
+ parent = args.getString(1);
+ newName = args.getString(2);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return copyTo(srcPath, parent, newName);
+ }
+ else if (ACTION_MOVE_TO.equals(action)) {
+ String srcPath = null;
+ String parent = null;
+ String newName = null;
+ try {
+ srcPath = args.getString(0);
+ parent = args.getString(1);
+ newName = args.getString(2);
+ }
+ catch (JSONException e) {
+ Logger.log(this.getClass().getName()
+ + ": Invalid or missing path: " + e);
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+ return moveTo(srcPath, parent, newName);
+ }
+ else if (ACTION_IS_FILE_SYSTEM_ROOT.equals(action)) {
+ return new PluginResult(PluginResult.Status.OK,
+ isFileSystemRoot(args.optString(0)));
+ }
+
+ // invalid action
+ return new PluginResult(PluginResult.Status.INVALID_ACTION,
+ "File: invalid action " + action);
+ }
+
+ /**
+ * Reads a file and encodes the contents using the specified encoding.
+ *
+ * @param filePath
+ * Full path of the file to be read
+ * @param encoding
+ * Encoding to use for the file contents
+ * @return PluginResult containing encoded file contents or error code if
+ * unable to read or encode file
+ */
+ protected static PluginResult readAsText(String filePath, String encoding) {
+ PluginResult result = null;
+ String logMsg = ": encoding file contents using " + encoding;
+
+ // read the file
+ try {
+ // return encoded file contents
+ byte[] blob = FileUtils.readFile(filePath, Connector.READ);
+ result = new PluginResult(PluginResult.Status.OK,
+ new String(blob, encoding));
+ }
+ catch (FileNotFoundException e) {
+ logMsg = e.toString();
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+ catch (UnsupportedEncodingException e) {
+ logMsg = e.toString();
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ ENCODING_ERR);
+ }
+ catch (IOException e) {
+ logMsg = e.toString();
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_READABLE_ERR);
+ }
+ finally {
+ Logger.log(FileManager.class.getName() + ": " + logMsg);
+ }
+
+ return result;
+ }
+
+ /**
+ * Read file and return data as a base64 encoded data url. A data url is of
+ * the form: data:[<mediatype>][;base64],<data>
+ *
+ * @param filePath
+ * Full path of the file to be read
+ * @return PluginResult containing the encoded file contents or an error
+ * code if unable to read the file
+ */
+ protected static PluginResult readAsDataURL(String filePath) {
+ String data = null;
+ try {
+ // read file
+ byte[] blob = FileUtils.readFile(filePath, Connector.READ);
+
+ // encode file contents using BASE64 encoding
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ Base64OutputStream base64OutputStream = new Base64OutputStream(
+ byteArrayOutputStream);
+ base64OutputStream.write(blob);
+ base64OutputStream.flush();
+ base64OutputStream.close();
+ data = byteArrayOutputStream.toString();
+ }
+ catch (FileNotFoundException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+ catch (IOException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_READABLE_ERR);
+ }
+
+ // put result in proper form
+ String mediaType = MIMETypeAssociations.getMIMEType(filePath);
+ if (mediaType == null) {
+ mediaType = "";
+ }
+ data = "data:" + mediaType + ";base64," + data;
+
+ return new PluginResult(PluginResult.Status.OK, data);
+ }
+
+ /**
+ * Writes data to the specified file.
+ *
+ * @param filePath
+ * Full path of file to be written to
+ * @param data
+ * Data to be written
+ * @param position
+ * Position at which to begin writing
+ * @return PluginResult containing the number of bytes written or error code
+ * if unable to write file
+ */
+ protected static PluginResult writeFile(String filePath, String data, int position) {
+ PluginResult result = null;
+ int bytesWritten = 0;
+ try {
+ // write file data
+ // The default String encoding on BB is ISO-8859-1 which causes
+ // issues with extended characters. Force to UTF-8 to provide
+ // greater character support and match other platforms.
+ bytesWritten = FileUtils.writeFile(filePath, data.getBytes("UTF-8"), position);
+ result = new PluginResult(PluginResult.Status.OK, bytesWritten);
+ }
+ catch (SecurityException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NO_MODIFICATION_ALLOWED_ERR);
+ }
+ catch (IOException e) {
+ // it's not a security issue, so the directory path is either
+ // not fully created or a general error occurred
+ Logger.log(FileManager.class.getName() + ": " + e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+
+ return result;
+ }
+
+ /**
+ * Changes the length of the specified file. If shortening, data beyond new
+ * length is discarded.
+ *
+ * @param fileName
+ * The full path of the file to truncate
+ * @param size
+ * The size to which the length of the file is to be adjusted
+ * @return PluginResult containing new file size or an error code if an
+ * error occurred
+ */
+ protected static PluginResult truncateFile(String filePath, long size) {
+ long fileSize = 0;
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection) Connector.open(filePath,
+ Connector.READ_WRITE);
+ if (!fconn.exists()) {
+ Logger.log(FileManager.class.getName() + ": path not found "
+ + filePath);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+ if (size >= 0) {
+ fconn.truncate(size);
+ }
+ fileSize = fconn.fileSize();
+ }
+ catch (IOException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NO_MODIFICATION_ALLOWED_ERR);
+ }
+ finally {
+ try {
+ if (fconn != null)
+ fconn.close();
+ }
+ catch (IOException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ }
+ }
+ return new PluginResult(PluginResult.Status.OK, fileSize);
+ }
+
+ /**
+ * Returns a directory entry that represents the specified file system. The
+ * directory entry does not represent the root of the file system, but a
+ * directory within the file system that is writable. Users must provide the
+ * file system type, which can be one of FS_TEMPORARY or FS_PERSISTENT.
+ *
+ * @param type
+ * The type of file system desired.
+ * @param size
+ * The minimum size, in bytes, of space required
+ * @return a PluginResult containing a file system object for the specified
+ * file system
+ */
+ protected static PluginResult requestFileSystem(int type, long size) {
+ if (!isValidFileSystemType(type)) {
+ Logger.log(FileManager.class.getName()
+ + ": Invalid file system type: " + Integer.toString(type));
+ return new PluginResult(
+ PluginResult.Status.JSON_EXCEPTION,
+ SYNTAX_ERR);
+ }
+
+ PluginResult result = null;
+ String filePath = null;
+ switch (type) {
+ case FS_TEMPORARY:
+ // create application-specific temp directory
+ try {
+ filePath = FileUtils.createApplicationTempDirectory();
+ }
+ catch (IOException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NO_MODIFICATION_ALLOWED_ERR);
+ }
+ break;
+ case FS_PERSISTENT:
+ // get a path to SD card (if present) or user directory (internal)
+ filePath = FileUtils.getFileSystemRoot();
+ break;
+ }
+
+ // create a file system entry from the path
+ Entry entry = null;
+ try {
+ // check the file system size
+ if (size > FileUtils.availableSize(filePath)) {
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION,
+ QUOTA_EXCEEDED_ERR);
+ }
+
+ entry = getEntryFromURI(filePath);
+ }
+ catch (Exception e) {
+ // bad path (not likely)
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ ENCODING_ERR);
+ }
+
+ try {
+ JSONObject fileSystem = new JSONObject();
+ fileSystem.put("name", getFileSystemName(type));
+ fileSystem.put("root", entry.toJSONObject());
+ result = new PluginResult(PluginResult.Status.OK, fileSystem);
+ }
+ catch (JSONException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION,
+ "File systen entry JSON conversion failed.");
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a file system entry object from the specified file system URI.
+ *
+ * @param uri
+ * the full path to the file or directory on the file system
+ * @return a PluginResult containing the file system entry
+ */
+ protected static PluginResult resolveFileSystemURI(String uri) {
+ PluginResult result = null;
+ Entry entry = null;
+
+ try {
+ entry = getEntryFromURI(uri);
+ }
+ catch (IllegalArgumentException e) {
+ return new PluginResult(
+ PluginResult.Status.JSON_EXCEPTION,
+ ENCODING_ERR);
+ }
+
+ if (entry == null) {
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+ else {
+ result = new PluginResult(PluginResult.Status.OK,
+ entry.toJSONObject());
+ }
+ return result;
+ }
+
+ /**
+ * Retrieve metadata for file or directory specified by path.
+ *
+ * @param path
+ * full path name of the file or directory
+ * @return PluginResult containing metadata for file system entry or an
+ * error code if unable to retrieve metadata
+ */
+ protected static PluginResult getMetadata(String path) {
+ PluginResult result = null;
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection)Connector.open(path);
+ if (fconn.exists()) {
+ long lastModified = fconn.lastModified();
+ result = new PluginResult(PluginResult.Status.OK, lastModified);
+ }
+ else {
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+ }
+ catch (IllegalArgumentException e) {
+ // bad path
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+ catch (IOException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ result = new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ e.getMessage());
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a listing of the specified directory contents. Names of both
+ * files and directories are returned.
+ *
+ * @param path
+ * full path name of directory
+ * @return PluginResult containing list of file and directory names
+ * corresponding to directory contents
+ */
+ protected static PluginResult listDirectory(String path) {
+ Enumeration listing = null;
+ try {
+ listing = FileUtils.listDirectory(path);
+ }
+ catch (Exception e) {
+ // bad path
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ NOT_FOUND_ERR);
+ }
+
+ // pass directory contents back as an array of Strings (names)
+ JSONArray array = new JSONArray();
+ while (listing.hasMoreElements()) {
+ array.add((String)listing.nextElement());
+ }
+
+ return new PluginResult(PluginResult.Status.OK, array.toString());
+ }
+
+ /**
+ * Copies a file or directory to a new location. If copying a directory, the
+ * entire contents of the directory are copied recursively.
+ *
+ * @param srcPath
+ * the full path of the file or directory to be copied
+ * @param parent
+ * the full path of the target directory to which the file or
+ * directory should be copied
+ * @param newName
+ * the new name of the file or directory
+ * @return PluginResult containing an Entry object representing the new
+ * entry, or an error code if an error occurs
+ */
+ protected static PluginResult copyTo(String srcPath, String parent, String newName) {
+ try {
+ FileUtils.copy(srcPath, parent, newName);
+ }
+ catch (IllegalArgumentException e) {
+ Logger.log(FileManager.class.getName() + ": " + e.getMessage());
+ return new PluginResult(
+ PluginResult.Status.JSON_EXCEPTION, ENCODING_ERR);
+ }
+ catch (FileNotFoundException e) {
+ Logger.log(FileManager.class.getName() + ": " + e.getMessage());
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR);
+ }
+ catch (SecurityException e) {
+ Logger.log(FileManager.class.getName() + ": " + e.getMessage());
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION, SECURITY_ERR);
+ }
+ catch (IOException e) {
+ Logger.log(FileManager.class.getName() + ": " + e.getMessage());
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION, INVALID_MODIFICATION_ERR);
+ }
+
+ return resolveFileSystemURI(getFullPath(parent, newName));
+ }
+
+ /**
+ * Moves a file or directory to a new location. If moving a directory, the
+ * entire contents of the directory are moved recursively.
+ * <p>
+ * It is an error to try to: move a directory inside itself; move a
+ * directory into its parent unless the name has changed; move a file to a
+ * path occupied by a directory; move a directory to a path occupied by a
+ * file; move any element to a path occupied by a directory that is not
+ * empty.
+ * </p>
+ * <p>
+ * A move of a file on top of an existing file must attempt to delete and
+ * replace that file. A move of a directory on top of an existing empty
+ * directory must attempt to delete and replace that directory.
+ * </p>
+ *
+ * @param srcPath
+ * the full path of the file or directory to be moved
+ * @param parent
+ * the full path of the target directory to which the file or
+ * directory should be copied
+ * @param newName
+ * the new name of the file or directory
+ * @return PluginResult containing an Entry object representing the new
+ * entry, or an error code if an error occurs
+ */
+ protected static PluginResult moveTo(String srcPath, String parent, String newName) {
+
+ // check paths
+ if (parent == null || newName == null) {
+ Logger.log(FileManager.class.getName() + ": Parameter cannot be null.");
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR);
+ }
+ else if (!parent.endsWith(FileUtils.FILE_SEPARATOR)) {
+ parent += FileUtils.FILE_SEPARATOR;
+ }
+
+ // Rules:
+ // 1 - file replace existing file ==> OK
+ // 2 - directory replace existing EMPTY directory ==> OK
+ // 3 - file replace existing directory ==> NO!
+ // 4 - directory replace existing file ==> NO!
+ // 5 - ANYTHING replace non-empty directory ==> NO!
+ //
+ // The file-to-directory and directory-to-file checks are performed in
+ // the copy operation (below). In addition, we check the destination
+ // path to see if it is a directory that is not empty. Also, if the
+ // source and target paths have the same parent directory, it is far
+ // more efficient to rename the source.
+ //
+ FileConnection src = null;
+ FileConnection dst = null;
+ try {
+ src = (FileConnection)Connector.open(srcPath, Connector.READ_WRITE);
+ if (!src.exists()) {
+ Logger.log(FileManager.class.getName() + ": Path not found: " + srcPath);
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR);
+ }
+
+ // cannot delete the destination path if it is a directory that is
+ // not empty
+ dst = (FileConnection) Connector.open(parent + newName, Connector.READ_WRITE);
+ if (dst.isDirectory() && dst.list("*", true).hasMoreElements()) {
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION, INVALID_MODIFICATION_ERR);
+ }
+
+ // simply rename if source path and parent are same directory
+ String srcURL = src.getURL();
+ String srcName = src.getName();
+ String srcDir = srcURL.substring(0, srcURL.length() - srcName.length());
+ if (srcDir.equals(parent)) {
+ // rename to itself is an error
+ if (FileUtils.stripSeparator(srcName).equals(
+ FileUtils.stripSeparator(newName))) {
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION,
+ INVALID_MODIFICATION_ERR);
+ }
+
+ // file replace file || directory replace directory ==> OK
+ // delete the existing entry
+ if (dst.exists() &&
+ ( (src.isDirectory() && dst.isDirectory()) ||
+ (!src.isDirectory() && !dst.isDirectory()) )) {
+ dst.delete();
+ }
+
+ // rename
+ src.rename(newName);
+ Entry entry = getEntryFromURI(parent + newName);
+ return new PluginResult(PluginResult.Status.OK, entry.toJSONObject());
+ }
+ }
+ catch (IllegalArgumentException e) {
+ Logger.log(FileManager.class.getName() + ": " + e);
+ return new PluginResult(
+ PluginResult.Status.JSON_EXCEPTION,
+ ENCODING_ERR);
+ }
+ catch (IOException e) {
+ // rename failed
+ Logger.log(FileManager.class.getName() + ": " + e);
+ return new PluginResult(
+ PluginResult.Status.IO_EXCEPTION,
+ INVALID_MODIFICATION_ERR);
+ }
+ finally {
+ try {
+ if (src != null) src.close();
+ if (dst != null) dst.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+
+ // There is no FileConnection API to move files and directories, so
+ // the move is a copy operation from source to destination, followed by
+ // a delete operation of the source.
+ //
+ // The following checks are made in the copy operation:
+ // * moving a directory into itself,
+ // * moving a file to an existing directory, and
+ // * moving a directory to an existing file
+ //
+ // copy source to destination
+ PluginResult result = copyTo(srcPath, parent, newName);
+
+ // if copy succeeded, delete source
+ if (result.getStatus() == PluginResult.Status.OK.ordinal()) {
+ try {
+ FileUtils.delete(srcPath);
+ }
+ catch (IOException e) {
+ // FIXME: half of move failed, but deleting either source or
+ // destination to compensate seems risky
+ Logger.log(FileManager.class.getName()
+ + ": Failed to delete source directory during move operation.");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a file system entry for the file or directory located at the
+ * specified path.
+ *
+ * @param filePath
+ * full path name of an entry on the file system
+ * @return a file system entry corresponding to the file path, or
+ * <code>null</code> if the path is invalid or does not exist on the
+ * file system
+ * @throws IllegalArgumentException
+ * is the file path is invalid
+ * @throws IOException
+ */
+ protected static Entry getEntryFromURI(String filePath)
+ throws IllegalArgumentException {
+ // check for bogus path
+ String path = (filePath == null) ? null : filePath.trim();
+ if (path == null || path.length() < 1) {
+ throw new IllegalArgumentException("Invalid URI.");
+ }
+
+ // create a file system entry
+ Entry entry = null;
+ if (path.startsWith(FileUtils.LOCAL_PROTOCOL)) {
+ entry = getEntryFromLocalURI(filePath);
+ }
+ else {
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection) Connector.open(path);
+ if (fconn.exists()) {
+ // create a new Entry
+ entry = new Entry();
+ entry.setDirectory(fconn.isDirectory());
+ entry.setName(FileUtils.stripSeparator(fconn.getName()));
+ entry.setFullPath(FileUtils.stripSeparator(path));
+ }
+ }
+ catch (IOException e) {
+ Logger.log(FileManager.class.getName() + ": " + e.getMessage());
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ return entry;
+ }
+
+ /**
+ * Creates a file system entry for a resource contained in the packaged
+ * application. Use this method if the specified path begins with
+ * <code>local:///</code> protocol.
+ *
+ * @param localPath
+ * the path of the application resource
+ * @return a file system entry corresponding to the local path, or
+ * <code>null</code> if a resource does not exist at the specified
+ * path
+ */
+ private static Entry getEntryFromLocalURI(String localPath) {
+ // Remove local:// from filePath but leave a leading /
+ String path = localPath.substring(8);
+ Entry entry = null;
+ if (FileUtils.FILE_SEPARATOR.equals(path)
+ || Application.class.getResourceAsStream(path) != null) {
+ entry = new Entry();
+ entry.setName(path.substring(1));
+ entry.setFullPath(localPath);
+ }
+ return entry;
+ }
+
+ /**
+ * Tests whether the specified file system type is valid.
+ *
+ * @param type
+ * file system type
+ * @return true if file system type is valid
+ */
+ protected static boolean isValidFileSystemType(int type) {
+ return (type == FS_TEMPORARY || type == FS_PERSISTENT);
+ }
+
+ /**
+ * Determines if the specified path is the root path of a file system.
+ *
+ * @param path
+ * full path
+ * @return true if the path is the root path of a file system
+ */
+ protected static boolean isFileSystemRoot(String path) {
+ if (path == null) {
+ return false;
+ }
+
+ if (!path.endsWith(FileUtils.FILE_SEPARATOR)) {
+ path += FileUtils.FILE_SEPARATOR;
+ }
+
+ boolean isRoot = false;
+ Enumeration e = FileSystemRegistry.listRoots();
+ while (e.hasMoreElements()) {
+ String root = "file:///" + (String) e.nextElement();
+ if (root.equals(path)) {
+ isRoot = true;
+ break;
+ }
+ }
+
+ return (isRoot || path.equals(FileUtils.getApplicationTempDirPath()));
+ }
+
+ /**
+ * Retrieves the name for the specified file system type.
+ *
+ * @param type
+ * file system type
+ * @return file system name
+ */
+ protected static String getFileSystemName(int type) {
+ String name = null;
+ switch (type) {
+ case FS_TEMPORARY:
+ name = "temporary";
+ break;
+ case FS_PERSISTENT:
+ name = "persistent";
+ break;
+ }
+ return name;
+ }
+
+ /**
+ * Returns full path from the directory and name specified.
+ *
+ * @param parent
+ * full path of the parent directory
+ * @param name
+ * name of the directory entry (can be <code>null</code>)
+ * @return full path of the file system entry
+ * @throws IllegalArgumentException
+ * if <code>parent</code> is <code>null</code>
+ */
+ public static String getFullPath(String parent, String name)
+ throws IllegalArgumentException {
+ if (parent == null) {
+ throw new IllegalArgumentException("Directory cannot be null.");
+ }
+
+ if (!parent.endsWith(FileUtils.FILE_SEPARATOR)) {
+ parent += FileUtils.FILE_SEPARATOR;
+ }
+ return (name == null) ? parent : parent + name;
+ }
+
+ /**
+ * Determines if the specified action should be run synchronously.
+ *
+ * @param action
+ * the action to perform
+ * @return true if the action should be synchronous
+ */
+ public boolean isSynch(String action) {
+ if (ACTION_IS_FILE_SYSTEM_ROOT.equals(action)) {
+ return true;
+ }
+ return super.isSynch(action);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/file/FileUtils.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/file/FileUtils.java b/framework/ext/src/org/apache/cordova/file/FileUtils.java
new file mode 100644
index 0000000..2a904ff
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/file/FileUtils.java
@@ -0,0 +1,675 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.file;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.microedition.io.Connector;
+import javax.microedition.io.file.FileConnection;
+import javax.microedition.io.file.FileSystemRegistry;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.io.FileNotFoundException;
+import net.rim.device.api.io.IOUtilities;
+import net.rim.device.api.io.MIMETypeAssociations;
+import net.rim.device.api.system.Application;
+
+/**
+ * Contains file utility methods.
+ */
+public class FileUtils {
+
+ public static final String FILE_SEPARATOR = System.getProperty("file.separator");
+ public static final String LOCAL_PROTOCOL = "local:///";
+
+ private static final String APP_TMP_DIR = "tmp" + CordovaExtension.getAppID();
+
+ /**
+ * Reads file as byte array.
+ * @param filePath Full path of the file to be read
+ * @param mode One of Connector.READ, READ_WRITE, WRITE
+ * @return file content as a byte array
+ */
+ public static byte[] readFile(String filePath, int mode) throws FileNotFoundException, IOException {
+ byte[] blob = null;
+ DataInputStream dis = null;
+ try {
+ dis = openDataInputStream(filePath, mode);
+ blob = IOUtilities.streamToBytes(dis);
+ }
+ finally {
+ try {
+ if (dis != null) dis.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return blob;
+ }
+
+ /**
+ * Utility function to open a DataInputStream from a file path.
+ *
+ * A file can be referenced with the following protocols:
+ * - System.getProperty("fileconn.dir.*")
+ * - local:/// references files bundled with the application
+ *
+ * @param filePath The full path to the file to open
+ * @param mode One of Connector.READ, READ_WRITE, WRITE
+ * @return Handle to the DataInputStream
+ */
+ private static DataInputStream openDataInputStream(final String filePath, int mode) throws FileNotFoundException, IOException {
+ FileConnection fconn = null;
+ DataInputStream dis = null;
+ try {
+ if (filePath.startsWith(LOCAL_PROTOCOL)) {
+ // Remove local:// from filePath but leave a leading /
+ dis = new DataInputStream(Application.class.getResourceAsStream(filePath.substring(8)));
+ }
+ else {
+ fconn = (FileConnection)Connector.open(filePath, mode);
+ if (!fconn.exists()) {
+ throw new FileNotFoundException(filePath + " not found");
+ }
+ dis = fconn.openDataInputStream();
+ }
+
+ if (dis == null) {
+ throw new FileNotFoundException(filePath + " not found");
+ }
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+
+ return dis;
+ }
+
+ /**
+ * Writes data to the specified file.
+ *
+ * @param filePath
+ * Full path of file to be written to
+ * @param data
+ * Data to be written
+ * @param position
+ * Position at which to begin writing
+ * @return length of data written to file
+ * @throws SecurityException
+ * if the application does not have write access to the file
+ * @throws IOException
+ * if directory structure does not exist or an unspecified error
+ * occurs
+ */
+ public static int writeFile(String filePath, byte[] data, int position)
+ throws SecurityException, IOException {
+ FileConnection fconn = null;
+ OutputStream os = null;
+ try {
+ fconn = (FileConnection) Connector.open(filePath,
+ Connector.READ_WRITE);
+ if (!fconn.exists()) {
+ fconn.create();
+ } else {
+ // Originally, this did an overwrite in place and did not
+ // truncate. The truncate was added to match behavior on
+ // other platforms.
+ fconn.truncate(position);
+ }
+ os = fconn.openOutputStream(position);
+ os.write(data);
+ }
+ finally {
+ try {
+ if (os != null)
+ os.close();
+ if (fconn != null)
+ fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return data.length;
+ }
+
+ /**
+ * Deletes the specified file or directory from file system. If the
+ * specified path is a directory, the deletion is recursive.
+ *
+ * @param path
+ * full path of file or directory to be deleted
+ * @throws IOException
+ */
+ public static void delete(String path) throws IOException {
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection)Connector.open(path, Connector.READ_WRITE);
+ if (fconn.exists()) {
+ // file
+ if (!fconn.isDirectory()) {
+ fconn.delete();
+ Logger.log(FileUtils.class.getName() + ": " + path + " deleted");
+ }
+ // directory
+ else {
+ if (!path.endsWith(FILE_SEPARATOR)) {
+ path += FILE_SEPARATOR;
+ }
+
+ // recursively delete directory contents
+ Enumeration contents = fconn.list("*", true);
+ if (contents.hasMoreElements()) {
+ fconn.close();
+ while (contents.hasMoreElements()) {
+ delete(path + contents.nextElement().toString());
+ }
+ fconn = (FileConnection)Connector.open(path, Connector.READ_WRITE);
+ }
+ // now delete this directory
+ fconn.delete();
+ Logger.log(FileUtils.class.getName() + ": " + path + " deleted");
+ }
+ }
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Creates a directory. Directories in the specified path are not created
+ * recursively. If the directory already exists, no action is taken.
+ *
+ * @param dirPath
+ * full path of directory to create
+ * @throws IOException
+ * if the target file system is not accessible, or an
+ * unspecified error occurs
+ */
+ public static void mkdir(String dirPath) throws IOException {
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection)Connector.open(dirPath);
+ if (fconn.isDirectory()) {
+ // nothing to do
+ return;
+ }
+ fconn.mkdir();
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Copies a file or directory to a new location. If copying a directory, the
+ * entire contents of the directory are copied recursively.
+ *
+ * @param srcPath
+ * the full path of the file or directory to be copied
+ * @param parent
+ * the full path of the target directory to which the file or
+ * directory should be copied
+ * @param newName
+ * the new name of the file or directory
+ * @throws IllegalArgumentException
+ * if an invalid source or destination path is provided
+ * @throws FileNotFoundException
+ * if the source path cannot be found on the file system
+ * @throws SecurityException
+ * if unable to create the new file or directory specified by
+ * destination path
+ * @throws IOException
+ * if an attempt is made to copy the contents of a directory
+ * into itself, or if the source and destination paths are
+ * identical, or if a general error occurs
+ */
+ public static void copy(String srcPath, String parent, String newName)
+ throws IllegalArgumentException, FileNotFoundException,
+ SecurityException, IOException {
+
+ FileConnection src = null;
+ FileConnection dst = null;
+ try {
+ src = (FileConnection)Connector.open(srcPath, Connector.READ_WRITE);
+
+ // ensure source exists
+ if (!src.exists()) {
+ throw new FileNotFoundException("Path not found: " + srcPath);
+ }
+
+ // ensure target parent directory exists
+ if (!isDirectory(parent)) {
+ throw new FileNotFoundException("Target directory not found: " + parent);
+ }
+
+ // form full destination path
+ if (!parent.endsWith(FileUtils.FILE_SEPARATOR)) {
+ parent += FileUtils.FILE_SEPARATOR;
+ }
+ String dstPath = parent + newName;
+
+ // source is a directory
+ if (src.isDirectory()) {
+ // target should also be directory; append file separator
+ if (!dstPath.endsWith(FILE_SEPARATOR)) {
+ dstPath += FILE_SEPARATOR;
+ }
+
+ // can't copy directory into itself
+ // file:///SDCard/tmp/ --> file:///SDCard/tmp/tmp/ ==> NO!
+ // file:///SDCard/tmp/ --> file:///SDCard/tmp/ ==> NO!
+ // file:///SDCard/tmp/ --> file:///SDCard/tmp2/ ==> OK
+ String srcURL = src.getURL();
+ if (dstPath.startsWith(srcURL)) {
+ throw new IOException("Cannot copy directory into itself.");
+ }
+
+ // create the destination directory
+ mkdir(dstPath);
+
+ // recursively copy directory contents
+ Enumeration contents = src.list("*", true);
+ if (contents.hasMoreElements()) {
+ src.close();
+ while (contents.hasMoreElements()) {
+ String name = contents.nextElement().toString();
+ copy(srcURL + name, dstPath, name);
+ }
+ }
+ }
+ // source is a file
+ else {
+ // can't copy file onto itself
+ if (dstPath.equals(srcPath)) {
+ throw new IOException("Cannot copy file onto itself.");
+ }
+
+ dst = (FileConnection) Connector.open(dstPath, Connector.READ_WRITE);
+
+ // replace existing file, but not directory
+ if (dst.exists()) {
+ if (dst.isDirectory()) {
+ throw new IOException(
+ "Cannot overwrite existing directory.");
+ }
+ else {
+ dst.delete();
+ }
+ }
+ dst.create();
+
+ // copy the contents - wish there was a better way
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ is = src.openInputStream();
+ os = dst.openOutputStream();
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ os.write(buf, 0, len);
+ }
+ }
+ finally {
+ if (is != null) is.close();
+ if (os != null) os.close();
+ }
+ }
+ }
+ finally {
+ try {
+ if (src != null) src.close();
+ if (dst != null) dst.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Creates an temporary directory for the application. The temporary
+ * directory is created in the following location:
+ * <code><root>/tmpGUID/</code> where <code><root>/</code>
+ * is the path of the writable directory on the file system (could be the SD
+ * card, if present, or the root file system on internal storage); and
+ * <code>tmpGUID/</code> is a application temporary directory that is
+ * created using the unique application GUID. If the application temporary
+ * directory does not exist, invoking this method will create it.
+ * <em>NOTE:</em> The <code><root>/tmpGUID/</code> application
+ * temporary directory and all its contents are deleted upon application
+ * exit.
+ *
+ * @return full path name of the application temporary directory
+ * @throws IOException
+ * if there are no file systems mounted, or an unspecified error
+ * occurs
+ */
+ public static String createApplicationTempDirectory() throws IOException {
+ // <root>/tmpGUID/
+ String tmpDir = getApplicationTempDirPath();
+ mkdir(tmpDir);
+
+ return tmpDir;
+ }
+
+ /**
+ * Creates a temporary directory on the writable storage area of the file
+ * system. The temporary directory is created in the following location:
+ * <code><root>/tmpGUID/dirName/</code> where
+ * <code><root>/tmpGUID/</code> is an application temporary
+ * directory that is created using the unique application GUID; and
+ * <code>dirName/</code> is an optional directory name to create beneath the
+ * application temporary directory. If the application temporary directory
+ * does not exist, invoking this method will create it. <em>NOTE:</em> The
+ * <code><root>/tmpGUID/</code> application temporary directory
+ * and all its contents are deleted upon application exit.
+ *
+ * @param dirName
+ * name of directory to be created beneath the application
+ * temporary directory
+ * @return full path name of the directory that was created
+ * @throws IOException
+ * if there are no file systems mounted, or an unspecified error
+ * occurs
+ */
+ public static String createTempDirectory(String dirName) throws IOException {
+ // create the application temp directory
+ String tmpDir = createApplicationTempDirectory();
+
+ // create specified sub-directory as "<root>/tmpGUID/dirName/"
+ dirName = (dirName == null) ? "" : dirName.trim();
+ if (dirName.length() > 0) {
+ if (!dirName.endsWith(FILE_SEPARATOR)) {
+ dirName += FILE_SEPARATOR;
+ }
+ tmpDir += dirName;
+ mkdir(tmpDir);
+ }
+ return tmpDir;
+ }
+
+ /**
+ * Attempts to delete the application temporary directory and all contents.
+ * The application temporary directory is:
+ * <code><root>/tmpGUID/</code>, where <code><root></code> is
+ * the file system root (could be the SD card or internal storage); and
+ * <code>tmpGUID</code> is the application temporary directory that is
+ * created using the unique application GUID. <em>NOTE:</em> The
+ * <code>tmpGUID</code> application temporary directory and all
+ * sub-directories are deleted upon application exit.
+ *
+ * @throws IOException
+ * if an unspecified error occurs
+ */
+ public synchronized static void deleteApplicationTempDirectory()
+ throws IOException {
+ String tmpDir = getApplicationTempDirPath();
+ delete(tmpDir);
+ }
+
+ /**
+ * Returns the full path of the application temporary directory. The path
+ * points to the following location: <code><root>/tmpGUID/</code>
+ * where <code><root>/</code> is the path of the writable directory on
+ * the file system (could be the SD card, if present, or the root file system
+ * on internal storage); and <code>tmpGUID/</code> is a application temporary
+ * directory that is created using the unique application GUID. The
+ * directory may not exist. Invoke
+ * <code>createApplicationTempDirectory</code> to create it.
+ *
+ * @return the full path name of the application temporary directory
+ */
+ public static String getApplicationTempDirPath() {
+ return getFileSystemRoot() + APP_TMP_DIR + FILE_SEPARATOR;
+ }
+
+ /**
+ * Returns the full path of a root file system. Will return the path of the
+ * SD card first, if it exists, or the root file system located on internal
+ * storage.
+ *
+ * @return full path that can be used to store files
+ */
+ public static String getFileSystemRoot() {
+ String root = null;
+ String sdcard = getSDCardPath();
+
+ // retrieve root list
+ Enumeration e = FileSystemRegistry.listRoots();
+ while (e.hasMoreElements()) {
+ root = "file:///" + (String) e.nextElement();
+ // system directory won't be writable
+ if (root.endsWith("system/")) {
+ continue;
+ }
+ // prefer the SDCard
+ else if (root.equals(sdcard)) {
+ break;
+ }
+ }
+ return root;
+ }
+
+ /**
+ * Returns the full path name to external storage (SD card, e.g.
+ * file:///SDCard/).
+ *
+ * @return full path name to the external storage (SD card)
+ */
+ public static String getSDCardPath() {
+ return System.getProperty("fileconn.dir.memorycard");
+ }
+
+ /**
+ * Returns the full path name of the user directory located on internal
+ * storage (e.g. file:///store/home/user/).
+ *
+ * @return full path name of the user directory
+ */
+ public static String getUserPath() {
+ // grab the music folder
+ String musicDir = System.getProperty("fileconn.dir.music");
+ // ignore trailing '/'
+ int i = musicDir.lastIndexOf('/', musicDir.length() - 2);
+ // strip off the last directory
+ return musicDir.substring(0, i + 1);
+ }
+
+ /**
+ * Returns the available size of the file system that the path resides on.
+ *
+ * @param path
+ * full path of a file system entry
+ * @return available size, in bytes, of the root file system
+ * @throws IllegalArgumentException
+ * if path is invalid
+ * @throws IOException
+ * if an error occurs
+ */
+ public static long availableSize(String path)
+ throws IllegalArgumentException, IOException {
+ long availableSize = 0;
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection) Connector.open(path);
+ availableSize = fconn.availableSize();
+ }
+ finally {
+ try {
+ if (fconn != null)
+ fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return availableSize;
+ }
+
+ /**
+ * Determines if the specified file system path exists.
+ * @param path full path of file or directory
+ * @return true if the file or directory exists
+ */
+ public static boolean exists(String path) {
+ boolean exists = false;
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection)Connector.open(path);
+ exists = fconn.exists();
+ }
+ catch (IllegalArgumentException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ }
+ catch (IOException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return exists;
+ }
+
+ /**
+ * Determines if the specified file system path refers to a directory.
+ * @param path full path of file or directory
+ * @return true if the file path exists, is accessible, and is a directory
+ */
+ public static boolean isDirectory(String path) {
+ boolean isDirectory = false;
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection)Connector.open(path);
+ isDirectory = fconn.isDirectory();
+ }
+ catch (IllegalArgumentException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ }
+ catch (IOException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return isDirectory;
+ }
+
+ /**
+ * Lists the contents of a directory. Lists both files and sub-directories.
+ *
+ * @param path
+ * full path of the directory to list
+ * @return Enumeration containing names of files and sub-directories.
+ * @throws FileNotFoundException
+ * if path is not found
+ * @throws IOException
+ * if an error occurs
+ */
+ public static Enumeration listDirectory(String path)
+ throws FileNotFoundException, IOException {
+ FileConnection fconn = null;
+ Enumeration listing = null;
+ try {
+ fconn = (FileConnection) Connector.open(path);
+ if (!fconn.exists()) {
+ throw new FileNotFoundException(path + " does not exist.");
+ }
+ listing = fconn.list();
+ }
+ finally {
+ try {
+ if (fconn != null)
+ fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return listing;
+ }
+
+ public static File getFileProperties(String filePath) throws FileNotFoundException {
+ File file = new File(stripSeparator(filePath));
+ FileConnection fconn = null;
+ try {
+ fconn = (FileConnection)Connector.open(filePath);
+ if (!fconn.exists()) {
+ throw new FileNotFoundException();
+ }
+ file.setLastModifiedDate(fconn.lastModified());
+ file.setName(stripSeparator(fconn.getName()));
+ file.setType(MIMETypeAssociations.getMIMEType(filePath));
+ file.setSize(fconn.fileSize());
+ }
+ catch (IllegalArgumentException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ }
+ catch (IOException e) {
+ Logger.log(FileUtils.class.getName() + ": " + e);
+ }
+ finally {
+ try {
+ if (fconn != null) fconn.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return file;
+ }
+
+ /**
+ * Strips the trailing slash from path names.
+ *
+ * @param path
+ * full or relative path name
+ * @return formatted path (without trailing slash)
+ */
+ public static String stripSeparator(String path) {
+ int len = FILE_SEPARATOR.length();
+ while (path.endsWith(FILE_SEPARATOR)) {
+ path = path.substring(0, path.length() - len);
+ }
+ return path;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/geolocation/Geolocation.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/geolocation/Geolocation.java b/framework/ext/src/org/apache/cordova/geolocation/Geolocation.java
new file mode 100644
index 0000000..1226f48
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/geolocation/Geolocation.java
@@ -0,0 +1,372 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.geolocation;
+
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.microedition.location.Criteria;
+import javax.microedition.location.Location;
+import javax.microedition.location.LocationException;
+import javax.microedition.location.LocationProvider;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.gps.BlackBerryCriteria;
+import net.rim.device.api.gps.BlackBerryLocationProvider;
+import net.rim.device.api.gps.GPSInfo;
+
+public class Geolocation extends Plugin {
+
+ /**
+ * Possible actions.
+ */
+ protected static final int ACTION_WATCH = 0;
+ protected static final int ACTION_CLEAR_WATCH = 1;
+ protected static final int ACTION_GET_POSITION = 2;
+ protected static final int ACTION_SHUTDOWN = 3;
+
+ /**
+ * Callback ID argument index.
+ */
+ protected static final int ARG_CALLBACK_ID = 0;
+
+ /**
+ * Minimum GPS accuracy (meters).
+ */
+ protected static final float MIN_GPS_ACCURACY = 10F; // meters
+
+ /**
+ * Hash of all the listeners created, keyed on callback ids.
+ */
+ protected final Hashtable geoListeners;
+
+ /**
+ * Constructor.
+ */
+ public Geolocation() {
+ this.geoListeners = new Hashtable();
+ }
+
+ /**
+ * Executes the specified geolocation action.
+ *
+ * @param action
+ * "getCurrentPosition" - Retrieves current location.
+ * "watchPosition" - Establishes a location provider that is keyed on specified position options
+ * and attaches a listener that notifies registered callbacks of location updates.
+ * "stop" - Clears the watch identified by the watch ID that must be specified in args.
+ * "shutdown" - Stops all listeners and resets all location providers.
+ * @param callbackId callback managed by the plugin manager (ignored)
+ * @param args contains the callback id and position options
+ */
+ public PluginResult execute(String action, JSONArray args, String callbackId) {
+
+ /*
+ * The geolocation plugin bypasses the plugin callback framework for
+ * success callbacks because the current implementation of the framework
+ * deletes the callbacks after they have been called. The geolocation
+ * listener callbacks need to continue listening for location changes,
+ * and are therefore managed separately from the plugin framework.
+ *
+ * This means the invoking script must pass the listener callback ID in
+ * the args parameter (along with the position options). The callbackId
+ * parameter (used by the plugin framework) is ignored.
+ *
+ * The invoking script should still provide a failure callback so the
+ * plugin framework can handle general error reporting.
+ */
+ String listenerCallbackId;
+ try {
+ listenerCallbackId = args.getString(ARG_CALLBACK_ID);
+ } catch (JSONException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Callback ID argument is not valid.");
+ }
+
+ if (!GPSInfo.isGPSModeAvailable(GPSInfo.GPS_DEVICE_INTERNAL)){
+ return new PluginResult(GeolocationStatus.GPS_NOT_AVAILABLE);
+ }
+
+ PositionOptions options;
+ switch (getAction(action)) {
+ case ACTION_CLEAR_WATCH:
+ clearWatch(listenerCallbackId);
+ return null;
+
+ case ACTION_WATCH:
+
+ try {
+ options = PositionOptions.fromJSONArray(args);
+ } catch (NumberFormatException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "One of the position options is not a valid number.");
+ } catch (JSONException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "One of the position options is not valid JSON.");
+ }
+
+ this.watchPosition(listenerCallbackId, options);
+ return null;
+
+ case ACTION_GET_POSITION:
+
+ try {
+ options = PositionOptions.fromJSONArray(args);
+ } catch (NumberFormatException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "One of the position options is not a valid number.");
+ } catch (JSONException e) {
+ return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "One of the position options is not valid JSON.");
+ }
+
+ this.getCurrentPosition(listenerCallbackId, options);
+ return null;
+
+ case ACTION_SHUTDOWN:
+ this.shutdown();
+ return null;
+ }
+
+ return new PluginResult(PluginResult.Status.INVALID_ACTION, "Geolocation: invalid action " + action);
+ }
+
+ /**
+ * Checks if the provided location is valid.
+ * @param location
+ * @return true if the location is valid
+ */
+ protected boolean isLocationValid(Location location) {
+ return location != null && location.isValid();
+ }
+
+ /**
+ * Checks if the provided location is fresh or not.
+ * @param po position options containing maximum location age allowed
+ * @param location location object
+ * @return true if the location is newer than maximum age allowed
+ */
+ protected boolean isLocationFresh(PositionOptions po, Location location) {
+ return new Date().getTime() - location.getTimestamp() < po.maxAge;
+ }
+
+ /**
+ * Checks if the accuracy of the location is high enough.
+ * @param po position options containing high accuracy flag
+ * @param location location object
+ * @return true if the location accuracy is lower than MIN_GPS_ACCURACY
+ */
+ protected boolean isLocationAccurate(PositionOptions po, Location location) {
+ return po.enableHighAccuracy && location.getQualifiedCoordinates().getHorizontalAccuracy() < MIN_GPS_ACCURACY;
+ }
+
+ /**
+ * Retrieves a location provider with some criteria.
+ * @param po position options
+ */
+ protected static LocationProvider getLocationProvider(PositionOptions po) {
+ // configure criteria for location provider
+ // Note: being too restrictive will make it less likely that one will be returned
+ BlackBerryCriteria criteria = new BlackBerryCriteria();
+
+ // can we get GPS info from the wifi network?
+ if (GPSInfo.isGPSModeAvailable(GPSInfo.GPS_MODE_ASSIST))
+ criteria.setMode(GPSInfo.GPS_MODE_ASSIST);
+ // relies on device GPS receiver - not good indoors or if obstructed
+ else if (GPSInfo.isGPSModeAvailable(GPSInfo.GPS_MODE_AUTONOMOUS))
+ criteria.setMode(GPSInfo.GPS_MODE_AUTONOMOUS);
+
+ criteria.setAltitudeRequired(true);
+
+ // enable full power usage to increase location accuracy
+ if (po.enableHighAccuracy) {
+ criteria.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH);
+ }
+
+ // Attempt to get a location provider
+ BlackBerryLocationProvider provider;
+ try {
+ // Note: this could return an existing provider that meets above criteria
+ provider = (BlackBerryLocationProvider) LocationProvider.getInstance(criteria);
+ } catch (LocationException e) {
+ // all LocationProviders are currently permanently unavailable :(
+ provider = null;
+ }
+
+ return provider;
+ }
+
+ /**
+ * Gets the current location, then creates a location listener to receive
+ * updates. Registers the specified callback with the listener.
+ * @param callbackId callback to receive location updates
+ * @param options position options
+ */
+ protected void watchPosition(String callbackId, PositionOptions options) {
+
+ // attempt to retrieve a location provider
+ LocationProvider provider = getLocationProvider(options);
+ if (provider == null) {
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_NOT_AVAILABLE));
+ return;
+ }
+
+ // create a listener for location updates
+ GeolocationListener listener;
+ try {
+ listener = new GeolocationListener(provider, callbackId, options);
+ } catch (IllegalArgumentException e) {
+ // if interval < -1, or
+ // if (interval != -1) and
+ // (timeout > interval or maxAge > interval or
+ // (timeout < 1 and timeout != -1) or
+ // (maxAge < 1 and maxAge != -1)
+ // )
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_JSON_EXCEPTION, e.getMessage()));
+ return;
+ }
+
+ // store the listener
+ addListener(callbackId, listener);
+ }
+
+ /**
+ * Shuts down all location listeners.
+ */
+ protected synchronized void shutdown() {
+ for (Enumeration listeners = this.geoListeners.elements(); listeners.hasMoreElements(); ) {
+ GeolocationListener listener = (GeolocationListener) listeners.nextElement();
+ listener.shutdown();
+ }
+ this.geoListeners.clear();
+ }
+
+ /**
+ * Clears the watch for the specified callback id.
+ * If no more watches exist for the location provider, it is shut down.
+ * @param callbackId identifer of the listener to shutdown
+ */
+ protected void clearWatch(String callbackId) {
+ synchronized(this.geoListeners) {
+ GeolocationListener listener = (GeolocationListener) this.geoListeners.get(callbackId);
+ listener.shutdown();
+ this.geoListeners.remove(callbackId);
+ }
+ }
+
+ /**
+ * Returns a PluginResult with status OK and a JSON object representing the coords
+ * @param callbackId callback to receive the the result
+ * @param po position options
+ */
+ protected void getCurrentPosition(String callbackId, PositionOptions options) {
+
+ // Check the device for its last known location (may have come from
+ // another app on the device that has already requested a location).
+ // If it is invalid, old, or inaccurate, attempt to get a new one.
+ Location location = LocationProvider.getLastKnownLocation();
+ if (!isLocationValid(location) || !isLocationFresh(options, location) || !isLocationAccurate(options, location)) {
+ // attempt to retrieve a location provider
+ LocationProvider provider = getLocationProvider(options);
+ if (provider == null) {
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_NOT_AVAILABLE));
+ return;
+ }
+
+ try {
+ // convert timeout from millis
+ int timeout = (options.timeout > 0) ? options.timeout/1000 : -1;
+ Logger.log(this.getClass().getName() + ": retrieving location with timeout=" + timeout);
+ location = provider.getLocation(timeout);
+ } catch(LocationException e) {
+ Logger.log(this.getClass().getName() + ": " + e.getMessage());
+ provider.reset();
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_TIMEOUT));
+ return;
+ } catch (InterruptedException e) {
+ Logger.log(this.getClass().getName() + ": " + e.getMessage());
+ provider.reset();
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_INTERUPTED_EXCEPTION));
+ return;
+ }
+ }
+
+ // send the location back
+ sendLocation(callbackId, location);
+ }
+
+ /**
+ * Converts the location to a geo position and sends result to JavaScript.
+ * @param callbackId callback to receive position
+ * @param location location to send
+ */
+ protected void sendLocation(String callbackId, Location location) {
+ // convert the location to a JSON object and return it in the PluginResult
+ JSONObject position = null;
+ try {
+ position = Position.fromLocation(location).toJSONObject();
+ } catch (JSONException e) {
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(PluginResult.Status.JSON_EXCEPTION,
+ "Converting the location to a JSON object failed"));
+ return;
+ }
+
+ // invoke the geolocation callback
+ CordovaExtension.invokeSuccessCallback(callbackId,
+ new GeolocationResult(GeolocationResult.Status.OK, position));
+ }
+
+ /**
+ * Returns action to perform.
+ * @param action
+ * @return action to perform
+ */
+ protected static int getAction(String action) {
+ if ("watchPosition".equals(action)) return ACTION_WATCH;
+ if ("stop".equals(action)) return ACTION_CLEAR_WATCH;
+ if ("getCurrentPosition".equals(action)) return ACTION_GET_POSITION;
+ if ("shutdown".endsWith(action)) return ACTION_SHUTDOWN;
+ return -1;
+ }
+
+ /**
+ * Adds a location listener.
+ * @param callbackId callback to receive listener updates
+ * @param listener location listener
+ */
+ protected synchronized void addListener(String callbackId, GeolocationListener listener) {
+ this.geoListeners.put(callbackId, listener);
+ }
+
+ /**
+ * Called when Plugin is destroyed.
+ */
+ public void onDestroy() {
+ this.shutdown();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/geolocation/GeolocationListener.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/geolocation/GeolocationListener.java b/framework/ext/src/org/apache/cordova/geolocation/GeolocationListener.java
new file mode 100644
index 0000000..58ef4f0
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/geolocation/GeolocationListener.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.geolocation;
+
+import javax.microedition.location.Location;
+import javax.microedition.location.LocationListener;
+import javax.microedition.location.LocationProvider;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+import org.apache.cordova.util.Logger;
+
+/**
+ * GeolocationListener listens for update notifications from a LocationProvider.
+ * Provides location update notifications to registered callback.
+ */
+public final class GeolocationListener implements LocationListener {
+
+ private LocationProvider locationProvider; // location provider the listener listens to
+ private String callbackId; // callback that is to be notified on location updates
+
+ /**
+ * Creates a new listener that attaches itself to the specified LocationProvider.
+ * @param locationProvider location provider that listener will attach to
+ * @param callbackId callback to receive location updates
+ * @param options position options
+ */
+ public GeolocationListener(LocationProvider locationProvider, String callbackId, PositionOptions options) {
+ this.locationProvider = locationProvider;
+ this.callbackId = callbackId;
+
+ // Add this as a location listener to the provider. Updates are received
+ // at the specified interval. This is where it gets confusing:
+ // the setLocationListener method takes three parameters: interval, timeout,
+ // and maxAge. The listener only seems to work if all three are the same
+ // value, which is probably best, since neither timeout nor maxAge can be
+ // larger than interval. Also, the actual timeout to wait for a valid
+ // location is [interval + timeout]. (I told you it was confusing).
+ // So, we do the only thing we can do, which is to divide the user timeout
+ // in half, and set it to the interval and timeout values. This will give
+ // us the correct timeout value. BTW, this is exactly what RIM does in
+ // their HTML5 implementation in the 6.0 browser. Try it :)
+ int seconds = (options.timeout > 0) ? options.timeout/2000 : 1; // half and convert to millis
+ this.locationProvider.setLocationListener(this,
+ seconds, // interval - seconds between location updates
+ seconds, // timeout - additional time to wait for update
+ seconds); // maxage - maximum age of location
+ }
+
+ /**
+ * Updated when location changes.
+ */
+ public void locationUpdated(LocationProvider provider, Location location) {
+ if (location.isValid()) {
+ Logger.log(this.getClass().getName() + ": updated with valid location");
+ this.updateLocation(location);
+ } else {
+ // This just means we couldn't get a valid location within the listener interval.
+ Logger.log(this.getClass().getName() + ": updated with invalid location");
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_TIMEOUT));
+ }
+ }
+
+ /**
+ * Updated when provider state changes.
+ */
+ public void providerStateChanged(LocationProvider provider, int newState) {
+ switch (newState) {
+ case LocationProvider.AVAILABLE:
+ Logger.log(this.getClass().getName() + ": provider state changed to AVAILABLE");
+ break;
+ case LocationProvider.OUT_OF_SERVICE:
+ Logger.log(this.getClass().getName() + ": provider state changed to OUT_OF_SERVICE");
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_OUT_OF_SERVICE));
+ this.shutdown();
+ break;
+ case LocationProvider.TEMPORARILY_UNAVAILABLE:
+ Logger.log(this.getClass().getName() + ": provider state changed to TEMPORARILY_UNAVAILABLE");
+ // This is what happens when you are inside
+ // TODO: explore possible ways to recover
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.GPS_TEMPORARILY_UNAVAILABLE));
+ this.shutdown();
+ break;
+ }
+ }
+
+ /**
+ * Shuts down the listener by resetting the location provider.
+ */
+ public void shutdown() {
+ Logger.log(this.getClass().getName() + ": resetting location provider for callback '" + callbackId + "'");
+ this.locationProvider.setLocationListener(null, 0, 0, 0);
+ this.locationProvider.reset();
+ }
+
+ /**
+ * Notifies callbacks of location updates.
+ * @param location updated location
+ */
+ protected void updateLocation(Location location) {
+ JSONObject position = null;
+ try {
+ position = Position.fromLocation(location).toJSONObject();
+ } catch (JSONException e) {
+ CordovaExtension.invokeErrorCallback(callbackId,
+ new GeolocationResult(PluginResult.Status.JSON_EXCEPTION, "Converting the location to a JSON object failed"));
+ }
+
+ CordovaExtension.invokeSuccessCallback(callbackId,
+ new GeolocationResult(GeolocationStatus.OK, position));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/geolocation/GeolocationResult.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/geolocation/GeolocationResult.java b/framework/ext/src/org/apache/cordova/geolocation/GeolocationResult.java
new file mode 100644
index 0000000..9969701
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/geolocation/GeolocationResult.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.geolocation;
+
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONObject;
+import org.apache.cordova.util.Logger;
+
+/**
+ * Extends PluginResult for the purposes of overriding the success and error
+ * callback invocations.
+ */
+public class GeolocationResult extends PluginResult {
+
+ /**
+ * Constructor.
+ * @param status
+ */
+ public GeolocationResult(Status status) {
+ super(status);
+ }
+
+ /**
+ * Constructor.
+ * @param status
+ * @param message
+ */
+ public GeolocationResult(Status status, String message) {
+ super(status, message);
+ }
+
+ /**
+ * Constructor.
+ * @param status
+ * @param message
+ */
+ public GeolocationResult(Status status, JSONObject message) {
+ super(status, message);
+ }
+
+ /**
+ * Produces the invocation string for the specified geolocation success callback.
+ * @param callbackId callback identifier
+ */
+ public String toSuccessCallbackString(String callbackId) {
+ Logger.log(this.getClass().getName() + ": invoking success callback: " + callbackId + ", with args: " + this.getJSONString());
+ return "try { navigator.geolocation.success('"+callbackId+"', " + this.getJSONString() + "); } catch(e) { alert('error in success callback:' + e.message); }";
+ }
+
+ /**
+ * Produces the invocation string for the specified geolocation error callback.
+ * @param callbackId callback identifier
+ */
+ public String toErrorCallbackString(String callbackId) {
+ return "try { navigator.geolocation.fail('"+callbackId+"', " + this.getJSONString() + "); } catch(e) { alert('error in error callback:' + e.message); }";
+ }
+}