From 410dd0d35f09dfc634fa20108e0d78ada73c4424 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Wed, 11 Apr 2012 20:34:39 +0200 Subject: [PATCH] New Android version from @marcucio with major improvements in batch processing --- Android/assets/www/SQLitePlugin.js | 259 +++++++++++++++++ .../plugin/sqlitePlugin/SQLitePlugin.java | 271 ++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100755 Android/assets/www/SQLitePlugin.js create mode 100755 Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java diff --git a/Android/assets/www/SQLitePlugin.js b/Android/assets/www/SQLitePlugin.js new file mode 100755 index 0000000..6044969 --- /dev/null +++ b/Android/assets/www/SQLitePlugin.js @@ -0,0 +1,259 @@ +(function() { + var root; + root = this; + root.SQLitePlugin = (function() { + console.log("root.SQLitePlugin"); + SQLitePlugin.prototype.openDBs = {}; + + function SQLitePlugin(dbPath, openSuccess, openError) + { + console.log("SQLitePlugin"); + this.dbPath = dbPath; + this.openSuccess = openSuccess; + this.openError = openError; + if (!dbPath) { + throw new Error("Cannot create a SQLitePlugin instance without a dbPath"); + } + this.openSuccess || (this.openSuccess = function() { + console.log("DB opened: " + dbPath); + }); + this.openError || (this.openError = function(e) { + console.log(e.message); + }); + this.open(this.openSuccess, this.openError); + } + + SQLitePlugin.prototype.transaction = function(fn, error, success) + { + console.log("SQLitePlugin.prototype.transaction"); + var t; + t = new root.SQLitePluginTransaction(this.dbPath); + fn(t); + return t.complete(success, error); + }; + + SQLitePlugin.prototype.open = function(success, error) + { + console.log("SQLitePlugin.prototype.open"); + var opts; + if (!(this.dbPath in this.openDBs)) { + this.openDBs[this.dbPath] = true; + PhoneGap.exec(success, error, "SQLitePlugin", "open", [this.dbPath]); + } + }; + + SQLitePlugin.prototype.close = function(success, error) + { + console.log("SQLitePlugin.prototype.close"); + var opts; + if (this.dbPath in this.openDBs) { + delete this.openDBs[this.dbPath]; + PhoneGap.exec(null, null, "SQLitePlugin", "close", [this.dbPath]); + } + }; + return SQLitePlugin; + })(); + get_unique_id = function() + { + var id = new Date().getTime(); + var id2 = new Date().getTime(); + while(id === id2) + { + id2 = new Date().getTime(); + } + return id2+'000'; + } + transaction_queue = []; + transaction_callback_queue = new Object(); + root.SQLitePluginTransaction = (function() { + console.log("root.SQLitePluginTransaction"); + function SQLitePluginTransaction(dbPath) + { + console.log("root.SQLitePluginTransaction.SQLitePluginTransaction"); + this.dbPath = dbPath; + this.executes = []; + this.trans_id = get_unique_id(); + this.__completed = false; + this.__submitted = false; + this.optimization_no_nested_callbacks = true;//if set to true large batches of queries within a transaction will be much faster but you lose the ability to do muiti level nesting of executeSQL callbacks + console.log("root.SQLitePluginTransaction - this.trans_id:"+this.trans_id); + transaction_queue[this.trans_id] = []; + transaction_callback_queue[this.trans_id] = new Object(); + } + SQLitePluginTransaction.queryCompleteCallback = function(transId, queryId, result) + { + console.log("SQLitePluginTransaction.queryCompleteCallback"); + var query = null; + for (var x in transaction_queue[transId]) + { + if(transaction_queue[transId][x]['query_id'] == queryId) + { + query = transaction_queue[transId][x]; + if(transaction_queue[transId].length == 1) + transaction_queue[transId] = []; + else + transaction_queue[transId].splice(x, 1); + break; + } + } + +// if(query) +// console.log("SQLitePluginTransaction.completeCallback---query:"+query['query']); + if(query && query['callback']) + { + query['callback'](result) + } + } + SQLitePluginTransaction.queryErrorCallback = function(transId, queryId, result) + { + var query = null; + for (var x in transaction_queue[transId]) + { + if(transaction_queue[transId][x]['query_id'] == queryId) + { + query = transaction_queue[transId][x]; + if(transaction_queue[transId].length == 1) + transaction_queue[transId] = []; + else + transaction_queue[transId].splice(x, 1); + break; + } + } + //if(query) + // console.log("SQLitePluginTransaction.queryErrorCallback---query:"+query['query']); + if(query && query['err_callback']) + query['err_callback'](result) + } + SQLitePluginTransaction.txCompleteCallback = function(transId) + { + if(typeof transId != 'undefined') + { + if(transId && transaction_callback_queue[transId] && transaction_callback_queue[transId]['success']) + { + transaction_callback_queue[transId]['success'](); + } + + + // delete transaction_queue[transId]; + // delete transaction_callback_queue[transId]; + } + else + console.log("SQLitePluginTransaction.txCompleteCallback---transId = NULL"); + } + SQLitePluginTransaction.txErrorCallback = function(transId, error) + { + if(typeof transId != 'undefined') + { + console.log("SQLitePluginTransaction.txErrorCallback---transId:"+transId); + if(transId && transaction_callback_queue[transId]['error']) + transaction_callback_queue[transId]['error'](error); + delete transaction_queue[transId]; + delete transaction_callback_queue[transId]; + } + else + console.log("SQLitePluginTransaction.txErrorCallback---transId = NULL"); + } + SQLitePluginTransaction.prototype.add_to_transaction = function(trans_id, query, params, callback, err_callback) + { + var new_query = new Object();; + new_query['trans_id'] = trans_id; + if(callback || !this.optimization_no_nested_callbacks) + new_query['query_id'] = get_unique_id(); + else if(this.optimization_no_nested_callbacks) + new_query['query_id'] = ""; + new_query['query'] = query; + if(params) + new_query['params'] = params; + else + new_query['params'] = []; + new_query['callback'] = callback; + new_query['err_callback'] = err_callback; + if(!transaction_queue[trans_id]) + transaction_queue[trans_id] = []; + transaction_queue[trans_id].push(new_query); + } + + SQLitePluginTransaction.prototype.executeSql = function(sql, values, success, error) { + console.log("SQLitePluginTransaction.prototype.executeSql"); + var errorcb, successcb, txself; + txself = this; + successcb = null; + if (success) + { + console.log("success not null:"+sql); + successcb = function(execres) + { + console.log("executeSql callback:"+JSON.stringify(execres)); + var res, saveres; + saveres = execres; + res = { + rows: { + item: function(i) { + return saveres[i]; + }, + length: saveres.length + }, + rowsAffected: saveres.rowsAffected, + insertId: saveres.insertId || null + }; + return success(txself, res); + }; + } + else + console.log("success NULL:"+sql); + + errorcb = null; + if (error) { + errorcb = function(res) { + return error(txself, res); + }; + } + this.add_to_transaction(this.trans_id, sql, values, successcb, errorcb); + console.log("executeSql - add_to_transaction"+sql); + }; + + SQLitePluginTransaction.prototype.complete = function(success, error) { + console.log("SQLitePluginTransaction.prototype.complete"); + var begin_opts, commit_opts, errorcb, executes, opts, successcb, txself; + if (this.__completed) throw new Error("Transaction already run"); + if (this.__submitted) throw new Error("Transaction already submitted"); + this.__submitted = true; + txself = this; + successcb = function() + { + if(transaction_queue[txself.trans_id].length > 0 && !txself.optimization_no_nested_callbacks) + { + txself.__submitted = false; + txself.complete(success, error); + } + else + { + this.__completed = true; + if(success) + return success(txself); + } + }; + errorcb = function(res) {}; + if (error) { + errorcb = function(res) { + return error(txself, res); + }; + } + transaction_callback_queue[this.trans_id]['success'] = successcb; + transaction_callback_queue[this.trans_id]['error'] = errorcb; + PhoneGap.exec(null, null, "SQLitePlugin", "executeSqlBatch", transaction_queue[this.trans_id]); + }; + return SQLitePluginTransaction; + })(); + + root.sqlitePlugin = { + openDatabase: function(dbPath, version, displayName, estimatedSize, creationCallback, errorCallback) { + if (version == null) version = null; + if (displayName == null) displayName = null; + if (estimatedSize == null) estimatedSize = 0; + if (creationCallback == null) creationCallback = null; + if (errorCallback == null) errorCallback = null; + return new SQLitePlugin(dbPath, creationCallback, errorCallback); + } + }; +}).call(this); \ No newline at end of file diff --git a/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java b/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java new file mode 100755 index 0000000..f0f7469 --- /dev/null +++ b/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java @@ -0,0 +1,271 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ +package com.phonegap.plugins.sqlitePlugin; + +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; + +public class SQLitePlugin extends Plugin { + + // Data Definition Language + SQLiteDatabase myDb = null; // Database object + String path = null; // Database path + String dbName = null; // Database name + + /** + * Constructor. + */ + public SQLitePlugin() { + } + + /** + * Executes the request and returns PluginResult. + * + * @param action + * The action to execute. + * @param args + * JSONArry of arguments for the plugin. + * @param callbackId + * The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + 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)); + } else if (action.equals("open")) { + this.openDatabase(args.getString(0), "1", + "database", 5000000); + //this.openDatabase(args.getString(0), args.getString(1), + // args.getString(2), args.getLong(3)); + } + else if (action.equals("executeSqlBatch")) + { + String[] queries = null; + String[] queryIDs = null; + String[][] params = null; + String trans_id = null; + JSONObject a = null; + JSONArray jsonArr = null; + int paramLen = 0; + + if (args.isNull(0)) { + queries = new String[0]; + } else { + int len = args.length(); + queries = new String[len]; + queryIDs = new String[len]; + params = new String[len][1]; + for (int i = 0; i < len; i++) + { + a = args.getJSONObject(i); + queries[i] = a.getString("query"); + queryIDs[i] = a.getString("query_id"); + trans_id = a.getString("trans_id"); + jsonArr = a.getJSONArray("params"); + paramLen = jsonArr.length(); + params[i] = new String[paramLen]; + + for (int j = 0; j < paramLen; j++) { + params[i][j] = jsonArr.getString(j); + if(params[i][j] == "null") + params[i][j] = ""; + } + } + } + if(trans_id != null) + this.executeSqlBatch(queries, params, queryIDs, trans_id); + else + Log.v("error", "null trans_id"); + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run + * synchronously. + * + * @param action + * The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + return true; + } + + /** + * 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) { + 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 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); + } + + public void executeSqlBatch(String[] queryarr, String[][] paramsarr, String[] queryIDs, String tx_id) { + try { + this.myDb.beginTransaction(); + String query = ""; + String query_id = ""; + String[] params; + int len = queryarr.length; + for (int i = 0; i < len; i++) { + query = queryarr[i]; + params = paramsarr[i]; + query_id = queryIDs[i]; + Cursor myCursor = this.myDb.rawQuery(query, params); + + this.processResults(myCursor, query_id, tx_id); + myCursor.close(); + } + this.myDb.setTransactionSuccessful(); + } + catch (SQLiteException ex) { + ex.printStackTrace(); + Log.v("executeSqlBatch", "SQLitePlugin.executeSql(): Error=" + ex.getMessage()); + this.sendJavascript("SQLitePluginTransaction.txErrorCallback('" + tx_id + "', '"+ex.getMessage()+"');"); + } + finally { + this.myDb.endTransaction(); + Log.v("executeSqlBatch", tx_id); + this.sendJavascript("SQLitePluginTransaction.txCompleteCallback('" + tx_id + "');"); + } + } + + /** + * Process query results. + * + * @param cur + * Cursor into query results + * @param tx_id + * Transaction id + */ + public void processResults(Cursor cur, String query_id, String tx_id) { + + String result = "[]"; + // If query result has rows + + if (cur.moveToFirst()) { + JSONArray fullresult = new JSONArray(); + String key = ""; + int colCount = cur.getColumnCount(); + + // Build up JSON result object for each row + do { + JSONObject row = new JSONObject(); + try { + for (int i = 0; i < colCount; ++i) { + key = cur.getColumnName(i); + if(android.os.Build.VERSION.SDK_INT >= 11) + { + switch(cur.getType (i)) + { + case Cursor.FIELD_TYPE_NULL: + row.put(key, null); + break; + case Cursor.FIELD_TYPE_INTEGER: + row.put(key, cur.getInt(i)); + break; + case Cursor.FIELD_TYPE_FLOAT: + row.put(key, cur.getFloat(i)); + break; + case Cursor.FIELD_TYPE_STRING: + row.put(key, cur.getString(i)); + break; + case Cursor.FIELD_TYPE_BLOB: + row.put(key, cur.getBlob(i)); + break; + } + } + else + { + row.put(key, cur.getString(i)); + } + } + fullresult.put(row); + + } catch (JSONException e) { + e.printStackTrace(); + } + + } while (cur.moveToNext()); + + result = fullresult.toString(); + } + if(query_id.length() > 0) + this.sendJavascript(" SQLitePluginTransaction.queryCompleteCallback('" + tx_id + "','" + query_id + "', " + result + ");"); + + } +}