diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 59401348..acef0497 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -316,7 +316,9 @@ PhoneGap.stringify = function(args) { s = s + '}'; } else { - s = s + '"' + args[i] + '"'; + var a = args[i].replace(/\\/g, '\\\\'); + a = a.replace(/"/g, '\\"'); + s = s + '"' + a + '"'; } } s = s + "]"; diff --git a/framework/assets/js/storage.js b/framework/assets/js/storage.js old mode 100644 new mode 100755 index ba234035..fe299a87 --- a/framework/assets/js/storage.js +++ b/framework/assets/js/storage.js @@ -5,71 +5,175 @@ * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required */ +/** + * Storage object that is called by native code when performing queries. + * PRIVATE METHOD + */ var DroidDB = function() { - this.txQueue = []; + this.txQueue = {}; }; +/** + * Callback from native code when result from a query is available. + * PRIVATE METHOD + * + * @param rawdata JSON string of the row data + * @param tx_id Transaction id + */ DroidDB.prototype.addResult = function(rawdata, tx_id) { - eval("var data = " + rawdata); - var tx = this.txQueue[tx_id]; - tx.resultSet.push(data); + try { + eval("var data = " + rawdata + ";"); + var tx = this.txQueue[tx_id]; + tx.resultSet.push(data); + } catch (e) { + console.log("DroidDB.addResult(): Error="+e); + } }; +/** + * Callback from native code when query is complete. + * PRIVATE METHOD + * + * @param tx_id + */ DroidDB.prototype.completeQuery = function(tx_id) { - var tx = this.txQueue[tx_id]; - var r = new result(); - r.rows.resultSet = tx.resultSet; - r.rows.length = tx.resultSet.length; - tx.win(r); + var tx = null; + try { + tx = this.txQueue[tx_id]; + var r = new DroidDB_Result(); + r.rows.resultSet = tx.resultSet; + r.rows.length = tx.resultSet.length; + delete this.txQueue[tx_id]; + } catch (e) { + console.log("DroidDB.completeQuery(): Error="+e); + } + try { + tx.successCallback(tx, r); + } catch (e) { + console.log("DroidDB.completeQuery(): Error calling user success callback="+e); + } }; +/** + * Callback from native code when query fails + * PRIVATE METHOD + * + * @param reason + * @param tx_id + */ DroidDB.prototype.fail = function(reason, tx_id) { - var tx = this.txQueue[tx_id]; - tx.fail(reason); + var tx = null; + try { + tx = this.txQueue[tx_id]; + delete this.txQueue[tx_id]; + } catch (e) { + console.log("DroidDB.fail(): Error="+e); + } + try { + tx.errorCallback(reason); + } catch (e) { + console.log("DroidDB.fail(): Error calling user error callback="+e); + } }; var DatabaseShell = function() { }; +/** + * Start a transaction. + * + * @param process {Function} The transaction function + */ DatabaseShell.prototype.transaction = function(process) { - tx = new Tx(); + var tx = new DroidDB_Tx(); process(tx); }; -var Tx = function() { - droiddb.txQueue.push(this); - this.id = droiddb.txQueue.length - 1; +/** + * Transaction object + * PRIVATE METHOD + */ +var DroidDB_Tx = function() { + + // Set the id of the transaction + this.id = PhoneGap.createUUID(); + + // Add this transaction to the queue + droiddb.txQueue[this.id] = this; + + // Init result this.resultSet = []; }; -Tx.prototype.executeSql = function(query, params, win, fail) { +/** + * Execute SQL statement + * + * @param query + * @param params + * @param successCallback + * @param errorCallback + */ +DroidDB_Tx.prototype.executeSql = function(query, params, successCallback, errorCallback) { + + // Init params array + if (typeof params == 'undefined') { + params = []; + } + + // Save callbacks + var tx = droiddb.txQueue[this.id]; + tx.successCallback = successCallback; + tx.errorCallback = errorCallback; + + // Call native code PhoneGap.execAsync(null, null, "Storage", "executeSql", [query, params, this.id]); - tx.win = win; - tx.fail = fail; }; -var result = function() { - this.rows = new Rows(); +/** + * SQL result set that is returned to user. + * PRIVATE METHOD + */ +DroidDB_Result = function() { + this.rows = new DroidDB_Rows(); }; -var Rows = function() { - this.resultSet = []; - this.length = 0; +/** + * SQL result set object + * PRIVATE METHOD + */ +DroidDB_Rows = function() { + this.resultSet = []; // results array + this.length = 0; // number of rows }; -Rows.prototype.item = function(row_id) { - return this.resultSet[id]; +/** + * Get item from SQL result set + * + * @param row The row number to return + * @return The row object + */ +DroidDB_Rows.prototype.item = function(row) { + return this.resultSet[row]; }; -var dbSetup = function(name, version, display_name, size) { +/** + * Open database + * + * @param name Database name + * @param version Database version + * @param display_name Database display name + * @param size Database size in bytes + * @return Database object + */ +DroidDB_openDatabase = function(name, version, display_name, size) { PhoneGap.execAsync(null, null, "Storage", "openDatabase", [name, version, display_name, size]); - db_object = new DatabaseShell(); - return db_object; + var db = new DatabaseShell(); + return db; }; PhoneGap.addConstructor(function() { if (typeof window.openDatabase == "undefined") { - navigator.openDatabase = window.openDatabase = dbSetup; + navigator.openDatabase = window.openDatabase = DroidDB_openDatabase; window.droiddb = new DroidDB(); } }); diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 46697229..e091cb10 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -291,15 +291,6 @@ public class DroidGap extends Activity { this.addService("Storage", "com.phonegap.Storage"); this.addService("Temperature", "com.phonegap.TempListener"); - // Add in support for storage for Android 1.X devices - if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { - System.out.println("Android 1.X device"); - - Package pack = this.getClass().getPackage(); - String appPackage = pack.getName(); - this.pluginManager.exec("Storage", "setStorage", null, "["+appPackage+"]", false); - } - } /** diff --git a/framework/src/com/phonegap/Storage.java b/framework/src/com/phonegap/Storage.java index a3339306..924b5b55 100755 --- a/framework/src/com/phonegap/Storage.java +++ b/framework/src/com/phonegap/Storage.java @@ -2,21 +2,21 @@ package com.phonegap; import org.json.JSONArray; import org.json.JSONException; - +import org.json.JSONObject; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; - import android.database.Cursor; import android.database.sqlite.*; -import android.util.Log; +/** + * This class implements the HTML5 database support for Android 1.X devices. + * It is not used for Android 2.X, since HTML5 database is built in to the browser. + */ public class Storage extends Plugin { - private static final String LOG_TAG = "SQLite Storage:"; - - SQLiteDatabase myDb; - String path; - String txid = ""; + SQLiteDatabase myDb = null; // Database object + String path = null; // Database path + String dbName = null; // Database name /** * Constructor. @@ -37,6 +37,7 @@ public class Storage extends Plugin { String result = ""; try { + // TODO: Do we want to allow a user to do this, since they could get to other app databases? if (action.equals("setStorage")) { this.setStorage(args.getString(0)); } @@ -67,57 +68,119 @@ public class Storage extends Plugin { public boolean isSynch(String action) { return false; } + + /** + * Clean up and close database. + */ + @Override + public void onDestroy() { + if (this.myDb != null) { + this.myDb.close(); + this.myDb = null; + } + } //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- + /** + * Set the application package for the database. Each application saves its + * database files in a directory with the application package as part of the file name. + * + * For example, application "com.phonegap.demo.Demo" would save its database + * files in "/data/data/com.phonegap.demo/databases/" directory. + * + * @param appPackage The application package. + */ public void setStorage(String appPackage) { - path = "/data/data/" + appPackage + "/databases/"; + this.path = "/data/data/" + appPackage + "/databases/"; } + /** + * Open database. + * + * @param db The name of the database + * @param version The version + * @param display_name The display name + * @param size The size in bytes + */ public void openDatabase(String db, String version, String display_name, long size) { - if (path != null) { - path += db + ".db"; - myDb = SQLiteDatabase.openOrCreateDatabase(path, null); + + // If database is open, then close it + if (this.myDb != null) { + this.myDb.close(); } + + // If no database path, generate from application package + if (this.path == null) { + Package pack = this.ctx.getClass().getPackage(); + String appPackage = pack.getName(); + this.setStorage(appPackage); + } + + this.dbName = this.path + db + ".db"; + this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null); } + /** + * Execute SQL statement. + * + * @param query The SQL query + * @param params Parameters for the query + * @param tx_id Transaction id + */ public void executeSql(String query, String[] params, String tx_id) { try { - txid = tx_id; - Cursor myCursor = myDb.rawQuery(query, params); - processResults(myCursor); - } catch (SQLiteException ex) { - Log.d(LOG_TAG, ex.getMessage()); - txid = ""; - this.sendJavascript("droiddb.fail(" + ex.getMessage() + "," + txid + ");"); + Cursor myCursor = this.myDb.rawQuery(query, params); + this.processResults(myCursor, tx_id); + myCursor.close(); + } + catch (SQLiteException ex) { + ex.printStackTrace(); + System.out.println("Storage.executeSql(): Error=" + ex.getMessage()); + + // Send error message back to JavaScript + this.sendJavascript("droiddb.fail('" + ex.getMessage() + "','" + tx_id + "');"); } } - public void processResults(Cursor cur) { - String key = ""; - String value = ""; - String resultString = ""; + /** + * Process query results. + * + * @param cur Cursor into query results + * @param tx_id Transaction id + */ + public void processResults(Cursor cur, String tx_id) { + + // If query result has rows if (cur.moveToFirst()) { + String key = ""; + String value = ""; int colCount = cur.getColumnCount(); + + // Build up JSON result object for each row do { - resultString = "{"; - for (int i = 0; i < colCount; ++i) { - key = cur.getColumnName(i); - value = cur.getString(i); - resultString += " \"" + key + "\" : \"" + value + "\""; - if (i != (colCount - 1)) { - resultString += ","; + JSONObject result = new JSONObject(); + try { + for (int i = 0; i < colCount; ++i) { + key = cur.getColumnName(i); + value = cur.getString(i).replace("\"", "\\\""); // must escape " with \" for JavaScript + result.put(key, value); } + + // Send row back to JavaScript + this.sendJavascript("droiddb.addResult('" + result.toString() + "','" + tx_id + "');"); + + } catch (JSONException e) { + e.printStackTrace(); } - resultString += "}"; - this.sendJavascript("droiddb.addResult('" + resultString + "', " + txid + ");"); - } while (cur.moveToNext()); - this.sendJavascript("droiddb.completeQuery(" + txid + ");"); - txid = ""; - myDb.close(); - } + + } while (cur.moveToNext()); + + // Let JavaScript know that there are no more rows + this.sendJavascript("droiddb.completeQuery('" + tx_id + "');"); + } } } \ No newline at end of file