more templates back into cli
This commit is contained in:
parent
be5fad55ca
commit
ba01a0a72b
121 changed files with 5 additions and 42 deletions
|
@ -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_});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue