Return-Path: X-Original-To: apmail-cordova-commits-archive@www.apache.org Delivered-To: apmail-cordova-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 2DF4C1063F for ; Tue, 30 Jul 2013 15:12:27 +0000 (UTC) Received: (qmail 4465 invoked by uid 500); 30 Jul 2013 15:12:27 -0000 Delivered-To: apmail-cordova-commits-archive@cordova.apache.org Received: (qmail 4441 invoked by uid 500); 30 Jul 2013 15:12:26 -0000 Mailing-List: contact commits-help@cordova.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cordova.apache.org Delivered-To: mailing list commits@cordova.apache.org Received: (qmail 4343 invoked by uid 99); 30 Jul 2013 15:12:25 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 30 Jul 2013 15:12:25 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 04A0031AB34; Tue, 30 Jul 2013 15:12:24 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: agrieve@apache.org To: commits@cordova.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: git commit: [CB-4428] Add android-storage plugin. Date: Tue, 30 Jul 2013 15:12:24 +0000 (UTC) Updated Branches: refs/heads/plugins [created] d3c19693e [CB-4428] Add android-storage plugin. Project: http://git-wip-us.apache.org/repos/asf/cordova-labs/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-labs/commit/d3c19693 Tree: http://git-wip-us.apache.org/repos/asf/cordova-labs/tree/d3c19693 Diff: http://git-wip-us.apache.org/repos/asf/cordova-labs/diff/d3c19693 Branch: refs/heads/plugins Commit: d3c19693e126a7199b865b6d7aed835166479a55 Parents: 8b8d4f6 Author: Andrew Grieve Authored: Tue Jul 30 11:11:25 2013 -0400 Committer: Andrew Grieve Committed: Tue Jul 30 11:11:25 2013 -0400 ---------------------------------------------------------------------- README.md | 25 +-- android-storage/README.md | 3 + android-storage/Storage.java | 244 +++++++++++++++++++++++++++ android-storage/openDatabase.js | 47 ++++++ android-storage/plugin.xml | 25 +++ android-storage/storage.js | 311 +++++++++++++++++++++++++++++++++++ 6 files changed, 633 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-labs/blob/d3c19693/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index a994618..c098977 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,3 @@ -# Cordova Laboratory - -> Caution: Safety Goggles are Recommended! - -## Purpose - -The purpose of this repo is for experimental code. Examples include demo apps, -native api explorations, or anything really that does not fit in an existing Cordova platform. - -## Project Organization - -> Everyone works on a branch - -`master` branch should *never* have content. - -Each project should create a separate branch to work on. There are major benefits -to this practice: - -- Each project has an isolate git history, which allows for easy migration to - a new git repository; -- Working directory is not polluted with the files of other projects. -- Projects will not step on each others toes. \ No newline at end of file +cordova-labs plugins branch +------------------------------- +This branch stores plugins that are built by the Cordova team, but are not supported as "core" plugins. http://git-wip-us.apache.org/repos/asf/cordova-labs/blob/d3c19693/android-storage/README.md ---------------------------------------------------------------------- diff --git a/android-storage/README.md b/android-storage/README.md new file mode 100644 index 0000000..4851e98 --- /dev/null +++ b/android-storage/README.md @@ -0,0 +1,3 @@ +cordova-plugin-android-storage +------------------------------- +This plugin enables WebSQL support on Android. http://git-wip-us.apache.org/repos/asf/cordova-labs/blob/d3c19693/android-storage/Storage.java ---------------------------------------------------------------------- diff --git a/android-storage/Storage.java b/android-storage/Storage.java new file mode 100644 index 0000000..193febc --- /dev/null +++ b/android-storage/Storage.java @@ -0,0 +1,244 @@ +/* + 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.labs.storage; + +import java.io.File; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.*; + +/** + * This class implements the HTML5 database support to work around a bug for + * Android 3.0 devices. It is not used for other versions of Android, since + * HTML5 database is built in to the browser. + */ +public class Storage extends CordovaPlugin { + + // Data Definition Language + private static final String ALTER = "alter"; + private static final String CREATE = "create"; + private static final String DROP = "drop"; + private static final String TRUNCATE = "truncate"; + + SQLiteDatabase myDb = null; // Database object + String path = null; // Database path + String dbName = null; // Database name + + /** + * Constructor. + */ + public Storage() { + } + + /** + * Executes the request and returns PluginResult. + * + * @param action + * The action to execute. + * @param args + * JSONArry of arguments for the plugin. + * @param callbackContext + * The callback context used when calling back into JavaScript. + * @return True if the action was valid, false otherwise. + */ + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + if (action.equals("openDatabase")) { + this.openDatabase(args.getString(0), args.getString(1), + args.getString(2), args.getLong(3)); + } else if (action.equals("executeSql")) { + String[] s = null; + if (args.isNull(1)) { + s = new String[0]; + } else { + JSONArray a = args.getJSONArray(1); + int len = a.length(); + s = new String[len]; + for (int i = 0; i < len; i++) { + s[i] = a.getString(i); + } + } + this.executeSql(args.getString(0), s, args.getString(2)); + } + else { + return false; + } + callbackContext.success(); + return true; + } + + /** + * Clean up and close database. + */ + @Override + public void onDestroy() { + if (this.myDb != null) { + this.myDb.close(); + this.myDb = null; + } + } + + /** + * Clean up on navigation/refresh. + */ + public void onReset() { + this.onDestroy(); + } + + // -------------------------------------------------------------------------- + // LOCAL METHODS + // -------------------------------------------------------------------------- + + /** + * 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) { + this.path = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); + } + + this.dbName = this.path + File.separator + db + ".db"; + + /* + * What is all this nonsense? Well the separator was incorrect so the db was showing up in the wrong + * directory. This bit of code fixes that issue and moves the db to the correct directory. + */ + File oldDbFile = new File(this.path + File.pathSeparator + db + ".db"); + if (oldDbFile.exists()) { + File dbPath = new File(this.path); + File dbFile = new File(dbName); + dbPath.mkdirs(); + oldDbFile.renameTo(dbFile); + } + + 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 { + if (isDDL(query)) { + this.myDb.execSQL(query); + this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', '');"); + } + else { + 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.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').failQuery('" + ex.getMessage() + "','" + tx_id + "');"); + } + } + + /** + * Checks to see the the query is a Data Definition command + * + * @param query to be executed + * @return true if it is a DDL command, false otherwise + */ + private boolean isDDL(String query) { + String cmd = query.toLowerCase(); + if (cmd.startsWith(DROP) || cmd.startsWith(CREATE) || cmd.startsWith(ALTER) || cmd.startsWith(TRUNCATE)) { + return true; + } + return false; + } + + /** + * Process query results. + * + * @param cur + * Cursor into query results + * @param tx_id + * Transaction id + */ + public void processResults(Cursor cur, String tx_id) { + + String result = "[]"; + // If query result has rows + + if (cur.moveToFirst()) { + JSONArray fullresult = new JSONArray(); + String key = ""; + String value = ""; + 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); + value = cur.getString(i); + row.put(key, value); + } + fullresult.put(row); + + } catch (JSONException e) { + e.printStackTrace(); + } + + } while (cur.moveToNext()); + + result = fullresult.toString(); + } + + // Let JavaScript know that there are no more rows + this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', " + result + ");"); + } + +} http://git-wip-us.apache.org/repos/asf/cordova-labs/blob/d3c19693/android-storage/openDatabase.js ---------------------------------------------------------------------- diff --git a/android-storage/openDatabase.js b/android-storage/openDatabase.js new file mode 100644 index 0000000..a67aea6 --- /dev/null +++ b/android-storage/openDatabase.js @@ -0,0 +1,47 @@ +/* + * 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. + * +*/ + + +var modulemapper = require('cordova/modulemapper'), + storage = require('./storage'); + +var originalOpenDatabase = modulemapper.getOriginalSymbol(window, 'openDatabase'); + +module.exports = function(name, version, desc, size) { + // First patch WebSQL if necessary + if (!originalOpenDatabase) { + // Not defined, create an openDatabase function for all to use! + return storage.openDatabase.apply(this, arguments); + } + + // Defined, but some Android devices will throw a SECURITY_ERR - + // so we wrap the whole thing in a try-catch and shim in our own + // if the device has Android bug 16175. + try { + return originalOpenDatabase(name, version, desc, size); + } catch (ex) { + if (ex.code !== 18) { + throw ex; + } + } + return storage.openDatabase(name, version, desc, size); +}; + + http://git-wip-us.apache.org/repos/asf/cordova-labs/blob/d3c19693/android-storage/plugin.xml ---------------------------------------------------------------------- diff --git a/android-storage/plugin.xml b/android-storage/plugin.xml new file mode 100644 index 0000000..00fed84 --- /dev/null +++ b/android-storage/plugin.xml @@ -0,0 +1,25 @@ + + + + + Android Storage + + + + + + + + + + + + + + + + + + http://git-wip-us.apache.org/repos/asf/cordova-labs/blob/d3c19693/android-storage/storage.js ---------------------------------------------------------------------- diff --git a/android-storage/storage.js b/android-storage/storage.js new file mode 100644 index 0000000..e615ebc --- /dev/null +++ b/android-storage/storage.js @@ -0,0 +1,311 @@ +/* + * + * 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. + * +*/ + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + channel = require('cordova/channel'); + +var queryQueue = {}; + +/** + * SQL result set object + * PRIVATE METHOD + * @constructor + */ +var DroidDB_Rows = function() { + this.resultSet = []; // results array + this.length = 0; // number of rows +}; + +/** + * 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]; +}; + +/** + * SQL result set that is returned to user. + * PRIVATE METHOD + * @constructor + */ +var DroidDB_Result = function() { + this.rows = new DroidDB_Rows(); +}; + +/** + * Callback from native code when query is complete. + * PRIVATE METHOD + * + * @param id Query id + */ +function completeQuery(id, data) { + var query = queryQueue[id]; + if (query) { + try { + delete queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + + // Save query results + var r = new DroidDB_Result(); + r.rows.resultSet = data; + r.rows.length = data.length; + try { + if (typeof query.successCallback === 'function') { + query.successCallback(query.tx, r); + } + } catch (ex) { + console.log("executeSql error calling user success callback: "+ex); + } + + tx.queryComplete(id); + } + } catch (e) { + console.log("executeSql error: "+e); + } + } +} + +/** + * Callback from native code when query fails + * PRIVATE METHOD + * + * @param reason Error message + * @param id Query id + */ +function failQuery(reason, id) { + var query = queryQueue[id]; + if (query) { + try { + delete queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + tx.queryList = {}; + + try { + if (typeof query.errorCallback === 'function') { + query.errorCallback(query.tx, reason); + } + } catch (ex) { + console.log("executeSql error calling user error callback: "+ex); + } + + tx.queryFailed(id, reason); + } + + } catch (e) { + console.log("executeSql error: "+e); + } + } +} + +/** + * SQL query object + * PRIVATE METHOD + * + * @constructor + * @param tx The transaction object that this query belongs to + */ +var DroidDB_Query = function(tx) { + + // Set the id of the query + this.id = utils.createUUID(); + + // Add this query to the queue + queryQueue[this.id] = this; + + // Init result + this.resultSet = []; + + // Set transaction that this query belongs to + this.tx = tx; + + // Add this query to transaction list + this.tx.queryList[this.id] = this; + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + +}; + +/** + * Transaction object + * PRIVATE METHOD + * @constructor + */ +var DroidDB_Tx = function() { + + // Set the id of the transaction + this.id = utils.createUUID(); + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + + // Query list + this.queryList = {}; +}; + +/** + * Mark query in transaction as complete. + * If all queries are complete, call the user's transaction success callback. + * + * @param id Query id + */ +DroidDB_Tx.prototype.queryComplete = function(id) { + delete this.queryList[id]; + + // If no more outstanding queries, then fire transaction success + if (this.successCallback) { + var count = 0; + var i; + for (i in this.queryList) { + if (this.queryList.hasOwnProperty(i)) { + count++; + } + } + if (count === 0) { + try { + this.successCallback(); + } catch(e) { + console.log("Transaction error calling user success callback: " + e); + } + } + } +}; + +/** + * Mark query in transaction as failed. + * + * @param id Query id + * @param reason Error message + */ +DroidDB_Tx.prototype.queryFailed = function(id, reason) { + + // The sql queries in this transaction have already been run, since + // we really don't have a real transaction implemented in native code. + // However, the user callbacks for the remaining sql queries in transaction + // will not be called. + this.queryList = {}; + + if (this.errorCallback) { + try { + this.errorCallback(reason); + } catch(e) { + console.log("Transaction error calling user error callback: " + e); + } + } +}; + +/** + * Execute SQL statement + * + * @param sql SQL statement to execute + * @param params Statement parameters + * @param successCallback Success callback + * @param errorCallback Error callback + */ +DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { + + // Init params array + if (typeof params === 'undefined') { + params = []; + } + + // Create query and add to queue + var query = new DroidDB_Query(this); + queryQueue[query.id] = query; + + // Save callbacks + query.successCallback = successCallback; + query.errorCallback = errorCallback; + + // Call native code + exec(null, null, "Storage", "executeSql", [sql, params, query.id]); +}; + +var DatabaseShell = function() { +}; + +/** + * Start a transaction. + * Does not support rollback in event of failure. + * + * @param process {Function} The transaction function + * @param successCallback {Function} + * @param errorCallback {Function} + */ +DatabaseShell.prototype.transaction = function(process, errorCallback, successCallback) { + var tx = new DroidDB_Tx(); + tx.successCallback = successCallback; + tx.errorCallback = errorCallback; + try { + process(tx); + } catch (e) { + console.log("Transaction error: "+e); + if (tx.errorCallback) { + try { + tx.errorCallback(e); + } catch (ex) { + console.log("Transaction error calling user error callback: "+e); + } + } + } +}; + +/** + * 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 + */ +var DroidDB_openDatabase = function(name, version, display_name, size) { + exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); + var db = new DatabaseShell(); + return db; +}; + + +module.exports = { + openDatabase:DroidDB_openDatabase, + failQuery:failQuery, + completeQuery:completeQuery +};