more templates back into cli
This commit is contained in:
parent
be5fad55ca
commit
ba01a0a72b
121 changed files with 5 additions and 42 deletions
27
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/README
Executable file
27
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/README
Executable file
|
@ -0,0 +1,27 @@
|
|||
How to run the tests:
|
||||
|
||||
* The tests themselves assume that jsunit is in a sibling directory
|
||||
to the one containing the distrbution. If this is not so, it is sufficient
|
||||
to edit the paths in the test files. (On UNIX, symlinks may be your
|
||||
friend if this is not convenient.)
|
||||
|
||||
* jsmock.js (available from http://jsmock.sourceforge.net/) should
|
||||
be placed in the distribution directory.
|
||||
|
||||
* Specify the test files via a URL parameter. (This might be an issue
|
||||
with jsunit: http://digitalmihailo.blogspot.com/2008/06/make-jsunit-work-in-firefox-30.html)
|
||||
|
||||
For example, if the root of your downloaded of the distribution is /mypath:
|
||||
|
||||
file:///mypath/jsunit/testRunner.html?testpage=mypath/webstorageportabilitylayer/dbwrapper_gears_test.html
|
||||
file:///mypath/jsunit/testRunner.html?testpage=mypath/webstorageportabilitylayer/dbwrapper_html5_test.html
|
||||
file:///mypath/jsunit/testRunner.html?testpage=mypath/webstorageportabilitylayer/dbwrapperapi_test.html
|
||||
|
||||
NB: the leading / in a UNIX path is not included in mypath so setting
|
||||
it via pwd will not deliver the desired effect.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
// Namespace.
|
||||
google.wspl.DatabaseFactory = google.wspl.DatabaseFactory || {};
|
||||
|
||||
/**
|
||||
* Factory function to build databases in a cross-API manner.
|
||||
* @param {string} dbName of the database
|
||||
* @param {string} dbworkerUrl the URL for Gears worker.
|
||||
* @return {google.wspl.Database} The database object.
|
||||
*/
|
||||
google.wspl.DatabaseFactory.createDatabase = function(dbName, dbworkerUrl) {
|
||||
var dbms;
|
||||
if (window.openDatabase) {
|
||||
// We have HTML5 functionality.
|
||||
dbms = new google.wspl.html5.Database(dbName);
|
||||
} else {
|
||||
// Try to use Google Gears.
|
||||
var gearsDb = goog.gears.getFactory().create('beta.database');
|
||||
var wp = goog.gears.getFactory().create('beta.workerpool');
|
||||
|
||||
// Note that Gears will not allow file based URLs when creating a worker.
|
||||
dbms = new wireless.db.gears.Database();
|
||||
dbms.openDatabase('', dbName, gearsDb);
|
||||
wp.onmessage = google.bind(dbms.onMessage_, dbms);
|
||||
|
||||
// Comment this line out to use the synchronous database.
|
||||
dbms.startWorker(wp, dbworkerUrl, 0);
|
||||
}
|
||||
return dbms;
|
||||
};
|
324
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/dbworker.js
Executable file
324
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/dbworker.js
Executable file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A worker thread that performs synchronous queries against a
|
||||
* Gears database on behalf of an asynchronous calling client.
|
||||
*
|
||||
* The worker replies to the sender with messages to pass results, errors, and
|
||||
* notifications about completed transactions. The type field of the message
|
||||
* body specifies the message type. For each successful statement, a RESULT
|
||||
* message is sent with a result attribute containing the Gears result set. For
|
||||
* the first unsuccessful statement, an ERROR message will be sent with details
|
||||
* stored in the error field. After the transaction has been committed, a COMMIT
|
||||
* message is sent. If the transaction is rolled back, a ROLLBACK message is
|
||||
* sent.
|
||||
*
|
||||
* NB: The worker must be served over http. Further, to operate successfully,
|
||||
* it requires the inclusion of global_functions.js and gearsutils.js.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DbWorker to handle incoming messages, execute queries, and return
|
||||
* results to the main thread.
|
||||
*
|
||||
* @param {GearsWorkerPool} wp The gears worker pool.
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.gears.DbWorker = function(wp) {
|
||||
|
||||
/**
|
||||
* An array of transaction ids representing the transactions that are open on
|
||||
* the database.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.transactions_ = [];
|
||||
|
||||
/**
|
||||
* The gears worker pool.
|
||||
* @type {GearsWorkerPool}
|
||||
* @private
|
||||
*/
|
||||
this.wp_ = wp;
|
||||
|
||||
this.wp_.onmessage = google.bind(this.onMessage_, this);
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.STARTED
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* The gears database that this worker thread will interact with.
|
||||
* @type {GearsDatabase}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.db_;
|
||||
|
||||
/**
|
||||
* A singleton instance of DbWorker.
|
||||
* @type {google.wspl.gears.DbWorker?}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.instance_;
|
||||
|
||||
/**
|
||||
* The sender ID of the incomming messages. Default to 0 for workerpool ID.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.senderId_ = 0;
|
||||
|
||||
/**
|
||||
* Message type constants for worker command messages.
|
||||
* @enum {number}
|
||||
*/
|
||||
google.wspl.gears.DbWorker.CommandTypes = {
|
||||
OPEN: 1,
|
||||
BEGIN: 2,
|
||||
EXECUTE: 3,
|
||||
COMMIT: 4,
|
||||
ROLLBACK: 5
|
||||
};
|
||||
|
||||
/**
|
||||
* Message type constants for worker reply messages.
|
||||
* @enum {number}
|
||||
*/
|
||||
google.wspl.gears.DbWorker.ReplyTypes = {
|
||||
RESULT: 1,
|
||||
FAILURE: 2,
|
||||
COMMIT: 3,
|
||||
ROLLBACK: 4,
|
||||
STARTED: 5,
|
||||
OPEN_SUCCESSFUL: 6,
|
||||
OPEN_FAILED: 7,
|
||||
LOG: 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the DbWorker.
|
||||
*/
|
||||
google.wspl.gears.DbWorker.start = function() {
|
||||
var wp = google.gears.workerPool;
|
||||
google.wspl.gears.DbWorker.instance_ = new google.wspl.gears.DbWorker(wp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an OPEN command from the main thread.
|
||||
*
|
||||
* @param {string} userId The user to which the database belongs.
|
||||
* @param {string} name The database's name.
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleOpen_ = function(userId, name) {
|
||||
this.log_('Attempting to create Gears database: userId=' + userId + ', name='
|
||||
+ name);
|
||||
try {
|
||||
this.db_ = google.gears.factory.create('beta.database', '1.0');
|
||||
google.wspl.GearsUtils.openDatabase(userId, name, this.db_, this.log_);
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.OPEN_SUCCESSFUL
|
||||
});
|
||||
} catch (ex) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED,
|
||||
'error': ex
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a EXECUTE command from the main thread.
|
||||
*
|
||||
* @param {!Array.<google.wspl.Statement>} statements The statements to execute.
|
||||
* @param {number} callbackId The callback to invoke after each execution.
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleExecute_ =
|
||||
function(statements, callbackId, transactionId) {
|
||||
var self = this;
|
||||
try {
|
||||
this.executeAll_(statements, function(results) {
|
||||
self.sendMessageToWorker_(/** @type {string} */({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.RESULT,
|
||||
'results': results,
|
||||
'callbackId': callbackId,
|
||||
'transactionId': transactionId
|
||||
}));
|
||||
});
|
||||
} catch (e) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.FAILURE,
|
||||
'error': e,
|
||||
'callbackId': callbackId,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes all of the statements on the Gears database. The callback is
|
||||
* invoked with the query results after each successful query execution.
|
||||
*
|
||||
* @param {!Array.<Object>} statements The statements to execute.
|
||||
* @param {Function} callback The callback to invoke with query results.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.executeAll_ =
|
||||
function(statements, callback) {
|
||||
var results = [];
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var resultset = this.db_.execute(statements[i]['sql'],
|
||||
statements[i]['params']);
|
||||
var result = google.wspl.GearsUtils.resultSetToObjectArray(resultset);
|
||||
results.push(result);
|
||||
}
|
||||
callback(results);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a BEGIN command from the main thread.
|
||||
*
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleBegin_ = function(transactionId) {
|
||||
this.transactions_.push(transactionId);
|
||||
this.db_.execute('BEGIN IMMEDIATE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a COMMIT command from the main thread.
|
||||
*
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleCommit_ = function(transactionId) {
|
||||
this.db_.execute('COMMIT');
|
||||
this.postCommit_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a ROLLBACK command from the main thread.
|
||||
*
|
||||
* @param {number} transactionId The transaction that the statements belong to.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.handleRollback_ = function(transactionId) {
|
||||
this.db_.execute('ROLLBACK');
|
||||
this.postRollback_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a COMMIT reply to the main thread for each transaction that was
|
||||
* committed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.postCommit_ = function() {
|
||||
for (var i = this.transactions_.length - 1; i >= 0; i--) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.COMMIT,
|
||||
'transactionId': this.transactions_[i]
|
||||
});
|
||||
}
|
||||
this.transactions_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a ROLLBACK reply to the main thread for each transaction that was
|
||||
* rolled back.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.postRollback_ = function() {
|
||||
for (var i = this.transactions_.length - 1; i >= 0; i --) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK,
|
||||
'transactionId': this.transactions_[i]
|
||||
});
|
||||
}
|
||||
this.transactions_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles incomming messages.
|
||||
* @param {string} a Deprecated.
|
||||
* @param {number} b Deprecated.
|
||||
* @param {Object} messageObject The message object.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.onMessage_ =
|
||||
function(a, b, messageObject) {
|
||||
this.senderId_ = messageObject.sender;
|
||||
var message = messageObject.body;
|
||||
var type = message['type'];
|
||||
var name = message['name'];
|
||||
var statements = message['statements'];
|
||||
var callbackId = message['callbackId'];
|
||||
var transactionId = message['transactionId'];
|
||||
var userId = message['userId'];
|
||||
|
||||
try {
|
||||
switch(type) {
|
||||
case google.wspl.gears.DbWorker.CommandTypes.OPEN:
|
||||
this.handleOpen_(userId, name);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.EXECUTE:
|
||||
this.handleExecute_(statements, callbackId, transactionId);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.BEGIN:
|
||||
this.handleBegin_(transactionId);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.COMMIT:
|
||||
this.handleCommit_(transactionId);
|
||||
break;
|
||||
|
||||
case google.wspl.gears.DbWorker.CommandTypes.ROLLBACK:
|
||||
this.handleRollback_(transactionId);
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
this.log_('Database worker failed: ' + ex.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a log message to the main thread to be logged.
|
||||
* @param {string} msg The message to log.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.log_ = function(msg) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.ReplyTypes.LOG,
|
||||
'msg': msg
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to the main worker thread.
|
||||
* @param {Object} msg The message object to send.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.DbWorker.prototype.sendMessageToWorker_ = function(msg) {
|
||||
this.wp_.sendMessage(msg, this.senderId_);
|
||||
};
|
|
@ -0,0 +1,393 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears worker tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="gearsutils.js"></script>
|
||||
<script type="text/javascript" src="dbworker.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var db;
|
||||
var wp;
|
||||
var factory;
|
||||
var callbackId = 10;
|
||||
var transactionId = 15;
|
||||
var name = 'name';
|
||||
var userId = 'userId';
|
||||
var utils;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
|
||||
// Mock the Gears factory.
|
||||
factory = mockControl.createMock();
|
||||
factory.addMockMethod('create');
|
||||
|
||||
// Mock Google Gears.
|
||||
google.gears = {};
|
||||
google.gears.factory = {};
|
||||
google.gears.factory.create = factory.create;
|
||||
|
||||
// Mock the Gears workerpool object.
|
||||
wp = mockControl.createMock({
|
||||
allowCrossOrigin: function(){},
|
||||
sendMessage: function(){}
|
||||
});
|
||||
|
||||
// Mock the Gears database object.
|
||||
db = mockControl.createMock({
|
||||
execute: function(){},
|
||||
open: function(){},
|
||||
close: function(){},
|
||||
remove: function(){}
|
||||
});
|
||||
|
||||
// Mock the Gears utility classes
|
||||
utils = mockControl.createMock({
|
||||
openDatabase: function(){},
|
||||
});
|
||||
|
||||
google.wspl = google.wspl || {};
|
||||
google.wspl.GearsUtils = google.wspl.GearsUtils || {};
|
||||
google.wspl.GearsUtils.openDatabase = utils.openDatabase;
|
||||
google.wspl.GearsUtils.resultSetToObjectArray = function(rs) {
|
||||
return rs;
|
||||
};
|
||||
}
|
||||
|
||||
function buildWorker() {
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), 0).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Wrong message type.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.STARTED, msg.type);
|
||||
});
|
||||
var worker = new google.wspl.gears.DbWorker(wp);
|
||||
worker.db_ = db;
|
||||
worker.log_ = function() {};
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
function testConstruction() {
|
||||
var worker = buildWorker();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleExecute_success() {
|
||||
var worker = buildWorker();
|
||||
var stat1 = {sql: 'sql1', params: [1, 2]};
|
||||
var stat2 = {sql: 'sql2', params: [3, 4]};
|
||||
var statements = [stat1, stat2];
|
||||
var type = google.wspl.gears.DbWorker.ReplyTypes.RESULT;
|
||||
|
||||
db.expects().execute(stat1.sql, stat1.params).andReturn('result1');
|
||||
db.expects().execute(stat2.sql, stat2.params).andReturn('result2');
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Wrong message type.', type, msg.type);
|
||||
assertEquals('Wrong results.length', 2, msg.results.length);
|
||||
assertEquals('Wrong results[0].', 'result1', msg.results[0]);
|
||||
assertEquals('Wrong results[1].', 'result2', msg.results[1]);
|
||||
assertEquals('Wrong callbackId.', callbackId, msg.callbackId);
|
||||
assertEquals('Wrong transactionId.', transactionId, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.handleExecute_(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleExecute_failure() {
|
||||
var worker = buildWorker();
|
||||
var stat1 = {sql: 'sql1', params: [1, 2]};
|
||||
var stat2 = {sql: 'sql2', params: [3, 4]};
|
||||
var stat3 = {sql: 'sql3', params: [5, 6]};
|
||||
var statements = [stat1, stat2, stat3];
|
||||
var type1 = google.wspl.gears.DbWorker.ReplyTypes.RESULT;
|
||||
var type2 = google.wspl.gears.DbWorker.ReplyTypes.FAILURE;
|
||||
var error = 'sql error';
|
||||
|
||||
db.expects().execute(stat1.sql, stat1.params).andReturn('result1');
|
||||
db.expects().execute(stat2.sql, stat2.params).andThrow(error);
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Wrong message type.', type2, msg.type);
|
||||
assertEquals('Wrong result.', error, msg.error.message);
|
||||
assertEquals('Wrong callbackId.', callbackId, msg.callbackId);
|
||||
assertEquals('Wrong transactionId.', transactionId, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.handleExecute_(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleBegin() {
|
||||
var worker = buildWorker();
|
||||
|
||||
// Expecting two transactions to begin.
|
||||
db.expects().execute('BEGIN IMMEDIATE');
|
||||
db.expects().execute('BEGIN IMMEDIATE');
|
||||
|
||||
worker.handleBegin_(transactionId);
|
||||
worker.handleBegin_(22);
|
||||
|
||||
assertEquals('Did not save first transaction id', transactionId,
|
||||
worker.transactions_[0]);
|
||||
assertEquals('Did not save second transaction id', 22,
|
||||
worker.transactions_[1]);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleCommit() {
|
||||
var worker = buildWorker();
|
||||
db.expects().execute('COMMIT');
|
||||
worker.handleCommit_(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleRollback() {
|
||||
var worker = buildWorker();
|
||||
db.expects().execute('ROLLBACK');
|
||||
worker.handleRollback_(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpen_success() {
|
||||
var worker = buildWorker();
|
||||
worker.db_ = null;
|
||||
|
||||
factory.expects().create('beta.database', '1.0').andReturn(db);
|
||||
utils.expects().openDatabase(userId, name, db, worker.log_).andReturn(db);
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function(msg) {
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.OPEN_SUCCESSFUL, msg.type);
|
||||
});
|
||||
|
||||
worker.handleOpen_(userId, name);
|
||||
assertEquals('Database wrongly set', db, worker.db_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpen_failure_gearsfactory() {
|
||||
var worker = buildWorker();
|
||||
worker.db_ = null;
|
||||
|
||||
factory.expects().create('beta.database', '1.0').andThrow('blah!');
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function(msg) {
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED, msg.type);
|
||||
});
|
||||
|
||||
worker.handleOpen_(userId, name);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpen_failure_dbopen() {
|
||||
var worker = buildWorker();
|
||||
worker.db_ = null;
|
||||
|
||||
factory.expects().create('beta.database', '1.0').andReturn(null);
|
||||
utils.expects().openDatabase(userId, name, null, worker.log_).andThrow('blah!');
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function(msg) {
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED, msg.type);
|
||||
});
|
||||
|
||||
worker.handleOpen_(userId, name);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testPostCommit() {
|
||||
var worker = buildWorker();
|
||||
worker.transactions_ = [4, 5];
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.COMMIT, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
5, msg.transactionId);
|
||||
});
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.COMMIT, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
4, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.postCommit_();
|
||||
assertEquals('Did not clear the transactions.', 0,
|
||||
worker.transactions_.length);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testPostRollback() {
|
||||
var worker = buildWorker();
|
||||
worker.transactions_ = [4, 5];
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
5, msg.transactionId);
|
||||
});
|
||||
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), worker.senderId_).andStub(
|
||||
function() {
|
||||
var msg = arguments[0];
|
||||
assertEquals('Type not set correctly.',
|
||||
google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK, msg.type);
|
||||
assertEquals('Transaction id not set correctly.',
|
||||
4, msg.transactionId);
|
||||
});
|
||||
|
||||
worker.postRollback_();
|
||||
assertEquals('Did not clear the transactions.', 0,
|
||||
worker.transactions_.length);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage() {
|
||||
var messageObject = {sender: 123, body: {}};
|
||||
var worker = buildWorker();
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
|
||||
assertEquals('Wrong sender ID.', 123, worker.senderId_);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_open() {
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.OPEN,
|
||||
name: name,
|
||||
userId: userId
|
||||
}};
|
||||
|
||||
var worker = buildWorker();
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('open');
|
||||
worker.handleOpen_ = handler.open;
|
||||
handler.expects().open(userId, name);
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_execute() {
|
||||
var worker = buildWorker();
|
||||
var statements = ['stat1', 'stat2'];
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.EXECUTE,
|
||||
statements: statements,
|
||||
callbackId: callbackId,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleExecute_ = function(stat, call, trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong statements.', statements, stat);
|
||||
assertEquals('Wrong callback id.', callbackId, call);
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleExecute_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_begin() {
|
||||
var worker = buildWorker();
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.BEGIN,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleBegin_ = function(trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleBegin_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_commit() {
|
||||
var worker = buildWorker();
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.COMMIT,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleCommit_ = function(trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleCommit_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnmessage_rollback() {
|
||||
var worker = buildWorker();
|
||||
var messageObject = {sender: 123, body: {
|
||||
type: google.wspl.gears.DbWorker.CommandTypes.ROLLBACK,
|
||||
transactionId: transactionId
|
||||
}};
|
||||
var called = false;
|
||||
|
||||
worker.handleRollback_ = function(trans) {
|
||||
called = true;
|
||||
assertEquals('Wrong transaction id.', transactionId, trans);
|
||||
};
|
||||
|
||||
worker.onMessage_(null, null, messageObject);
|
||||
assertTrue('handleRollback_ not called.', called);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Starts the dbworker.
|
||||
*
|
||||
* When constructing the worker for execution, this needs to be the last
|
||||
* file. The worker consists of the following source files combined together.
|
||||
*
|
||||
* globalfunctions.js
|
||||
* gearsutils.js
|
||||
* dbworker.js
|
||||
* dbworkerstarter.js
|
||||
*
|
||||
* and then loaded into a Gears worker process as implemented in
|
||||
* databasefactory.js
|
||||
*/
|
||||
|
||||
google.wspl.gears.DbWorker.start();
|
|
@ -0,0 +1,595 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A Gears implementation of dbwrapperapi Database.
|
||||
*
|
||||
* This implementation locks database access upon invoking the transaction's
|
||||
* populate callback. Statements are then asynchronously sent to a worker
|
||||
* thread for execution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see google.wspl.Database#Database
|
||||
* @param {boolean} opt_sync Perform all callbacks synchronously.
|
||||
* @constructor
|
||||
* @extends google.wspl.Database
|
||||
*/
|
||||
google.wspl.gears.Database = function(opt_sync) {
|
||||
google.wspl.Database.call(this);
|
||||
|
||||
/**
|
||||
* Begin transactions synchronously.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.synchronous_ = !!opt_sync;
|
||||
};
|
||||
google.inherits(google.wspl.gears.Database, google.wspl.Database);
|
||||
|
||||
/**
|
||||
* The time to wait for the dbworker to reply with STARTED.
|
||||
* @type {number}
|
||||
*/
|
||||
google.wspl.gears.Database.TIMEOUT = 60000;
|
||||
|
||||
/**
|
||||
* Whether the gears worker failed to reply with STARTED before TIMEOUT.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.workerTimeout_ = false;
|
||||
|
||||
/**
|
||||
* Flag set when the worker is ready with an open database connection.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.workerReady_ = false;
|
||||
|
||||
/**
|
||||
* Flag set when this database should use the worker to process transactions.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.useWorker_ = false;
|
||||
|
||||
/**
|
||||
* The user for this database.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.userId_;
|
||||
|
||||
/**
|
||||
* The name for this database.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.name_;
|
||||
|
||||
/**
|
||||
* A map of open transactions and their callbacks.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.transactions_ = {};
|
||||
|
||||
/**
|
||||
* An array of transaction ids that should be executed in order as the lock
|
||||
* becomes available.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.queuedTransactions_ = [];
|
||||
|
||||
/**
|
||||
* The transaction lock for this database.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.locked_ = false;
|
||||
|
||||
/**
|
||||
* The number of transactions to be used as an index.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.transCount_ = 1;
|
||||
|
||||
/**
|
||||
* The id of the transaction being executed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.currentTransactionId_;
|
||||
|
||||
/**
|
||||
* The Gears worker pool.
|
||||
* @type {GearsWorkerPool}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.wp_;
|
||||
|
||||
/**
|
||||
* The worker ID.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.workerId_;
|
||||
|
||||
/**
|
||||
* The Gears database object.
|
||||
* @type {GearsDatabase}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.db_;
|
||||
|
||||
/**
|
||||
* Opens a new Gears database. This operation can only be performed once.
|
||||
* @param {string} userId The user for this database.
|
||||
* @param {string} name The name for this database.
|
||||
* @param {GearsDatabase} gearsDb The gears database.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.openDatabase = function(userId, name,
|
||||
gearsDb) {
|
||||
if (!this.db_) {
|
||||
this.db_ = gearsDb;
|
||||
this.userId_ = userId;
|
||||
this.name_ = name;
|
||||
google.wspl.GearsUtils.openDatabase(userId, name, this.db_,
|
||||
google.logger);
|
||||
} else {
|
||||
google.logger('openDatabase already invoked.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a worker to handle the database interactions. The worker will be
|
||||
* asynchronously started after the specified delay and will not be used until
|
||||
* the completion of any pending transaction.
|
||||
* @param {GearsWorkerPool} wp The Gears worker pool.
|
||||
* @param {string} workerUrl The URL to find the gears database worker.
|
||||
* @return {number} The worker ID.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.startWorker = function(wp, workerUrl) {
|
||||
this.wp_ = wp;
|
||||
|
||||
google.logger('Starting dbworker thread.');
|
||||
|
||||
this.workerId_ = wp.createWorkerFromUrl(workerUrl);
|
||||
|
||||
this.timeoutId_ = window.setTimeout(google.bind(this.handleTimeout_, this),
|
||||
google.wspl.gears.Database.TIMEOUT);
|
||||
|
||||
return this.workerId_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.Transaction#createTransaction
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.createTransaction = function(populate,
|
||||
opt_callback) {
|
||||
var transactionCallback = opt_callback || {
|
||||
onSuccess : function() {},
|
||||
onFailure : function() {}
|
||||
};
|
||||
|
||||
var id = this.transCount_++;
|
||||
var transaction = new google.wspl.gears.Transaction(id, this);
|
||||
|
||||
this.saveTransaction_(transaction, transactionCallback, populate);
|
||||
|
||||
this.queuedTransactions_.push(transaction.id_);
|
||||
this.nextTransaction_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the transaction and transaction callback to be accessed later when a
|
||||
* commit or rollback is performed.
|
||||
*
|
||||
* @param {google.wspl.gears.Transaction} transaction The transaction that the
|
||||
* callback belongs to.
|
||||
* @param {Object} callback A transaction callback with onSuccess and onFailure
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.saveTransaction_ = function(
|
||||
transaction, callback, populate) {
|
||||
this.transactions_[transaction.id_] = {
|
||||
transaction: transaction,
|
||||
callback: callback,
|
||||
populate: populate
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles incomming messages.
|
||||
* @param {string} a Deprecated.
|
||||
* @param {number} b Deprecated.
|
||||
* @param {Object} messageObject The message object.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.onMessage_ =
|
||||
function(a, b, messageObject) {
|
||||
var message = messageObject.body;
|
||||
|
||||
try {
|
||||
switch(message['type']) {
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.RESULT:
|
||||
this.handleResult_(message['results'], message['callbackId'],
|
||||
message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.FAILURE:
|
||||
this.handleFailure_(message['error'], message['callbackId'],
|
||||
message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.COMMIT:
|
||||
this.handleCommit_(message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.ROLLBACK:
|
||||
this.handleRollback_(message['transactionId']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.STARTED:
|
||||
this.handleStarted_();
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.OPEN_SUCCESSFUL:
|
||||
this.handleOpenSuccessful_();
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.OPEN_FAILED:
|
||||
this.handleOpenFailed_(message['error']);
|
||||
break;
|
||||
case google.wspl.gears.DbWorker.ReplyTypes.LOG:
|
||||
google.logger(message['msg']);
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
google.logger('Gears database failed: ' + ex.message, ex);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a new Gears database.
|
||||
*
|
||||
* @param {string} userId The user to which the database belongs.
|
||||
* @param {string} name The name of the database.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doOpen = function(userId, name) {
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.OPEN,
|
||||
'name': name,
|
||||
'userId': userId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Begins a new transaction on the Gears database.
|
||||
*
|
||||
* @param {number} transactionId The id of the transaction being committed.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doBegin = function(transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.db_.execute('BEGIN IMMEDIATE');
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.BEGIN,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Commits the current transaction on the Gears database. The transactionId
|
||||
* is used to invoke the callback associated with the transaction.
|
||||
*
|
||||
* @param {number} transactionId The id of the transaction being committed.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doCommit = function(transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.db_.execute('COMMIT');
|
||||
this.postCommit_();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.COMMIT,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Rolls the current transaction back on the Gears database. The transactionId
|
||||
* is used to invoke the callback associated with the transaction.
|
||||
*
|
||||
* @param {number} transactionId The id of the transaction being rolled back.
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doRollback = function(transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.db_.execute('ROLLBACK');
|
||||
this.postRollback_();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.ROLLBACK,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the Gears database. The transactionId and
|
||||
* callbackId are used to identify the callback that should be invoked when
|
||||
* handleResult or handleFailure is called.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements The group of statements to
|
||||
* execute
|
||||
* @param {number} callbackId The callback to invoke for each statement
|
||||
* @param {number} transactionId The transaction that the statements belong to
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doExecute = function(statements,
|
||||
callbackId,
|
||||
transactionId) {
|
||||
if (!this.useWorker_) {
|
||||
this.doExecuteSynchronously_(statements, callbackId, transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
var newStatements = [];
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
newStatements[i] = {
|
||||
'sql': statements[i].sql,
|
||||
'params': statements[i].params
|
||||
};
|
||||
}
|
||||
|
||||
this.sendMessageToWorker_({
|
||||
'type': google.wspl.gears.DbWorker.CommandTypes.EXECUTE,
|
||||
'statements': newStatements,
|
||||
'callbackId': callbackId,
|
||||
'transactionId': transactionId
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the synchronous Gears databse.
|
||||
* @param {Array.<google.wspl.Statement>} statements
|
||||
* @param {number} callbackId
|
||||
* @param {number} transactionId
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.doExecuteSynchronously_ =
|
||||
function(statements, callbackId, transactionId) {
|
||||
var db = this;
|
||||
var results = [];
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
try {
|
||||
var resultset = this.db_.execute(statements[i].sql, statements[i].params);
|
||||
var result = google.wspl.GearsUtils.resultSetToObjectArray(resultset);
|
||||
results.push(result);
|
||||
} catch (e) {
|
||||
var error = e;
|
||||
function failureCallback() {
|
||||
db.handleFailure_(error, callbackId, transactionId);
|
||||
};
|
||||
this.setTimeout_(failureCallback, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function resultCallback() {
|
||||
db.handleResult_(results, callbackId, transactionId);
|
||||
};
|
||||
this.setTimeout_(resultCallback, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a RESULT message from the worker thread.
|
||||
*
|
||||
* @param {!Array.<!Array.<Object>>} results A Gears result set.
|
||||
* @param {number} callbackId The callback to invoke.
|
||||
* @param {number} transactionId The transaction that the statement is executing
|
||||
* in.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleResult_ = function(results,
|
||||
callbackId, transactionId) {
|
||||
var transInfo = this.transactions_[transactionId];
|
||||
if (transInfo) {
|
||||
for (var i = 0, l = results.length; i < l; i++) {
|
||||
var resultSet = new google.wspl.gears.ResultSet(results[i]);
|
||||
transInfo.transaction.success(resultSet, callbackId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a FAILURE message from the worker thread.
|
||||
*
|
||||
* @param {Error} error An error produced by the Gears database
|
||||
* @param {number} callbackId The callback to invoke
|
||||
* @param {number} transactionId The transaction that the statement is executing
|
||||
* in
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleFailure_ = function(error,
|
||||
callbackId, transactionId) {
|
||||
var transInfo = this.transactions_[transactionId];
|
||||
if (transInfo) {
|
||||
transInfo.error = error;
|
||||
transInfo.transaction.failure(error, callbackId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a COMMIT message from the worker thread.
|
||||
*
|
||||
* @param {number} id The transaction id.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleCommit_ = function(id) {
|
||||
var transaction = this.removeTransaction_(id);
|
||||
if (transaction) {
|
||||
transaction.callback.onSuccess();
|
||||
}
|
||||
|
||||
this.nextTransaction_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the completion of a commit from the synchronous database.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.postCommit_ = function() {
|
||||
this.handleCommit_(this.currentTransactionId_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a ROLLBACK message from the worker thread.
|
||||
*
|
||||
* @param {number} id The transaction id
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleRollback_ = function(id) {
|
||||
var transaction = this.removeTransaction_(id);
|
||||
if (transaction) {
|
||||
transaction.callback.onFailure(transaction.error);
|
||||
}
|
||||
|
||||
this.nextTransaction_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the completion of a rollback from the synchronous database.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.postRollback_ = function() {
|
||||
this.handleRollback_(this.currentTransactionId_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a STARTED message from the worker thread.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleStarted_ = function() {
|
||||
if (!this.workerTimeout_) {
|
||||
google.logger('Dbworker started.');
|
||||
window.clearTimeout(this.timeoutId_);
|
||||
this.timeoutId_ = 0;
|
||||
this.doOpen(this.userId_, this.name_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a timeout of waiting for a STARTED message from the worker thread.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleTimeout_ = function() {
|
||||
this.workerTimeout_ = true;
|
||||
google.logger('Timed out while waiting for the dbworker to start.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a OPEN_SUCCESSFUL message from the worker thread.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleOpenSuccessful_ = function() {
|
||||
this.workerReady_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a OPEN_FAILED message from the worker thread.
|
||||
* @param {string} error
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.handleOpenFailed_ = function(error) {
|
||||
google.logger('Worker failed to open Gears database.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the next transaction if there is one queued.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.nextTransaction_ = function() {
|
||||
if (this.queuedTransactions_.length && !this.locked_) {
|
||||
this.locked_ = true;
|
||||
|
||||
if (this.workerReady_ && !this.useWorker_) {
|
||||
this.useWorker_ = true;
|
||||
google.logger('Switching to asynchronous database interface.');
|
||||
}
|
||||
|
||||
var id = this.queuedTransactions_.shift();
|
||||
this.currentTransactionId_ = id;
|
||||
var transactionData = this.transactions_[id];
|
||||
|
||||
var db = this;
|
||||
function populate() {
|
||||
transactionData.populate(transactionData.transaction);
|
||||
|
||||
// If populate did not execute statements on the database, invoke the
|
||||
// success callback and process the next transaction.
|
||||
if (!transactionData.transaction.isExecuting()) {
|
||||
db.handleCommit_(id);
|
||||
}
|
||||
};
|
||||
|
||||
this.setTimeout_(populate, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans up the transaction and transaction callback for the id specified.
|
||||
*
|
||||
* @param {number} id The transaction id.
|
||||
* @return {google.wspl.Transaction} The transaction and callback in an object.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.removeTransaction_ = function(id) {
|
||||
this.locked_ = false;
|
||||
var transaction = this.transactions_[id];
|
||||
if (transaction) {
|
||||
delete this.transactions_[id];
|
||||
}
|
||||
return transaction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a function using window's setTimeout.
|
||||
* @param {Function} func The function to execute.
|
||||
* @param {number} time The time delay before invocation.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.setTimeout_ = function(func, time) {
|
||||
if (this.synchronous_) {
|
||||
func();
|
||||
} else {
|
||||
window.setTimeout(func, time);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to the database worker thread.
|
||||
* @param {Object} msg The message object to send.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Database.prototype.sendMessageToWorker_ = function(msg) {
|
||||
this.wp_.sendMessage(msg, this.workerId_);
|
||||
};
|
|
@ -0,0 +1,404 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears database wrapper tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="gearsutils.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="gears_resultset.js"></script>
|
||||
<script type="text/javascript" src="gears_transaction.js"></script>
|
||||
<script type="text/javascript" src="dbworker.js"></script>
|
||||
<script type="text/javascript" src="dbwrapper_gears.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var callbackMock;
|
||||
var dbMock;
|
||||
var wp;
|
||||
var workerId = 123;
|
||||
var transMock;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
|
||||
callbackMock = mockControl.createMock({
|
||||
onSuccess: function(){},
|
||||
onFailure: function(){}
|
||||
});
|
||||
|
||||
transMock = mockControl.createMock({
|
||||
success: function(){},
|
||||
failure: function(){}
|
||||
});
|
||||
|
||||
wp = mockControl.createMock({
|
||||
allowCrossOrigin: function(){},
|
||||
createWorkerFromUrl: function(){},
|
||||
sendMessage: function(){}
|
||||
});
|
||||
|
||||
dbMock = mockControl.createMock({
|
||||
execute: function(){},
|
||||
open: function(){},
|
||||
close: function(){},
|
||||
remove: function(){}
|
||||
});
|
||||
|
||||
google.wspl.GearsUtils.resultSetToObjectArray = function(rs) {
|
||||
return rs;
|
||||
};
|
||||
}
|
||||
|
||||
function buildSyncDatabase() {
|
||||
var database = new google.wspl.gears.Database(true);
|
||||
database.userId_ = 'userId';
|
||||
database.name_ = 'name';
|
||||
database.db_ = dbMock;
|
||||
return database;
|
||||
}
|
||||
|
||||
function buildDatabase() {
|
||||
var database = buildSyncDatabase();
|
||||
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.OPEN;
|
||||
wp.expects().createWorkerFromUrl('workerUrl').andReturn(workerId);
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('Wrong message type.', type, arguments[0].type);
|
||||
assertEquals('Incorrect name.', 'name', arguments[0].name);
|
||||
assertEquals('Incorrect user id.', 'userId', arguments[0].userId);
|
||||
});
|
||||
|
||||
database.startWorker(wp, 'workerUrl', 0);
|
||||
database.handleStarted_();
|
||||
database.handleOpenSuccessful_();
|
||||
database.useWorker_ = true;
|
||||
return database;
|
||||
}
|
||||
|
||||
function testConstructor() {
|
||||
var db = buildSyncDatabase();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testStartWorker() {
|
||||
var db = buildDatabase();
|
||||
assertTrue('Expected worker to be ready.', db.workerReady_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateTransaction() {
|
||||
var db = buildDatabase();
|
||||
var tx;
|
||||
var populateMock = function(txa) {
|
||||
tx = txa;
|
||||
var transactions = db.transactions_;
|
||||
assertEquals('missing transaction', tx, transactions[tx.id_].transaction);
|
||||
assertEquals('missing callback', callbackMock,
|
||||
transactions[tx.id_].callback);
|
||||
assertEquals('database not saved', db, tx.db_);
|
||||
assertTrue('database should be locked', db.locked_);
|
||||
};
|
||||
|
||||
callbackMock.expects().onSuccess();
|
||||
|
||||
var transactions = db.transactions_;
|
||||
db.createTransaction(populateMock, callbackMock);
|
||||
assertEquals('failed to clean up transaction', undefined,
|
||||
transactions[tx.id_]);
|
||||
assertFalse('database should not be locked', db.locked_);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testMultipleTransactions() {
|
||||
var db = buildDatabase();
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('populate');
|
||||
handler.addMockMethod('onSuccess');
|
||||
handler.addMockMethod('onFailure');
|
||||
|
||||
var trans;
|
||||
handler.expects().populate(TypeOf.isA(google.wspl.gears.Transaction)).andStub(
|
||||
function(tx) {
|
||||
trans = tx;
|
||||
tx.numActiveExecutes_ = 1;
|
||||
});
|
||||
|
||||
db.createTransaction(handler.populate, handler);
|
||||
db.createTransaction(handler.populate, handler);
|
||||
mockControl.verify();
|
||||
|
||||
handler.expects().onSuccess();
|
||||
handler.expects().populate(TypeOf.isA(google.wspl.gears.Transaction)).andStub(
|
||||
function(tx) {
|
||||
trans = tx;
|
||||
tx.numActiveExecutes_ = 1;
|
||||
});
|
||||
|
||||
db.handleCommit_(trans.id_);
|
||||
mockControl.verify();
|
||||
|
||||
handler.expects().onFailure(undefined).andStub(function() {
|
||||
db.createTransaction(handler.populate, handler);
|
||||
});
|
||||
handler.expects().populate(TypeOf.isA(google.wspl.gears.Transaction)).andStub(
|
||||
function(tx) {
|
||||
trans = tx;
|
||||
tx.numActiveExecutes_ = 1;
|
||||
});
|
||||
|
||||
db.handleRollback_(trans.id_);
|
||||
mockControl.verify();
|
||||
|
||||
handler.expects().onSuccess();
|
||||
|
||||
db.handleCommit_(trans.id_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoBegin() {
|
||||
var db = buildDatabase();
|
||||
var transactionId = 10;
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.BEGIN;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertNotUndefined('Missing transaction id.', arguments[0].transactionId);
|
||||
});
|
||||
db.doBegin(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousBegin() {
|
||||
var db = buildSyncDatabase();
|
||||
dbMock.expects().execute('BEGIN IMMEDIATE');
|
||||
db.doBegin(1);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoExecute() {
|
||||
var statements = [{sql: 's1', params: 'p1'},
|
||||
{sql: 's2', params: 'p2'},
|
||||
{sql: 's3', params: 'p3'}];
|
||||
var callbackId = 5;
|
||||
var transactionId = 10;
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.EXECUTE;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('statements do not match', statements.length,
|
||||
arguments[0].statements.length);
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
assertEquals('a statement sql does not match', statements[i].sql,
|
||||
arguments[0].statements[i].sql);
|
||||
assertEquals('a statement params does not match', statements[i].params,
|
||||
arguments[0].statements[i].params);
|
||||
}
|
||||
assertEquals('missing callback id', callbackId, arguments[0].callbackId);
|
||||
assertEquals('missing trans id', transactionId, arguments[0].transactionId);
|
||||
});
|
||||
db.doExecute(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousExecute() {
|
||||
var statements = [{sql: 's1', params: 'p1'},
|
||||
{sql: 's2', params: 'p2'},
|
||||
{sql: 's3', params: 'p3'}];
|
||||
var callbackId = 5;
|
||||
var transactionId = 10;
|
||||
var db = buildSyncDatabase();
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var stat = statements[i];
|
||||
dbMock.expects().execute(stat.sql, stat.params).andReturn('result' + i);
|
||||
}
|
||||
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('handleResult_');
|
||||
db.handleResult_ = handler.handleResult_;
|
||||
handler.expects().handleResult_(
|
||||
TypeOf.isA(Array), callbackId, transactionId).andStub(function(results) {
|
||||
var expected = ['result0', 'result1', 'result2'];
|
||||
assertArrayEquals('Wrong results.', expected, results);
|
||||
});
|
||||
|
||||
db.doExecute(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousExecute_failure() {
|
||||
var statements = [{sql: 's1', params: 'p1'},
|
||||
{sql: 's2', params: 'p2'},
|
||||
{sql: 's3', params: 'p3'}];
|
||||
var callbackId = 5;
|
||||
var transactionId = 10;
|
||||
var db = buildSyncDatabase();
|
||||
|
||||
dbMock.expects().execute('s1', 'p1').andReturn('result0');
|
||||
dbMock.expects().execute('s2', 'p2').andThrow(Error('db error'));
|
||||
|
||||
var handler = mockControl.createMock();
|
||||
handler.addMockMethod('handleFailure_');
|
||||
db.handleFailure_ = handler.handleFailure_;
|
||||
handler.expects().handleFailure_(
|
||||
TypeOf.isA(Error), callbackId, transactionId);
|
||||
|
||||
db.doExecute(statements, callbackId, transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoCommit() {
|
||||
var transactionId = 10;
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.COMMIT;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('missing trans id', transactionId, arguments[0].transactionId);
|
||||
});
|
||||
db.doCommit(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousCommit() {
|
||||
var db = buildSyncDatabase();
|
||||
dbMock.expects().execute('COMMIT');
|
||||
db.doCommit(1);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDoRollback() {
|
||||
var transactionId = 10;
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.ROLLBACK;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('missing trans id', transactionId, arguments[0].transactionId);
|
||||
});
|
||||
db.doRollback(transactionId);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testSynchronousRollback() {
|
||||
var db = buildSyncDatabase();
|
||||
dbMock.expects().execute('ROLLBACK');
|
||||
db.doRollback(1);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleCommit() {
|
||||
var db = buildDatabase();
|
||||
db.transactions_[5] = {transaction: 'tx', callback: callbackMock};
|
||||
callbackMock.expects().onSuccess();
|
||||
db.handleCommit_(5);
|
||||
assertUndefined('failed to remove transaction', db.transactions_[5]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleRollback() {
|
||||
var db = buildDatabase();
|
||||
db.transactions_[5] = {
|
||||
transaction: 'tx',
|
||||
callback: callbackMock,
|
||||
error: Error('error')
|
||||
};
|
||||
callbackMock.expects().onFailure(TypeOf.isA(Error)).andStub(function() {
|
||||
assertEquals('did not pass error', 'error', arguments[0].message);
|
||||
});
|
||||
db.handleRollback_(5);
|
||||
assertUndefined('failed to remove transaction', db.transactions_[5]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleResult() {
|
||||
var db = buildDatabase();
|
||||
transMock.expects().success(TypeOf.isA(google.wspl.gears.ResultSet),
|
||||
22).andStub(function() {
|
||||
assertEquals('result not set', 'result1', arguments[0].resultArray_);
|
||||
});
|
||||
transMock.expects().success(TypeOf.isA(google.wspl.gears.ResultSet),
|
||||
22).andStub(function() {
|
||||
assertEquals('result not set', 'result2', arguments[0].resultArray_);
|
||||
});
|
||||
db.transactions_[5] = {transaction: transMock, callback: 'cb'};
|
||||
|
||||
db.handleResult_(['result1', 'result2'], 22, 5);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleFailure() {
|
||||
var db = buildDatabase();
|
||||
transMock.expects().failure(TypeOf.isA(Error), 22).andStub(function() {
|
||||
assertEquals('error not set', 'error', arguments[0].message);
|
||||
});
|
||||
db.transactions_[5] = {transaction: transMock, callback: 'cb'};
|
||||
|
||||
db.handleFailure_(Error('error'), 22, 5);
|
||||
assertEquals('failed to save error', 'error',
|
||||
db.transactions_[5].error.message);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleStarted() {
|
||||
var db = buildDatabase();
|
||||
var type = google.wspl.gears.DbWorker.CommandTypes.OPEN;
|
||||
wp.expects().sendMessage(TypeOf.isA(Object), workerId).andStub(function() {
|
||||
assertEquals('wrong message type', type, arguments[0].type);
|
||||
assertEquals('wrong db userId', 'userId', arguments[0].userId);
|
||||
assertEquals('wrong db name', 'name', arguments[0].name);
|
||||
});
|
||||
db.handleStarted_();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleStarted_afterTimeout() {
|
||||
var db = buildDatabase();
|
||||
db.handleTimeout_();
|
||||
|
||||
// This should do nothing.
|
||||
db.handleStarted_();
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleTimeout() {
|
||||
var db = buildDatabase();
|
||||
db.handleTimeout_();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpenSuccessful() {
|
||||
var db = buildDatabase();
|
||||
db.handleOpenSuccessful_();
|
||||
assertTrue('Worker should be ready.', db.workerReady_);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testHandleOpenFailed() {
|
||||
var db = buildDatabase();
|
||||
db.handleOpenFailed_('error');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Generic Database API.
|
||||
*
|
||||
* A small set of classes to define how we interact with databases that
|
||||
* can easily be implemented on top of HTML5.
|
||||
*/
|
||||
|
||||
google.wspl.html5 = google.wspl.html5 || {};
|
||||
|
||||
/**
|
||||
* Specification's default largest database size in HTML5 databases.
|
||||
* @type{number}
|
||||
*/
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE = 1024 * 1024 * 4;
|
||||
|
||||
/**
|
||||
* Creates an HTML5 Transaction object.
|
||||
* @see google.wspl.Transaction#Transaction
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.Transaction
|
||||
*
|
||||
* @param {SQLTransaction} html5tx The HTML5 implementation of transactions.
|
||||
*/
|
||||
google.wspl.html5.Transaction = function(html5tx) {
|
||||
this.tx_ = html5tx;
|
||||
};
|
||||
google.inherits(google.wspl.html5.Transaction, google.wspl.Transaction);
|
||||
|
||||
/**
|
||||
* Runs an array of statements in a single database transaction.
|
||||
* Invokes the onSuccess callback once for each succeesfully executed
|
||||
* statement and
|
||||
* once for the first failed statement.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements The statements to
|
||||
* execute.
|
||||
* @param {Object?} opt_callback An object containing onSuccess and onFailure
|
||||
* handlers.
|
||||
*/
|
||||
google.wspl.html5.Transaction.prototype.executeAll = function(statements,
|
||||
opt_callback) {
|
||||
if (statements.length == 0) {
|
||||
throw Error('Possibly silly attempt to execute empty statement list.');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
for (var i = 0; i < statements.length; ++i) {
|
||||
var statement = statements[i];
|
||||
google.logger('SQL: ' + statement.sql + ' PARAMS: ' + statement.params);
|
||||
this.tx_.executeSql(statement.sql, statement.params,
|
||||
function(tx, result) {
|
||||
if (opt_callback && opt_callback.onSuccess) {
|
||||
var resultSet = new google.wspl.html5.ResultSet(result);
|
||||
opt_callback.onSuccess(self, resultSet);
|
||||
}
|
||||
},
|
||||
function(tx, error) {
|
||||
if (opt_callback && opt_callback.onFailure) {
|
||||
opt_callback.onFailure(error);
|
||||
}
|
||||
// fail the whole transaction if any step fails
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.Database#Database
|
||||
* @param {string} name The name for this database.
|
||||
* @param {window} opt_window A window object for dependency injection.
|
||||
* @constructor
|
||||
* @extends google.wspl.Database
|
||||
*/
|
||||
google.wspl.html5.Database = function(name, opt_window) {
|
||||
/**
|
||||
* Sequence number for transactions.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.sequenceNum_ = 1;
|
||||
|
||||
/**
|
||||
* Map of transactionIds -> transaction start time in millis.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.inflightTransactions_ = {};
|
||||
|
||||
var win = opt_window || window;
|
||||
this.db_ = win.openDatabase(name, '',
|
||||
name, google.wspl.LARGEST_SUPPORTED_DATABASE);
|
||||
if (this.db_ == null) {
|
||||
throw Error('The returned database was null.');
|
||||
}
|
||||
};
|
||||
google.inherits(google.wspl.html5.Database, google.wspl.Database);
|
||||
|
||||
/**
|
||||
* @see google.wspl.Database#createTransaction
|
||||
*/
|
||||
google.wspl.html5.Database.prototype.createTransaction = function(populate,
|
||||
opt_callback) {
|
||||
var transactionCallback = opt_callback || {
|
||||
onSuccess: function() {},
|
||||
onFailure: function() {}
|
||||
};
|
||||
|
||||
var transactionId = this.sequenceNum_++;
|
||||
var inflightTransactions = this.inflightTransactions_;
|
||||
inflightTransactions[transactionId] = this.getCurrentTime();
|
||||
this.db_.transaction(
|
||||
function(tx) {
|
||||
// Delete the transaction before the executing it because our
|
||||
// definition of an 'in-flight' transaction is the time between
|
||||
// when the request was made and when the database starts to
|
||||
// execute the transaction.
|
||||
delete inflightTransactions[transactionId];
|
||||
populate(new google.wspl.html5.Transaction(tx));
|
||||
},
|
||||
function(error) {transactionCallback.onFailure(error);},
|
||||
function() {transactionCallback.onSuccess();});
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if there is an in-flight database transaction that's older than
|
||||
* the given time period.
|
||||
* @param {number} olderThanMillis The time period.
|
||||
* @return {boolean} True if the database has an in-flight transaction older
|
||||
* than the given time period, false otherwise.
|
||||
*/
|
||||
google.wspl.html5.Database.prototype.hasInflightTransactions =
|
||||
function(olderThanMillis) {
|
||||
for (var transactionId in this.inflightTransactions_) {
|
||||
var startTime = this.inflightTransactions_[transactionId];
|
||||
if (this.getCurrentTime() - startTime > olderThanMillis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current time.
|
||||
* @return {number} The current time in millis.
|
||||
*/
|
||||
google.wspl.html5.Database.prototype.getCurrentTime = function() {
|
||||
// The iPhone does not support Date.now()
|
||||
var d = new Date();
|
||||
return d.getTime();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an HTML5 ResultSet object.
|
||||
* @see google.wspl.ResultSet#ResultSet
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.ResultSet
|
||||
*
|
||||
* @param {Object} html5_result The HTML5 implementation of result set.
|
||||
*/
|
||||
google.wspl.html5.ResultSet = function(html5_result) {
|
||||
this.result_ = html5_result;
|
||||
this.index_ = 0;
|
||||
};
|
||||
google.inherits(google.wspl.html5.ResultSet, google.wspl.ResultSet);
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#isValidRow
|
||||
*/
|
||||
google.wspl.html5.ResultSet.prototype.isValidRow = function() {
|
||||
return this.index_ >= 0 && this.index_ < this.result_.rows.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#next
|
||||
*/
|
||||
google.wspl.html5.ResultSet.prototype.next = function() {
|
||||
this.index_ ++;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#getRow
|
||||
*/
|
||||
google.wspl.html5.ResultSet.prototype.getRow = function() {
|
||||
return this.result_.rows.item(this.index_);
|
||||
};
|
|
@ -0,0 +1,468 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Database wrapper tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="dbwrapper_html5.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var html5Tx;
|
||||
var callbackMock;
|
||||
var html5Window;
|
||||
var html5Db;
|
||||
var populateMock;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
html5Tx = mockControl.createMock({executeSql: function(){}});
|
||||
callbackMock = mockControl.createMock({onSuccess: function(){},
|
||||
onFailure: function(){}});
|
||||
html5Window = mockControl.createMock({openDatabase: function(){}});
|
||||
html5Db = mockControl.createMock({transaction: function(){}});
|
||||
populateMock = mockControl.createMock({populate: function(){}});
|
||||
|
||||
dbTx = mockControl.createMock({execute: function(){}, executeAll:
|
||||
function(){}});
|
||||
dbRows = mockControl.createMock({item: function(){}});
|
||||
}
|
||||
|
||||
function testConstructTransaction() {
|
||||
var trans = new google.wspl.html5.Transaction('foo');
|
||||
assertEquals('transaction instance not set correctly', 'foo', trans.tx_);
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll() {
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
try {
|
||||
trans.executeAll([], callbackMock);
|
||||
fail('Should never get here');
|
||||
} catch(e) {
|
||||
assertEquals(
|
||||
'did not exception fault on empty statement list',
|
||||
'Error: Possibly silly attempt to execute empty statement list.',
|
||||
e.toString());
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
/*
|
||||
The sequence of digits at the end of the test names indicate as boolean
|
||||
variables the success or failure of each statement.
|
||||
*/
|
||||
function testTransactionExecuteAll_1() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback = null;
|
||||
var failureCallback = null;
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback = arguments[2];
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset', arguments[1].result_)});
|
||||
|
||||
trans.executeAll([stat1], callbackMock);
|
||||
successCallback(html5Tx, 'resultset');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_11() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var failureCallback = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset1', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset2', arguments[1].result_)});
|
||||
|
||||
trans.executeAll([stat1, stat2], callbackMock);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_111() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var successCallback3 = null;
|
||||
var failureCallback = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat3', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback3 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 2, params[0]);
|
||||
assertEquals('incorrect params to sql', 3, params[1]);
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset1', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset2', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset3', arguments[1].result_)});
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3], callbackMock);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
successCallback3(html5Tx, 'resultset3');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_noCallback_111() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var successCallback3 = null;
|
||||
var failureCallback = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(
|
||||
function() { successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat3', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(
|
||||
function() { successCallback3 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 2, params[0]);
|
||||
assertEquals('incorrect params to sql', 3, params[1]);
|
||||
});
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3]);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
successCallback3(html5Tx, 'resultset3');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecuteAll_110() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback1 = null;
|
||||
var successCallback2 = null;
|
||||
var successCallback3 = null;
|
||||
var failureCallback3 = null;
|
||||
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback1 = arguments[2];
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat2', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback2 = arguments[2];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 1, params[0]);
|
||||
});
|
||||
|
||||
html5Tx.expects().executeSql('stat3', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback3 = arguments[2];
|
||||
failureCallback3 = arguments[3];
|
||||
var params = arguments[1];
|
||||
assertEquals('incorrect params to sql', 2, params[0]);
|
||||
assertEquals('incorrect params to sql', 3, params[1]);
|
||||
});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset1', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onSuccess(trans,
|
||||
TypeOf.isA(google.wspl.html5.ResultSet)).andStub(function() {
|
||||
assertEquals('result not returned', 'resultset2', arguments[1].result_)});
|
||||
|
||||
callbackMock.expects().onFailure('error3');
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3], callbackMock);
|
||||
successCallback1(html5Tx, 'resultset1');
|
||||
successCallback2(html5Tx, 'resultset2');
|
||||
assertTrue('failure case callback not terminating transaction',
|
||||
failureCallback3(html5Tx, 'error3'));
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testTransactionExecute_nocallback() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
|
||||
var trans = new google.wspl.html5.Transaction(html5Tx);
|
||||
|
||||
var successCallback = null;
|
||||
var failureCallback = null;
|
||||
html5Tx.expects().executeSql('stat1', TypeOf.isA(Array),
|
||||
TypeOf.isA(Function), TypeOf.isA(Function)).andStub(function() {
|
||||
successCallback = arguments[2];
|
||||
});
|
||||
|
||||
trans.execute(stat1);
|
||||
successCallback(html5Tx, 'resultset');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function buildDatabase() {
|
||||
html5Window.expects().openDatabase('name', '', 'name',
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE).
|
||||
andReturn(html5Db);
|
||||
return new google.wspl.html5.Database('name', html5Window);
|
||||
}
|
||||
|
||||
function testConstructDatabase() {
|
||||
var db = buildDatabase();
|
||||
mockControl.verify();
|
||||
assertEquals('did not set db_ correctly', db.db_, html5Db);
|
||||
}
|
||||
|
||||
function testConstructDatabase_null() {
|
||||
html5Window.expects().openDatabase('name', '', 'name',
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE).
|
||||
andReturn(null);
|
||||
try {
|
||||
var db = google.wspl.html5.Database('name', html5Window);
|
||||
fail('Should never get here');
|
||||
} catch (e) {
|
||||
if (e.isJsUnitException) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testConstructDatabase_undefined() {
|
||||
html5Window.expects().openDatabase('name', '', 'name',
|
||||
google.wspl.LARGEST_SUPPORTED_DATABASE).
|
||||
andReturn(undefined);
|
||||
try {
|
||||
var db = google.wspl.html5.Database('name', html5Window);
|
||||
fail('Should never get here');
|
||||
} catch (e) {
|
||||
if (e.isJsUnitException) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function createTransactionCore() {
|
||||
var db = buildDatabase();
|
||||
var fun1 = null;
|
||||
var failure = null;
|
||||
var success = null;
|
||||
html5Db.expects().transaction(TypeOf.isA(Function), TypeOf.isA(Function),
|
||||
TypeOf.isA(Function)).andStub(function() {
|
||||
fun1 = arguments[0];
|
||||
failure = arguments[1];
|
||||
success = arguments[2];
|
||||
});
|
||||
|
||||
var tx = null;
|
||||
var populateMock = function(txa) {
|
||||
tx = txa;
|
||||
};
|
||||
|
||||
db.createTransaction(populateMock, callbackMock);
|
||||
fun1('transactionTest');
|
||||
|
||||
assertEquals('transaction not saved', 'transactionTest', tx.tx_);
|
||||
assertEquals('did not set db_ correctly', db.db_, html5Db);
|
||||
|
||||
return {failure: failure, success: success};
|
||||
}
|
||||
|
||||
function testCreateTransaction_success() {
|
||||
cbs = createTransactionCore();
|
||||
callbackMock.expects().onSuccess();
|
||||
cbs.success('success');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateTransaction_failure() {
|
||||
cbs = createTransactionCore();
|
||||
callbackMock.expects().onFailure('error');
|
||||
cbs.failure('error');
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateTransaction_onlyPopulate() {
|
||||
cbs = createTransactionCore();
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testDatabaseExecute_nocallback() {
|
||||
var db = buildDatabase();
|
||||
|
||||
dbTx.expects().execute('statementText', null);
|
||||
db.execute('statementText');
|
||||
}
|
||||
|
||||
function testDatabaseExecute_nocallback() {
|
||||
var db = buildDatabase();
|
||||
|
||||
dbTx.expects().execute('statementText', null);
|
||||
db.execute('statementText');
|
||||
}
|
||||
|
||||
function testDatabaseExecuteAll() {
|
||||
var db = buildDatabase();
|
||||
dbTx.expects().executeAll(TypeOf.isA(Array));
|
||||
}
|
||||
|
||||
function testResultSetNext() {
|
||||
var res = {rows: {length: 4}};
|
||||
var result = new google.wspl.html5.ResultSet(res);
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
assertTrue('expected valid row', result.isValidRow());
|
||||
result.next();
|
||||
}
|
||||
assertFalse('expected invalid row', result.isValidRow());
|
||||
}
|
||||
|
||||
function testResultSetIsValidRow() {
|
||||
var res = {rows: {length: 0}};
|
||||
var result = new google.wspl.html5.ResultSet(res);
|
||||
|
||||
assertFalse('expected invalid row', result.isValidRow());
|
||||
res.rows.length = 1;
|
||||
assertTrue('expected valid row', result.isValidRow());
|
||||
result.next();
|
||||
assertFalse('expected invalid row', result.isValidRow());
|
||||
}
|
||||
|
||||
function testResultSetGetRow() {
|
||||
var res = {rows: {
|
||||
length: 3,
|
||||
item: function(index) {
|
||||
assertEquals('expected index of 1', 1, index);
|
||||
return {h0: 'value0', h1: 'value1', h2: 'value2'};
|
||||
}
|
||||
}};
|
||||
|
||||
var result = new google.wspl.html5.ResultSet(res);
|
||||
result.next();
|
||||
var row = result.getRow();
|
||||
assertEquals('first field is not valid', 'value0', row.h0);
|
||||
assertEquals('first field is not valid', 'value1', row.h1);
|
||||
assertEquals('first field is not valid', 'value2', row.h2);
|
||||
}
|
||||
|
||||
function testHasInflightTransactions() {
|
||||
var fakeTime = 1000;
|
||||
var db = buildDatabase();
|
||||
var dummyPopulate = function() {};
|
||||
db.getCurrentTime = function() {return fakeTime;};
|
||||
|
||||
// Create a transaction, make sure it's kept track of.
|
||||
var transId = db.sequenceNum_;
|
||||
db.createTransaction(dummyPopulate);
|
||||
assertEquals('There should be an inflight request.',
|
||||
fakeTime, db.inflightTransactions_[transId]);
|
||||
|
||||
// Haven't advanced time, so hasInflightTransactions should return false.
|
||||
assertFalse('Expected hasInflightTransactions=false.',
|
||||
db.hasInflightTransactions(100));
|
||||
|
||||
// Advance time, now hasInflightTransactions should return true.
|
||||
fakeTime += 10000;
|
||||
var faqs = db.hasInflightTransactions(100);
|
||||
assertTrue('Expected hasInflightTransactions=true.',
|
||||
db.hasInflightTransactions(100));
|
||||
|
||||
// Now now have the transaction completed, hasInflightTransactions
|
||||
// should return false.
|
||||
delete db.inflightTransactions_[transId];
|
||||
assertFalse('Expected hasInflightTransactions=false.',
|
||||
db.hasInflightTransactions(100));
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
202
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/dbwrapperapi.js
Executable file
202
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/dbwrapperapi.js
Executable file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Generic Database API.
|
||||
*
|
||||
* A small set of classes to define how we interact with databases that can
|
||||
* easily be implemented on top of HTML5 and Gears. The classes in this file
|
||||
* should be extended to provide the missing method implementations and more
|
||||
* sophisticated constructors where applicable.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a Statement object. A Statement is an SQL statement paired
|
||||
* with the parameters needed to execute it.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!string} sql The SQL statement.
|
||||
* @param {Array.<Object>?} opt_params The parameters for the SQL statement.
|
||||
*/
|
||||
google.wspl.Statement = function(sql, opt_params) {
|
||||
|
||||
/**
|
||||
* The SQL statement with '?' in place of parameters.
|
||||
* @type {string}
|
||||
*/
|
||||
this.sql = sql;
|
||||
|
||||
/**
|
||||
* The parameters to use with the SQL statement.
|
||||
* @type {!Array}
|
||||
*/
|
||||
this.params = opt_params || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new statement object from the given statement with the parameters
|
||||
* set as specified.
|
||||
* @param {Array.<Object>} params The array of values for ? placeholders.
|
||||
* @return {!google.wspl.Statement} The created Statement.
|
||||
*/
|
||||
google.wspl.Statement.prototype.createStatement = function(params) {
|
||||
return new google.wspl.Statement(this.sql, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a Transaction object. Transaction objects
|
||||
* group together a series of statements into a single atomic
|
||||
* action on the database.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.Transaction = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a statement and an optional callback object and
|
||||
* runs the statement on the database. The callback can be used to
|
||||
* add more statements to the same transaction, or execute can be
|
||||
* called repeatedly and the transactions will later execute in the
|
||||
* order provided.
|
||||
*
|
||||
* @param {google.wspl.Statement} statement The statement to execute.
|
||||
* @param {Object} opt_callback An object containing onSuccess and onFailure
|
||||
* handlers.
|
||||
*/
|
||||
google.wspl.Transaction.prototype.execute = function(statement,
|
||||
opt_callback) {
|
||||
this.executeAll([statement], opt_callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs an array of statements in a single database transaction.
|
||||
* Invokes the onSuccess callback once for each successfully executed
|
||||
* statement and once for the first failed statement. The callback can be
|
||||
* used to add more statements to the same transaction, or executeAll can
|
||||
* be called repeatedly and each block of statements given will execute
|
||||
* in the same order as the sequence of calls to executeAll.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements The statements to
|
||||
* execute.
|
||||
* @param {Object?} opt_callback An object containing onSuccess and onFailure
|
||||
* handlers.
|
||||
*/
|
||||
google.wspl.Transaction.prototype.executeAll = function(statements,
|
||||
opt_callback) {
|
||||
throw Error('executeAll not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a Database object. Database objects are handles that allow
|
||||
* access to a database (and create the corresponding database if it doesn't
|
||||
* already exist). To open the database, pass the name of the needed
|
||||
* database to the constructor, and then execute transactions on it using
|
||||
* the execute method.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.Database = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a transaction object that can execute a series of SQL statements
|
||||
* atomically.
|
||||
*
|
||||
* @param {Function} populate A callback to run execute calls on the
|
||||
* transaction.
|
||||
* @param {Object?} opt_callback An optional success/failure callback that is
|
||||
* called when the entire transaction is finished executing.
|
||||
*/
|
||||
google.wspl.Database.prototype.createTransaction = function(populate,
|
||||
opt_callback) {
|
||||
throw Error('createTransaction not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the database, invoking the optional
|
||||
* callback after statement execution and the optional transactionCallback upon
|
||||
* completion of the transaction.
|
||||
*
|
||||
* @param {google.wspl.Statement} statement the statement to execute
|
||||
* @param {Object?} opt_callback object that defines onSuccess and onFailure
|
||||
* @param {Object?} opt_transactionCallback object that defines onSuccess and
|
||||
* onFailure
|
||||
*/
|
||||
google.wspl.Database.prototype.execute = function(statement,
|
||||
opt_callback,
|
||||
opt_transactionCallback) {
|
||||
this.createTransaction(function(tx) {
|
||||
tx.execute(statement, opt_callback);
|
||||
}, opt_transactionCallback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes an array of statements on the database, invoking the optional
|
||||
* callback for each statement in the transaction. In the case of a statement
|
||||
* failure, only the first failed statement will be reported and the transaction
|
||||
* will be rolled back. This method invokes the optional transactionCallback
|
||||
* upon completion of the transaction.
|
||||
*
|
||||
* @param {Array.<google.wspl.Statement>} statements the statements to execute
|
||||
* @param {Object?} opt_callback object that defines onSuccess and onFailure
|
||||
* @param {Object?} opt_transactionCallback object that defines onSuccess and
|
||||
* onFailure
|
||||
*/
|
||||
google.wspl.Database.prototype.executeAll = function(statements,
|
||||
opt_callback,
|
||||
opt_transactionCallback) {
|
||||
this.createTransaction(function(tx) {
|
||||
tx.executeAll(statements, opt_callback);
|
||||
}, opt_transactionCallback);
|
||||
};
|
||||
|
||||
/**
|
||||
* An immutable set of results that is returned from a single successful query
|
||||
* on the database.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.ResultSet = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if next() will advance to a valid row in the result set.
|
||||
*
|
||||
* @return {boolean} if next() will advance to a valid row in the result set
|
||||
*/
|
||||
google.wspl.ResultSet.prototype.isValidRow = function() {
|
||||
throw Error('isValidRow not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Advances to the next row in the results.
|
||||
*/
|
||||
google.wspl.ResultSet.prototype.next = function() {
|
||||
throw Error('next not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current row as an object with a property for each field returned
|
||||
* by the database. The property will have the name of the column and the value
|
||||
* of the cell.
|
||||
*
|
||||
* @return {Object} The current row
|
||||
*/
|
||||
google.wspl.ResultSet.prototype.getRow = function() {
|
||||
throw Error('getRow not implemented');
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Database wrapper api</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
function subStatementTest(stm) {
|
||||
assertEquals('statement not set correctly', 'bar', stm.sql);
|
||||
assertEquals('first param not set correctly', 1, stm.params[0]);
|
||||
assertEquals('second param not set correctly', 2, stm.params[1]);
|
||||
}
|
||||
|
||||
function testConstructStatement() {
|
||||
var stm = new google.wspl.Statement('foo');
|
||||
assertEquals('statement not set correctly', 'foo', stm.sql);
|
||||
|
||||
var stm = new google.wspl.Statement('bar', [1,2]);
|
||||
subStatementTest(stm);
|
||||
}
|
||||
|
||||
function testConstructStatementFromTemplate() {
|
||||
var temp = new google.wspl.Statement('bar');
|
||||
var stm = temp.createStatement([1,2]);
|
||||
subStatementTest(stm);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A Gears implementation of dbwrapperapi ResultSet.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a Gears ResultSet object.
|
||||
* @see google.wspl.ResultSet#ResultSet
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.ResultSet
|
||||
* @param {!Array.<Object>} resultArray An array of hash objects where the
|
||||
* column names in the query are used as members of the objects.
|
||||
*/
|
||||
google.wspl.gears.ResultSet = function(resultArray) {
|
||||
google.wspl.ResultSet.call(this);
|
||||
|
||||
/**
|
||||
* The result set as an array of hash objects.
|
||||
* @type {!Array.<Object>}
|
||||
* @private
|
||||
*/
|
||||
this.resultArray_ = resultArray;
|
||||
};
|
||||
google.inherits(google.wspl.gears.ResultSet, google.wspl.ResultSet);
|
||||
|
||||
/**
|
||||
* The current record in the result set.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.current_ = 0;
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#isValidRow
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.isValidRow = function() {
|
||||
return this.current_ < this.resultArray_.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#next
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.next = function() {
|
||||
this.current_++;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see google.wspl.ResultSet#getRow
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.ResultSet.prototype.getRow = function() {
|
||||
return this.resultArray_[this.current_];
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears resultset tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="gears_resultset.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var resultArray;
|
||||
|
||||
function setUp() {
|
||||
var obj1 = {
|
||||
field1: 'a',
|
||||
field2: 2,
|
||||
field3: 'c'
|
||||
};
|
||||
|
||||
var obj2 = {
|
||||
field1: 'd',
|
||||
field2: 4,
|
||||
field3: 'f'
|
||||
};
|
||||
|
||||
var obj3 = {
|
||||
field1: 'g',
|
||||
field2: 6,
|
||||
field3: 'i'
|
||||
};
|
||||
|
||||
resultArray = [obj1, obj2, obj3];
|
||||
}
|
||||
|
||||
function testResultSetNext() {
|
||||
var result = new google.wspl.gears.ResultSet(resultArray);
|
||||
assertEquals('incorrect value for current', 0, result.current_);
|
||||
result.next();
|
||||
assertEquals('incorrect value for current', 1, result.current_);
|
||||
result.next();
|
||||
assertEquals('incorrect value for current', 2, result.current_);
|
||||
}
|
||||
|
||||
function testResultSetIsValidRow() {
|
||||
var result = new google.wspl.gears.ResultSet(resultArray);
|
||||
assertTrue('incorrect return from isValidRow', result.isValidRow());
|
||||
result.next();
|
||||
assertTrue('incorrect return from isValidRow', result.isValidRow());
|
||||
result.next();
|
||||
assertTrue('incorrect return from isValidRow', result.isValidRow());
|
||||
result.next();
|
||||
assertFalse('incorrect return from isValidRow', result.isValidRow());
|
||||
}
|
||||
|
||||
function testResultSetGetRow() {
|
||||
var result = new google.wspl.gears.ResultSet(resultArray);
|
||||
result.next();
|
||||
|
||||
var row = result.getRow();
|
||||
assertEquals('first field is not valid', 'd', row.field1);
|
||||
assertEquals('first field is not valid', 4, row.field2);
|
||||
assertEquals('first field is not valid', 'f', row.field3);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A Gears implementation of dbwrapperapi Transaction.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a Gears Transaction object.
|
||||
* @see google.wspl.ResultSet#ResultSet
|
||||
*
|
||||
* @constructor
|
||||
* @extends google.wspl.Transaction
|
||||
*
|
||||
* @param {number} id The unique id for this transaction
|
||||
* @param {google.wspl.gears.Database} db The Gears implementation of the
|
||||
* dbwrapperapi database
|
||||
*/
|
||||
google.wspl.gears.Transaction = function(id, db) {
|
||||
google.wspl.Transaction.call(this);
|
||||
|
||||
/**
|
||||
* The unique id for this transaction.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.id_ = id;
|
||||
|
||||
/**
|
||||
* The Gears implementation of the dbwrapperapi database.
|
||||
* @type {google.wspl.gears.Database}
|
||||
* @private
|
||||
*/
|
||||
this.db_ = db;
|
||||
|
||||
/**
|
||||
* A map of statements, callback, and current statement.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.activeExecutes_ = {};
|
||||
};
|
||||
google.inherits(google.wspl.gears.Transaction, google.wspl.Transaction);
|
||||
|
||||
/**
|
||||
* The number of active executes.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.numActiveExecutes_ = 0;
|
||||
|
||||
/**
|
||||
* The id for the next call to execute. Incremented after use.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.nextCallbackId_ = 1;
|
||||
|
||||
/**
|
||||
* Whether the transaction should be rolled back or not. This property is set
|
||||
* to true when a statement fails.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.needsRollback_ = false;
|
||||
|
||||
/**
|
||||
* Begins a new transaction with the Gears database. Commits the transaction if
|
||||
* all calls to executeAll for this transaction have finished receiving
|
||||
* callbacks. Rolls the transaction back if a statement failed.
|
||||
*
|
||||
* @see google.wspl.Transaction#executeAll
|
||||
* @inheritDoc
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.executeAll = function(statements,
|
||||
opt_callback) {
|
||||
if (statements.length == 0) {
|
||||
throw Error('Possibly silly attempt to execute empty statement list.');
|
||||
}
|
||||
if (this.numActiveExecutes_ == 0) {
|
||||
this.db_.doBegin(this.id_);
|
||||
}
|
||||
|
||||
this.numActiveExecutes_++;
|
||||
var callbackId = this.nextCallbackId_++;
|
||||
|
||||
var callback = opt_callback || {
|
||||
onSuccess : function() {},
|
||||
onFailure : function() {}
|
||||
};
|
||||
|
||||
this.activeExecutes_[callbackId] = {
|
||||
statements: statements,
|
||||
currentStatement: 0,
|
||||
callback: callback
|
||||
};
|
||||
|
||||
this.db_.doExecute(statements, callbackId, this.id_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes onSuccess on the specified callback.
|
||||
*
|
||||
* @param {google.wspl.ResultSet} result The result of a successful statement
|
||||
* @param {number} callbackId The callback to invoke
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.success = function(result,
|
||||
callbackId) {
|
||||
if (!this.needsRollback_) {
|
||||
var activeExecute = this.activeExecutes_[callbackId];
|
||||
activeExecute.callback.onSuccess(this, result);
|
||||
}
|
||||
this.endStatement_(callbackId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes onFailure on the specified callback.
|
||||
*
|
||||
* @param {Error} error The error of an unsuccessful statement
|
||||
* @param {number} callbackId The callback to invoke
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.failure = function(error,
|
||||
callbackId) {
|
||||
if (!this.needsRollback_) {
|
||||
this.needsRollback_ = true;
|
||||
var activeExecute = this.activeExecutes_[callbackId];
|
||||
activeExecute.callback.onFailure(error);
|
||||
}
|
||||
this.endStatement_(callbackId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles clean up for the end of a single execution.
|
||||
*
|
||||
* @param {number} callbackId The callback to clean up.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.endStatement_ = function(callbackId) {
|
||||
var activeExecute = this.activeExecutes_[callbackId];
|
||||
var statements = activeExecute.statements;
|
||||
var currentStatement = ++activeExecute.currentStatement;
|
||||
|
||||
if (currentStatement == statements.length) {
|
||||
this.endExecute_(callbackId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles clean up for the end of a call to executeAll. Performs a commit or
|
||||
* rollback if this is the last active execute to clean up.
|
||||
*
|
||||
* @param {number} callbackId The callback to clean up
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.endExecute_ = function(callbackId) {
|
||||
delete this.activeExecutes_[callbackId];
|
||||
this.numActiveExecutes_--;
|
||||
if (!this.isExecuting()) {
|
||||
this.endTransaction_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instructs the worker to commit the transaction or roll it back if a failure
|
||||
* occurred and a rollback is required.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.endTransaction_ = function() {
|
||||
if (this.needsRollback_) {
|
||||
this.db_.doRollback(this.id_);
|
||||
} else {
|
||||
this.db_.doCommit(this.id_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the transaction has statements executing, false
|
||||
* otherwise.
|
||||
*/
|
||||
google.wspl.gears.Transaction.prototype.isExecuting = function() {
|
||||
return this.numActiveExecutes_ > 0;
|
||||
};
|
|
@ -0,0 +1,221 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears transaction tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="dbwrapperapi.js"></script>
|
||||
<script type="text/javascript" src="gears_resultset.js"></script>
|
||||
<script type="text/javascript" src="gears_transaction.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var callbackMock;
|
||||
var dbMock;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
callbackMock = mockControl.createMock({
|
||||
onSuccess: function(){},
|
||||
onFailure: function(){}
|
||||
});
|
||||
dbMock = mockControl.createMock({
|
||||
doBegin: function(){},
|
||||
doCommit: function(){},
|
||||
doRollback: function(){},
|
||||
doExecute: function(){}
|
||||
});
|
||||
}
|
||||
|
||||
function testConstructTransaction() {
|
||||
var trans = new google.wspl.gears.Transaction(10, 'foo');
|
||||
assertEquals('foo', trans.db_);
|
||||
assertEquals(10, trans.id_);
|
||||
}
|
||||
|
||||
function testExecuteAll_noStatements() {
|
||||
var trans = new google.wspl.gears.Transaction(27, dbMock);
|
||||
|
||||
try {
|
||||
trans.executeAll([], callbackMock);
|
||||
fail('Should never get here');
|
||||
} catch(e) {
|
||||
if (e.isJsUnitException) {
|
||||
throw e;
|
||||
}
|
||||
assertEquals('did not exception fault on empty statement list',
|
||||
'Error: Possibly silly attempt to execute empty statement list.',
|
||||
e.toString());
|
||||
}
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testExecuteAll_noCallback() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var transactionId = 27;
|
||||
var callbackId = 1;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2, stat3], callbackId, transactionId);
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3]);
|
||||
trans.success('resultset1', callbackId);
|
||||
trans.success('resultset2', callbackId);
|
||||
trans.success('resultset3', callbackId);
|
||||
}
|
||||
|
||||
function testExecuteAll_success() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var transactionId = 27;
|
||||
var callbackId = 1;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2, stat3], callbackId, transactionId);
|
||||
callbackMock.expect().onSuccess(trans, 'resultset1');
|
||||
callbackMock.expect().onSuccess(trans, 'resultset2');
|
||||
callbackMock.expect().onSuccess(trans, 'resultset3');
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3], callbackMock);
|
||||
trans.success('resultset1', callbackId);
|
||||
trans.success('resultset2', callbackId);
|
||||
trans.success('resultset3', callbackId);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testExecuteAll_failure() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var stat4 = new google.wspl.Statement('stat4', [4, 5]);
|
||||
var transactionId = 27;
|
||||
var callbackId = 1;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2, stat3, stat4], callbackId,
|
||||
transactionId);
|
||||
|
||||
callbackMock.expect().onSuccess(trans, 'resultset1');
|
||||
callbackMock.expect().onFailure('sql error');
|
||||
dbMock.expects().doRollback(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2, stat3, stat4], callbackMock);
|
||||
trans.success('resultset1', callbackId);
|
||||
trans.failure('sql error', callbackId);
|
||||
|
||||
// These should do nothing.
|
||||
trans.success('resultset3', callbackId);
|
||||
trans.failure('sql error', callbackId);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testExecuteAll_multipleCalls() {
|
||||
var stat1 = new google.wspl.Statement('stat1');
|
||||
var stat2 = new google.wspl.Statement('stat2', [1]);
|
||||
var stat3 = new google.wspl.Statement('stat3', [2, 3]);
|
||||
var stat4 = new google.wspl.Statement('stat4', [4, 5]);
|
||||
var transactionId = 27;
|
||||
var callbackId_1 = 1;
|
||||
var callbackId_2 = 2;
|
||||
var callbackId_3 = 3;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
dbMock.expects().doBegin(transactionId);
|
||||
dbMock.expects().doExecute([stat1, stat2], callbackId_1, transactionId);
|
||||
dbMock.expects().doExecute([stat3], callbackId_2, transactionId);
|
||||
callbackMock.expects().onSuccess(trans, 'resultset1').andStub(function() {
|
||||
trans.executeAll([stat4], callbackMock);
|
||||
});
|
||||
dbMock.expects().doExecute([stat4], callbackId_3, transactionId);
|
||||
callbackMock.expects().onSuccess(trans, 'resultset2').andStub(function() {});
|
||||
callbackMock.expects().onSuccess(trans, 'resultset3');
|
||||
callbackMock.expects().onSuccess(trans, 'resultset4');
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.executeAll([stat1, stat2], callbackMock);
|
||||
trans.executeAll([stat3], callbackMock);
|
||||
trans.success('resultset1', callbackId_1);
|
||||
trans.success('resultset2', callbackId_1);
|
||||
trans.success('resultset3', callbackId_2);
|
||||
trans.success('resultset4', callbackId_3);
|
||||
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnSuccess() {
|
||||
var transactionId = 27;
|
||||
var callbackId = 3;
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
callbackMock.expects().onSuccess(trans, 'result2');
|
||||
callbackMock.expects().onSuccess(trans, 'result3');
|
||||
dbMock.expects().doCommit(transactionId);
|
||||
|
||||
trans.numActiveExecutes_ = 1;
|
||||
trans.activeExecutes_[callbackId] = {
|
||||
statements: ['s1', 's2', 's3'],
|
||||
currentStatement: 1,
|
||||
callback: callbackMock
|
||||
};
|
||||
trans.success('result2', callbackId);
|
||||
trans.success('result3', callbackId);
|
||||
assertUndefined('activeExecute not removed',
|
||||
trans.activeExecutes_[callbackId]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testOnFailure() {
|
||||
var transactionId = 27;
|
||||
var callbackId = 5;
|
||||
callbackMock.expects().onFailure(TypeOf.isA(Error)).andStub(function() {
|
||||
assertEquals('error not returned', 'error', arguments[0].message);
|
||||
});
|
||||
dbMock.expects().doRollback(transactionId);
|
||||
var trans = new google.wspl.gears.Transaction(transactionId, dbMock);
|
||||
|
||||
trans.numActiveExecutes_ = 1;
|
||||
trans.activeExecutes_[callbackId] = {
|
||||
statements: ['s1', 's2', 's3'],
|
||||
currentStatement: 1,
|
||||
callback: callbackMock
|
||||
};
|
||||
|
||||
trans.failure(Error('error'), callbackId);
|
||||
trans.success('result3', callbackId);
|
||||
assertUndefined('activeExecute not removed',
|
||||
trans.activeExecutes_[callbackId]);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
94
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/gearsutils.js
Executable file
94
middleman-cli/lib/middleman-templates/mobile/source/tools/wspl/gearsutils.js
Executable file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Some simple utilities for supporting Gears usage.
|
||||
*/
|
||||
google.wspl.GearsUtils = google.wspl.GearsUtils || {};
|
||||
|
||||
/**
|
||||
* Returns an array of hash objects, one per row in the result set,
|
||||
* where the column names in the query are used as the members of
|
||||
* the object.
|
||||
*
|
||||
* @param {GearsResultSet} rs the result set returned by execute.
|
||||
* @return {Array.<Object>} An array containing hashes. Returns an empty
|
||||
* array if there are no matching rows.
|
||||
*/
|
||||
google.wspl.GearsUtils.resultSetToObjectArray = function(rs) {
|
||||
var rv = [];
|
||||
if (rs) {
|
||||
var cols = rs['fieldCount']();
|
||||
var colNames = [];
|
||||
for (var i = 0; i < cols; i++) {
|
||||
colNames.push(rs['fieldName'](i));
|
||||
}
|
||||
|
||||
while (rs['isValidRow']()) {
|
||||
var h = {};
|
||||
for (var i = 0; i < cols; i++) {
|
||||
h[colNames[i]] = rs['field'](i);
|
||||
}
|
||||
rv.push(h);
|
||||
rs['next']();
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum file name length.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_ = 64;
|
||||
|
||||
/**
|
||||
* Ensures that the given dbName is safe to use as a Gears database name.
|
||||
* @type {!string} dbName
|
||||
* @return {!string} The sanitized name.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.GearsUtils.makeSafeFileName_ = function(dbName) {
|
||||
var sanitizedFileName = dbName.replace(/[^a-zA-Z0-9\.\-@_]/g, '');
|
||||
if (sanitizedFileName.length <=
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_) {
|
||||
return sanitizedFileName;
|
||||
} else {
|
||||
return sanitizedFileName.substring(0,
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a Gears Database using the provided userid and name.
|
||||
* @param {string} userId The user to which the database belongs.
|
||||
* @param {string} name The database's name.
|
||||
* @param {GearsDatabase} db The Gears database to open.
|
||||
* @param {function(string)} opt_logger A logger function for writing
|
||||
* messages.
|
||||
* @return {GearsDatabase} The open GearsDatabase object.
|
||||
*/
|
||||
google.wspl.GearsUtils.openDatabase = function(userId, name, db,
|
||||
opt_logger) {
|
||||
var dbId = userId + '-' + name;
|
||||
var safeDbId = google.wspl.GearsUtils.makeSafeFileName_(dbId);
|
||||
if (opt_logger && dbId != safeDbId) {
|
||||
opt_logger('database name ' + dbId + '->' + safeDbId);
|
||||
}
|
||||
db.open(safeDbId);
|
||||
return db;
|
||||
};
|
|
@ -0,0 +1,84 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Gears Utilities Tests</title>
|
||||
<script type="text/javascript" src="../jsunit/app/jsUnitCore.js"></script>
|
||||
<script type="text/javascript" src="jsmock.js"></script>
|
||||
<script type="text/javascript" src="global_functions.js"></script>
|
||||
<script type="text/javascript" src="gearsutils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
|
||||
var mockControl;
|
||||
var mockGearsDb;
|
||||
var mockLogger;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new MockControl();
|
||||
mockGearsDb = mockControl.createMock({
|
||||
execute: function(){},
|
||||
open: function(){},
|
||||
close: function(){},
|
||||
remove: function(){}
|
||||
});
|
||||
|
||||
mockLogger = mockControl.createMock({
|
||||
info: function(){}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function testMakeSafeFileName() {
|
||||
assertEquals('simple name wrongly modified', 'ABCDEFGHIJKLMNOPQRSTUVXYZ',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('ABCDEFGHIJKLMNOPQRSTUVXYZ'));
|
||||
assertEquals('simple name wrongly modified', 'abcdefghijklmnopqrstuvxyz',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('abcdefghijklmnopqrstuvxyz'));
|
||||
assertEquals('simple name wrongly modified', '.-@_',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('.-@_'));
|
||||
|
||||
var longName = 'abcdefghijklmnopqrstuvxyz' + 'abcdefghijklmnopqrstuvxyz' +
|
||||
'abcdefghijklmnopqrstuvxyz';
|
||||
assertEquals('long name not truncated',
|
||||
google.wspl.GearsUtils.MAX_FILE_NAME_LENGTH_,
|
||||
google.wspl.GearsUtils.makeSafeFileName_(longName).length);
|
||||
|
||||
assertEquals('forbidden character not stripped', 'abc',
|
||||
google.wspl.GearsUtils.makeSafeFileName_('a#b$()//c'));
|
||||
}
|
||||
|
||||
function testCreateDatabase_success() {
|
||||
mockGearsDb.expects().open('hello-there');
|
||||
google.wspl.GearsUtils.openDatabase('hello', 'there', mockGearsDb,
|
||||
mockLogger);
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
function testCreateDatabase_invalidname() {
|
||||
mockLogger.expects().info('database name foo/bar-ranch->foobar-ranch');
|
||||
mockGearsDb.expects().open('foobar-ranch');
|
||||
assertEquals('bad return value', mockGearsDb,
|
||||
google.wspl.GearsUtils.openDatabase('foo/bar', 'ranch',
|
||||
mockGearsDb, mockLogger.info));
|
||||
mockControl.verify();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Global function implementations used for running the
|
||||
* web storage portability layer code outside of the Google internal
|
||||
* development environment.
|
||||
*
|
||||
* Include this file only once.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Namespace object.
|
||||
* @type {Object}
|
||||
*/
|
||||
var google = google || {};
|
||||
google.wspl = google.wspl || {};
|
||||
google.wspl.gears = google.wspl.gears || {};
|
||||
|
||||
/**
|
||||
* Inherit the prototype methods from one constructor into another.
|
||||
* @param {Function} childConstructor Child class.
|
||||
* @param {Function} parentConstructor Parent class.
|
||||
*/
|
||||
google.inherits = function(childConstructor, parentConstructor) {
|
||||
function tempConstructor() {};
|
||||
tempConstructor.prototype = parentConstructor.prototype;
|
||||
childConstructor.prototype = new tempConstructor();
|
||||
childConstructor.prototype.constructor = childConstructor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a context object to the function.
|
||||
* @param {Function} fn The function to bind to.
|
||||
* @param {Object} context The "this" object to use when the function is run.
|
||||
* @return {Function} A partially-applied form of fn.
|
||||
*/
|
||||
google.bind = function(fn, context) {
|
||||
return function() {
|
||||
return fn.apply(context, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Null function used for callbacks.
|
||||
* @type {Function}
|
||||
*/
|
||||
google.nullFunction = function() {};
|
||||
|
||||
/**
|
||||
* Simple logging facility.
|
||||
* @param {string} msg A message to write to the console.
|
||||
*/
|
||||
google.logger = function(msg) {
|
||||
// Uncomment the below to get log messages
|
||||
// May require firebug enabled to work in FireFox.
|
||||
// window.console.info(msg);
|
||||
};
|
|
@ -0,0 +1,347 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
A simple application showing the use of the web storage portability layer
|
||||
code and cache pattern for offline web application.
|
||||
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>SimpleNotes Demo</title>
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
<script src="../global_functions.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="../dbwrapperapi.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="../dbwrapper_html5.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="../databasefactory.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="template.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
<script src="simplenotes.js" language="javascript"
|
||||
type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="list-note" class="screen list-note-screen" >
|
||||
<div class="title-bar">Note List</div>
|
||||
<div class="top command-bar">
|
||||
<button id="view-note-new">New</button>
|
||||
</div>
|
||||
<div class="status-bar">status bar</div>
|
||||
<div class="list-note-container-empty" style="display:none;"></div>
|
||||
<div class="list-note-container" style="display:none;">
|
||||
<div class="list-note-item">
|
||||
<a href="javascript:showNote(%noteKey%);">%subject%</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top command-bar">
|
||||
<button id="view-note-new">New</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-note" class="screen view-note-screen" style="display:none;">
|
||||
<div class="title-bar">View Note</div>
|
||||
<div class="top command-bar">
|
||||
<button id="view-note-back">Back</button>
|
||||
<button id="view-note-edit">Edit Note</button>
|
||||
</div>
|
||||
<div class="status-bar">status bar</div>
|
||||
<div class="view-note-container" style="display:none;">
|
||||
<div class="subject">%subject%</div>
|
||||
<div class="body">%body%</div>
|
||||
</div>
|
||||
<div class="bottom command-bar">
|
||||
<button id="view-note-back">Back</button>
|
||||
<button id="view-note-edit">Edit Note</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-note" class="screen edit-note-screen" style="display:none;">
|
||||
<div class="title-bar">Edit Note</div>
|
||||
<div class="top command-bar">
|
||||
<button id="edit-back-save">Save Note</button>
|
||||
<button id="edit-back-revert">Revert Note</button>
|
||||
</div>
|
||||
<div class="status-bar">status bar</div>
|
||||
<div>Subject:</div>
|
||||
<textarea rows="1" style="width:100%;" id="edit-note-subject"></textarea>
|
||||
<br>
|
||||
<div>Note:</div>
|
||||
<textarea rows="20" style="width:100%;" id="edit-note-body"></textarea>
|
||||
<div class="bottom command-bar">
|
||||
<button id="edit-back-save">Save Note</button>
|
||||
<button id="edit-back-revert">Revert Note</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
|
||||
/**
|
||||
* Tests the given DOM element for a specific CSS class.
|
||||
* @param {Object} e A DOM element.
|
||||
* @param {string} css A CSS class identifier to match.
|
||||
* @return {boolean} true if the element has the CSS class.
|
||||
*/
|
||||
function hasCssClass(e, css) {
|
||||
return e && e.className && e.className.search(css + '( |$)') > -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the parent element of a given element that has
|
||||
* the specified CSS class.
|
||||
* @param {Object} e A DOM element.
|
||||
* @param {string} css A CSS class identifier to match.
|
||||
* @param {Object} opt_stop A parent DOM element to stop at.
|
||||
* @return {Object} A DOM element parent of e or null.
|
||||
*/
|
||||
function findParentOfClass(e, css, opt_stop) {
|
||||
var stop = opt_stop || document;
|
||||
while (e && e != stop && !hasCssClass(e, css)) {
|
||||
e = e.parentElement;
|
||||
}
|
||||
return hasCssClass(e, css) ? e : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to the note creation screen.
|
||||
*/
|
||||
function editNote(event) {
|
||||
google.logger('editNote: <' + currentNote.subject + '>');
|
||||
hideBlock('#edit-note .status-bar');
|
||||
|
||||
var subject = document.getElementById('edit-note-subject');
|
||||
var body = document.getElementById('edit-note-body');
|
||||
subject.value = currentNote.subject;
|
||||
body.value = currentNote.body;
|
||||
|
||||
google.logger('end of editNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a note.
|
||||
*/
|
||||
function saveNote(event) {
|
||||
google.logger('saveNote: do database or other activity here');
|
||||
// Copy contents from fields and save.
|
||||
var subject = document.getElementById('edit-note-subject');
|
||||
var body = document.getElementById('edit-note-body');
|
||||
|
||||
cache.applyUiChange(currentNote.noteKey, subject.value,
|
||||
body.value, showNote);
|
||||
google.logger('saveNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Abandons the note.
|
||||
*/
|
||||
function abandonNote(event) {
|
||||
google.logger('abandonNote: do database or other activity here');
|
||||
showNote(currentNote.noteKey);
|
||||
google.logger('abandonNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to edit a new note.
|
||||
*/
|
||||
function newNote(event) {
|
||||
google.logger('newNote: do database or other activity here');
|
||||
alert('not implemented');
|
||||
// grab the contents and inject into the database.
|
||||
var subject = document.getElementById('edit-note-subject');
|
||||
var body = document.getElementById('edit-note-body');
|
||||
subject.value = '';
|
||||
body.value = '';
|
||||
google.logger('newNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps button names to customizer functions.
|
||||
*/
|
||||
var buttonHandlerSpecification = {
|
||||
'view-note-new': ['edit-note', newNote],
|
||||
'view-note-back': ['list-note', showNoteList],
|
||||
'view-note-edit': ['edit-note', editNote],
|
||||
'edit-back-save': ['view-note', saveNote],
|
||||
'edit-back-revert': ['view-note', abandonNote]
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides a block specified by a CSS selector and parent node.
|
||||
* @param {string} selector A CSS selector string.
|
||||
* @param {Object} opt_el A DOM element.
|
||||
*/
|
||||
function hideBlock(selector, opt_el) {
|
||||
var el = opt_el || document;
|
||||
var targetEl = el.querySelector(selector);
|
||||
targetEl.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a block specified by a CSS selector and parent node, inserting
|
||||
* the specified string as the innerHTML property of the selected block.
|
||||
* @param {string} selector A CSS selector
|
||||
* @param {string} html String to assign to the innerHTML property.
|
||||
* @param {Object} opt_el A DOM element.
|
||||
*/
|
||||
function showBlock(selector, html, opt_el) {
|
||||
var el = opt_el || document;
|
||||
var targetEl = el.querySelector(selector);
|
||||
if (html) targetEl.innerHTML = html;
|
||||
targetEl.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all button events.
|
||||
*/
|
||||
function buttonHandler(event) {
|
||||
google.logger('clicked on: ' + event.target.id);
|
||||
|
||||
var currentScreen = findParentOfClass(event.target, 'screen');
|
||||
var nextScreen = document.getElementById(
|
||||
buttonHandlerSpecification[event.target.id][0]);
|
||||
var helperFunction = buttonHandlerSpecification[event.target.id][1];
|
||||
|
||||
// All button handlers have a common implementation.
|
||||
// 1. hide current screen
|
||||
currentScreen.style.display = 'none';
|
||||
|
||||
// 2. show new screen
|
||||
nextScreen.style.display = 'block';
|
||||
|
||||
// 3. Activate status
|
||||
showBlock('.status-bar', 'Working...', nextScreen);
|
||||
|
||||
// 4. Call customizer function (which will probably query the model
|
||||
// stored in the cache.
|
||||
helperFunction(event);
|
||||
|
||||
// 5. A (possibly asynchronous) callback from the customizer will
|
||||
// finish the action by running the appropriate view function.
|
||||
google.logger('end of button Handler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Call back from a specific JavaScript url to show a specific
|
||||
* note.
|
||||
* @param {number} noteKey
|
||||
* @return {boolean}
|
||||
*/
|
||||
function showNote(noteKey) {
|
||||
hideBlock('#list-note');
|
||||
showBlock('#view-note .status-bar', 'Working...');
|
||||
showBlock('#view-note');
|
||||
cache.getValues('fullnote', [noteKey], renderFullNote);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for a note and display it.
|
||||
* @param {Object} oneNote
|
||||
* @param {boolean} complete True if the note has been provided or
|
||||
* if an updated note is arriving. (Necessary for future support for
|
||||
* sub-note updates.)
|
||||
*/
|
||||
function renderFullNote(oneNote, complete) {
|
||||
google.logger('renderFullNote');
|
||||
|
||||
showBlock('.view-note-container', noteViewTemplate.process(oneNote));
|
||||
if (complete) {
|
||||
hideBlock('#view-note .status-bar');
|
||||
}
|
||||
currentNote = oneNote;
|
||||
google.logger('done renderFullNote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML for a note list and display it.
|
||||
* @param {Array.<Object>} noteHeaders
|
||||
* @param {boolean} complete True if all in-existence notes that lie
|
||||
* in the query range have been provided even if less than what was
|
||||
* requested.
|
||||
*/
|
||||
function renderNoteList(noteHeaders, complete) {
|
||||
google.logger('renderNoteList: ' + noteHeaders.length);
|
||||
|
||||
if (noteHeaders.length == 0) {
|
||||
showBlock('.list-note-container-empty', 'No notes');
|
||||
} else {
|
||||
var notes = noteHeaders.map(function(n) {
|
||||
return noteListTemplate.process(n);
|
||||
});
|
||||
showBlock('.list-note-container', notes.join());
|
||||
}
|
||||
if (complete) {
|
||||
hideBlock('#list-note .status-bar');
|
||||
}
|
||||
google.logger('done renderNoteList');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for an updated note list.
|
||||
*/
|
||||
function showNoteList() {
|
||||
google.logger('showNoteList');
|
||||
cache.getValues('list', [0,20], renderNoteList);
|
||||
currentNote = undefined;
|
||||
google.logger('end of showNoteList');
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor for the controller. Sets up the UI and
|
||||
* queries the cache for initial notelist contents.
|
||||
*/
|
||||
function createController() {
|
||||
google.logger('createController:');
|
||||
var buttons = document.querySelectorAll('.command-bar button');
|
||||
Array.prototype.forEach.call(buttons, function(b) {
|
||||
b.addEventListener('click', buttonHandler);
|
||||
});
|
||||
google.logger('controller is live');
|
||||
showNoteList();
|
||||
}
|
||||
|
||||
google.logger('about to fetch the template');
|
||||
var currentNote = undefined;
|
||||
|
||||
var noteListTemplate = new google.wspl.simplenotes.Template(
|
||||
document.querySelector('.list-note-container').innerHTML);
|
||||
|
||||
var noteViewTemplate = new google.wspl.simplenotes.Template(
|
||||
document.querySelector('.view-note-container').innerHTML);
|
||||
|
||||
// var noteEditTemplate = new google.wspl.simplenotes.Template();
|
||||
|
||||
google.logger('created the template');
|
||||
|
||||
// debugging code... tear out...
|
||||
// test the template construction...
|
||||
// showBlock('.list-note-container', noteListTemplate.process(
|
||||
// {noteKey: 123, subject: 'hi rob'}));
|
||||
|
||||
|
||||
google.logger('loaded database');
|
||||
var cache = new google.wspl.simplenotes.Cache();
|
||||
cache.startCache(createController);
|
||||
|
||||
google.logger('finished loading page');
|
||||
</script>
|
||||
</html>
|
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A concrete example of the cache pattern for building an offline
|
||||
* webapplication: a cache for SimpleNotes.
|
||||
*/
|
||||
|
||||
google.wspl.simplenotes = google.wspl.simplenotes || {};
|
||||
google.logger('start simplenotes.js');
|
||||
|
||||
/**
|
||||
* Status keys for the write buffer.
|
||||
* @enum {number}
|
||||
*/
|
||||
google.wspl.simplenotes.WriteBufferStates = {
|
||||
/**
|
||||
* The update is in flight to the server.
|
||||
*/
|
||||
INFLIGHT: 1,
|
||||
|
||||
/**
|
||||
* The update needs to be (re)sent to the server but is not in flight.
|
||||
*/
|
||||
SEND: 2,
|
||||
|
||||
/**
|
||||
* The update needs to be applied to the cached notes.
|
||||
*/
|
||||
REAPPLY: 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SimpleNotes cache wrapping a backing database.
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.simplenotes.Cache = function() {
|
||||
this.dbms_ = google.wspl.DatabaseFactory.createDatabase(
|
||||
'simple-notes', 'http://yourdomain/dbworker.js');
|
||||
|
||||
/**
|
||||
* Cache directory is a two-tuple over a range. (Holes
|
||||
* must be allowed to support delection.)
|
||||
* This is the lower (inclusive) bound of the cached range.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.start_ = -1;
|
||||
|
||||
/**
|
||||
*
|
||||
* This is the upper (inclusive) bound of the cached range.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.end_ = -1;
|
||||
|
||||
/**
|
||||
* Start of range of notes known to exist on server at time of last
|
||||
* response.
|
||||
* @param {number}
|
||||
*/
|
||||
this.serverStart_ = -1;
|
||||
|
||||
/**
|
||||
* End of range of notes known to exist on server at time of last
|
||||
* response.
|
||||
* @param {number}
|
||||
*/
|
||||
this.serverEnd_ = -1;
|
||||
|
||||
/**
|
||||
* Time of last refresh.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastRefresh_ = -1;
|
||||
|
||||
/**
|
||||
* Last missing query.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.lastMiss_ = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interval between refreshes in milliseconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_ = 2000;
|
||||
|
||||
google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TABLE IF NOT EXISTS cached_notes (' +
|
||||
'noteKey INTEGER UNIQUE PRIMARY KEY,' +
|
||||
'subject TEXT,' +
|
||||
'body TEXT' +
|
||||
');'
|
||||
);
|
||||
|
||||
google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TABLE IF NOT EXISTS write_buffer (' +
|
||||
'sequence INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT,' +
|
||||
'noteKey INTEGER,' +
|
||||
'status INTEGER,' +
|
||||
'subject TEXT,' +
|
||||
'body TEXT' +
|
||||
');'
|
||||
);
|
||||
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT MIN(noteKey) as minNoteKey FROM cached_notes;');
|
||||
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT MAX(noteKey) as maxNoteKey FROM cached_notes;');
|
||||
|
||||
/**
|
||||
* Builds a cache and writebuffer combination for notes and then
|
||||
* invokes the given callback.
|
||||
* @param {function) callback.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.startCache = function(callback) {
|
||||
google.logger('startCache');
|
||||
var statc = 0;
|
||||
var self = this;
|
||||
|
||||
var perStatCallback = function(tx, result) {
|
||||
google.logger('perStatCallback');
|
||||
if (statc == 4) {
|
||||
self.start_ = (result.isValidRow()) ? result.getRow().minNoteKey : -1;
|
||||
self.serverStart_ = self.start_; // Temporary. Remove when server exists.
|
||||
} else if (statc == 5) {
|
||||
self.end_ = (result.isValidRow()) ? result.getRow().maxNoteKey : -1;
|
||||
self.serverEnd_ = self.end_; // Temporary. Remove when server exists.
|
||||
}
|
||||
statc++;
|
||||
};
|
||||
|
||||
this.dbms_.executeAll([
|
||||
google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_,
|
||||
google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_,
|
||||
google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_,
|
||||
google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_,
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_,
|
||||
google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_],
|
||||
{onSuccess: perStatCallback, onFailure: this.logError_},
|
||||
{onSuccess: callback, onFailure: this.logError_});
|
||||
google.logger('finished startCache');
|
||||
};
|
||||
|
||||
/**
|
||||
* Stub function to be replaced with a server communication.
|
||||
* @param {Array.<Object>} updates Payload to send to server.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.sendXhrPost = function(updates) {
|
||||
google.logger('Should dispatch XHR to server now.');
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT noteKey, subject from cached_notes WHERE ' +
|
||||
'noteKey >= ? AND ' +
|
||||
'noteKey <= ? ' +
|
||||
';'
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests if the given range is stored in the cache.
|
||||
* Note that this mechanism requires extension to handle the
|
||||
* creation of new notes.
|
||||
* @param {number} start Lower bound (inclusive) on range.
|
||||
* @param {number} end Uppder bound (inclusive) on range.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.isCacheHit_ = function(start, end) {
|
||||
return start >= this.start_ && end <= this.end_
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a possibly useful error message.
|
||||
* @param {Object} error An error descriptor.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.logError_ = function(error) {
|
||||
google.logger('Simple Notes Cache is sad: ' + error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queries the cache for a list of note headers.
|
||||
* @param {number} start The lower id in a range of notes.
|
||||
* @param {number} end The higher id in a range of notes.
|
||||
* @param {function(Array.<Object>)} valuesCallback A function to call
|
||||
* with the result set after the transaction has completed.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.getNoteList_ = function(start, end,
|
||||
valuesCallback) {
|
||||
var notes = [];
|
||||
|
||||
var accumulateResults = function(tx, result) {
|
||||
for(; result.isValidRow(); result.next()) {
|
||||
notes.push(result.getRow());
|
||||
google.logger('pushed...');
|
||||
}
|
||||
};
|
||||
|
||||
var inTransactionGetNotes = function(tx) {
|
||||
tx.execute(google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_.
|
||||
createStatement([start, end]), {
|
||||
onSuccess: accumulateResults,
|
||||
onFailure: this.logError_});
|
||||
};
|
||||
|
||||
var hit = this.isCacheHit_(start, end);
|
||||
this.dbms_.createTransaction(inTransactionGetNotes, {onSuccess: function() {
|
||||
valuesCallback(notes, hit);
|
||||
}, onFailure: this.logError_});
|
||||
|
||||
if (hit) {
|
||||
this.fetchFromServer(this.start_, this.end_); // Refresh
|
||||
} else {
|
||||
this.fetchFromServer(Math.min(this.start_, start), Math.max(this.end_, end));
|
||||
this.lastMiss_ = {callback: valuesCallback, start: start, end: end};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.GET_ONE_NOTE_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT noteKey, subject, body from cached_notes WHERE ' +
|
||||
'noteKey = ? ' +
|
||||
';'
|
||||
);
|
||||
|
||||
/**
|
||||
* Queries the cache for a list of note headers.
|
||||
* @param {number} noteId The note to get from the cache.
|
||||
* @param {function(Array.<Object>)} valuesCallback A function to call
|
||||
* with the result set after the transaction has completed.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.getOneNote_ = function(noteId,
|
||||
callback) {
|
||||
var note;
|
||||
|
||||
this.dbms_.execute(google.wspl.simplenotes.Cache.GET_ONE_NOTE_.
|
||||
createStatement([noteId]),
|
||||
{onSuccess: function(tx, result) { note = result.getRow(); },
|
||||
onFailure: this.logError_},
|
||||
{onSuccess: function() { callback(note, true); },
|
||||
onFailure: this.logError_});
|
||||
};
|
||||
|
||||
/**
|
||||
* Queries the cache for either a list of notes or a single note including
|
||||
* its body.
|
||||
* @param {string} type The kind of values desired: 'list' or 'fullnote'.
|
||||
* @param {Array.<number>} query The query for the values.
|
||||
* @param {function(Array.<Object>)} valuesCallback A function to call
|
||||
* with the result set after the transaction has completed.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.getValues = function(type,
|
||||
query, valuesCallback) {
|
||||
|
||||
// Reduce any query to what would be available from the server
|
||||
query[0] = Math.max(this.serverStart_, query[0]);
|
||||
query[1] = Math.min(this.serverEnd_, query[1]);
|
||||
|
||||
if (type == 'list') {
|
||||
this.getNoteList_(query[0], query[1], valuesCallback);
|
||||
} else if (type == 'fullnote') {
|
||||
this.getOneNote_(query[0], valuesCallback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* SQL trigger to insert a new change from write buffer to
|
||||
* cache.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TRIGGER IF NOT EXISTS updateTrigger ' +
|
||||
'AFTER INSERT ON write_buffer ' +
|
||||
'BEGIN ' +
|
||||
' REPLACE INTO cached_notes ' +
|
||||
' SELECT noteKey, subject, body ' +
|
||||
' FROM write_buffer WHERE status & 8 = 8; ' +
|
||||
' UPDATE write_buffer SET status = status & ~ 8; ' +
|
||||
'END;'
|
||||
);
|
||||
|
||||
/**
|
||||
* SQL trigger to replay changes from the write buffer to
|
||||
* the cache.
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_ =
|
||||
new google.wspl.Statement(
|
||||
'CREATE TRIGGER IF NOT EXISTS replayTrigger ' +
|
||||
'AFTER UPDATE ON write_buffer WHEN NEW.status & 8 = 8 ' +
|
||||
'BEGIN ' +
|
||||
' REPLACE INTO cached_notes ' +
|
||||
' SELECT noteKey, subject, body ' +
|
||||
' FROM write_buffer ' +
|
||||
' WHERE noteKey = NEW.noteKey ' +
|
||||
' ORDER BY sequence ASC;' +
|
||||
' UPDATE write_buffer SET status = status & ~ 8; ' +
|
||||
'END;'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* SQL statement to mark actions for replay.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.MARK_FOR_REPLAY =
|
||||
new google.wspl.Statement(
|
||||
'UPDATE write_buffer SET status = status | 8;');
|
||||
|
||||
/**
|
||||
* SQL statement to insert notes updates.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_ =
|
||||
new google.wspl.Statement(
|
||||
'INSERT INTO write_buffer (' +
|
||||
'noteKey, status, subject, body' +
|
||||
') ' + 'VALUES(?,?,?,?);');
|
||||
|
||||
/**
|
||||
* Updates the given entry and write a new write buffer entry.
|
||||
* @param {number} noteKey
|
||||
* @param {string} subject
|
||||
* @param {string} body
|
||||
* @param {function(number)} ackCallback
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.applyUiChange = function(noteKey,
|
||||
subject, body, ackCallback) {
|
||||
var self = this;
|
||||
var update = [noteKey, 2 | 8, subject, body];
|
||||
var stat = google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_.createStatement(
|
||||
update);
|
||||
|
||||
this.dbms_.execute(stat, null, {onSuccess: function() {
|
||||
google.logger('applyUiChange cb');
|
||||
ackCallback(noteKey);
|
||||
}, onFailure: function (error) {
|
||||
self.logError_(error);
|
||||
ackCallback(-1);
|
||||
}});
|
||||
};
|
||||
|
||||
/**
|
||||
* SQL statement to insert notes updates.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.INSERT_NOTE_ =
|
||||
new google.wspl.Statement(
|
||||
'REPLACE INTO cached_notes (noteKey, subject, body) ' +
|
||||
'VALUES(?,?,?);' );
|
||||
|
||||
/**
|
||||
* SQL statement to force replay of pending actions by setting a bit
|
||||
* flag on each write-buffer row indicating that it should be reapplied
|
||||
* to the contents of the cache.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.FORCE_REPLAY_ =
|
||||
new google.wspl.Statement(
|
||||
'UPDATE write_buffer SET status = status | 8;' );
|
||||
|
||||
/**
|
||||
* SQL statement to delete notes no longer to be cached.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.EVICT_ =
|
||||
new google.wspl.Statement(
|
||||
'DELETE FROM cached_notes WHERE noteKey < ? OR noteKey > ?;');
|
||||
|
||||
/**
|
||||
* Applies the changes delivered from the server by first inserting
|
||||
* them into the cache and reapplying the write-buffer to the cache.
|
||||
* @param {!Array.<Object>} notes An array of arrays.
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.insertUpdate = function(notes) {
|
||||
var self = this; var stats = [];
|
||||
var start = notes[0].noteKey;
|
||||
var end = notes[0].noteKey;
|
||||
|
||||
for (var i = 0; i < notes.length; i++) {
|
||||
stats.push(google.wspl.simplenotes.Cache.INSERT_NOTE_.
|
||||
createStatement([notes[i].noteKey, notes[i].subject, notes[i].body]));
|
||||
start = Math.min(start, notes[0].noteKey);
|
||||
end = Math.max(end, notes[0].noteKey);
|
||||
}
|
||||
stats.push(google.wspl.simplenotes.Cache.EVICT_.createStatement([start, end]));
|
||||
stats.push(google.wspl.simplenotes.Cache.FORCE_REPLAY_);
|
||||
|
||||
var inTrans = function(tx) {
|
||||
self.start_ = start;
|
||||
self.end_ = end;
|
||||
tx.executeAll(stats);
|
||||
};
|
||||
|
||||
var afterInsert = function(tx) {
|
||||
if (this.lastMiss_ &&
|
||||
this.isCacheHit_(this.lastMiss_.start, this.lastMiss_.end)) {
|
||||
this.lastMiss_.callback(notes);
|
||||
this.lastMiss_ = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
this.dbms_.createTransaction(inTrans, {onSuccess: afterInsert,
|
||||
onError: this.logError_});
|
||||
};
|
||||
|
||||
/**
|
||||
* SQL statement to force replay of pending actions by setting a bit
|
||||
* flag on each write-buffer row indicating that it should be reapplied
|
||||
* to the contents of the cache.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_ =
|
||||
new google.wspl.Statement(
|
||||
'SELECT noteKey, subject, body FROM write_buffer WHERE status & 2 = 2;');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SQL statement to mark write buffer statements as inflight.
|
||||
* @type {!google.wspl.Statement}
|
||||
* @private
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_ =
|
||||
new google.wspl.Statement(
|
||||
'UPDATE write_buffer SET status = status & ~2 | 1 WHERE status & 2 = 2;');
|
||||
|
||||
/**
|
||||
* Fetches new material from the server as required.
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {function} opt_valueCallBack
|
||||
*/
|
||||
google.wspl.simplenotes.Cache.prototype.fetchFromServer = function(start,
|
||||
end) {
|
||||
google.logger('fetchFromServer');
|
||||
var now = this.dbms_.getCurrentTime();
|
||||
if (start >= this.start_ && end <= this.end_ &&
|
||||
now - this.lastRefresh_ <
|
||||
google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_) {
|
||||
return;
|
||||
}
|
||||
|
||||
var updates = []; var self = this; var flag = 1; var sql = []
|
||||
sql.push(google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_);
|
||||
sql.push(google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_);
|
||||
|
||||
var accumulateUpdates = function(tx, rs) {
|
||||
if (flag == 1) {
|
||||
for(; rs.isValidRow(); rs.next()) { updates.push(['u', rs.getRow()]); }
|
||||
flag++;
|
||||
}
|
||||
};
|
||||
|
||||
var ackAndPost = function() {
|
||||
updates.push(['q', {start: start, end: end}]);
|
||||
self.sendXhrPost(updates);
|
||||
};
|
||||
|
||||
this.dbms_.executeAll(sql,
|
||||
{onSuccess: accumulateUpdates, onFailure: this.logError_},
|
||||
{onSuccess: ackAndPost, onFailure: this.logError_});
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #efefef;
|
||||
font: medium "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background-color: #ffd21d;
|
||||
display: none;
|
||||
-webkit-border-radius: 6.0px;
|
||||
-moz-border-radius: 6.0px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.screen {
|
||||
border: thin solid #939393;
|
||||
padding: 2px 2px 2px 2px;
|
||||
}
|
||||
|
||||
.command-bar {
|
||||
background-color: #275390;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
font-weight: bold;
|
||||
background-color: #4b4b4b;
|
||||
text-align: center;
|
||||
padding: 3px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
button {
|
||||
text-align:center;
|
||||
font-family: Helvetica;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
text-decoration: none;
|
||||
text-shadow: #fff 0px 2px 2px;
|
||||
padding: 3px;
|
||||
-webkit-border-radius: 6.0px;
|
||||
-moz-border-radius: 6.0px;
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2009 Google Inc.
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview template.js contains the implementation for a simple template
|
||||
* scheme. Templates are fragments of HTML containing patterns into which
|
||||
* arguments will be substituted.
|
||||
*/
|
||||
|
||||
google.wspl.simplenotes = google.wspl.simplenotes || {};
|
||||
|
||||
/**
|
||||
* Template class constructor. A template is an object which will
|
||||
* substitute provided parameters into a marked-up string.
|
||||
* @param {string} template
|
||||
* @constructor
|
||||
*/
|
||||
google.wspl.simplenotes.Template = function(template) {
|
||||
this.template_ = template;
|
||||
this.res_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the template expanded with the given args where args
|
||||
* is an object (acting as an associative array) binding keys (found
|
||||
* in the template wrapped with % symbols) to the associated
|
||||
* values.
|
||||
*
|
||||
* Template substitution symbols without corresponding arguments
|
||||
* will be passed through unchanged to the output.
|
||||
*
|
||||
* We assume that in typical use, the same template will be expanded
|
||||
* repeatedly with different values. In this case, storing and re-using
|
||||
* previously generated regular expressions will provide a performance
|
||||
* improvement.
|
||||
* @param {Object} args associates names with values
|
||||
* @param {boolean} opt_rebuild set to true to force re-building the
|
||||
* the regular epxression.
|
||||
*/
|
||||
google.wspl.simplenotes.Template.prototype.process = function(args,
|
||||
opt_rebuild) {
|
||||
var rebuild = opt_rebuild || false;
|
||||
|
||||
if (rebuild || this.res_ == null) {
|
||||
var accumulatedRe = [];
|
||||
this.res_ = null;
|
||||
for (var a in args) {
|
||||
accumulatedRe.push('%' + String(a) + '%');
|
||||
}
|
||||
if (accumulatedRe.length > 0) {
|
||||
this.res_ = new RegExp(accumulatedRe.join('|'), 'g');
|
||||
}
|
||||
}
|
||||
if (this.res_ != null) {
|
||||
return this.template_.replace(this.res_, function(match) {
|
||||
var keyName = match.slice(1,-1);
|
||||
return args[keyName];
|
||||
});
|
||||
} else {
|
||||
return this.template_;
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue