Adding float-no-zero branch hosted build

This commit is contained in:
ahocevar
2014-03-07 10:55:12 +01:00
parent 84cad42f6d
commit bd9092199b
1664 changed files with 731463 additions and 0 deletions
@@ -0,0 +1,521 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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 Definition of goog.gears.BaseStore which
* is a base class for the various database stores. It provides
* the basic structure for creating, updating and removing the store, as well
* as versioning. It also provides ways to interconnect stores.
*
*/
goog.provide('goog.gears.BaseStore');
goog.provide('goog.gears.BaseStore.SchemaType');
goog.require('goog.Disposable');
/**
* This class implements the common store functionality
*
* @param {goog.gears.Database} database The data base to store the data in.
* @constructor
* @extends {goog.Disposable}
*/
goog.gears.BaseStore = function(database) {
goog.Disposable.call(this);
/**
* The underlying database that holds the message store.
* @private
* @type {goog.gears.Database}
*/
this.database_ = database;
};
goog.inherits(goog.gears.BaseStore, goog.Disposable);
/**
* Schema definition types
* @enum {number}
*/
goog.gears.BaseStore.SchemaType = {
TABLE: 1,
VIRTUAL_TABLE: 2,
INDEX: 3,
BEFORE_INSERT_TRIGGER: 4,
AFTER_INSERT_TRIGGER: 5,
BEFORE_UPDATE_TRIGGER: 6,
AFTER_UPDATE_TRIGGER: 7,
BEFORE_DELETE_TRIGGER: 8,
AFTER_DELETE_TRIGGER: 9
};
/**
* The name of the store. Subclasses should override and choose their own
* name. That name is used for the maintaining the version string
* @protected
* @type {string}
*/
goog.gears.BaseStore.prototype.name = 'Base';
/**
* The version number of the database schema. It is used to determine whether
* the store's portion of the database needs to be updated. Subclassses should
* override this value.
* @protected
* @type {number}
*/
goog.gears.BaseStore.prototype.version = 1;
/**
* The database schema for the store. This is an array of objects, where each
* object describes a database object (table, index, trigger). Documentation
* about the object's fields can be found in the #createSchema documentation.
* This is in the prototype so that it can be overriden by the subclass. This
* field is read only.
* @protected
* @type {Array.<Object>}
*/
goog.gears.BaseStore.prototype.schema = [];
/**
* Gets the underlying database.
* @return {goog.gears.Database}
* @protected
*/
goog.gears.BaseStore.prototype.getDatabaseInternal = function() {
return this.database_;
};
/**
* Updates the tables for the message store in the case where
* they are out of date.
*
* @protected
* @param {number} persistedVersion the current version of the tables in the
* database.
*/
goog.gears.BaseStore.prototype.updateStore = function(persistedVersion) {
// TODO(user): Need to figure out how to handle updates
// where to store the version number and is it globale or per unit.
};
/**
* Preloads any applicable data into the tables.
*
* @protected
*/
goog.gears.BaseStore.prototype.loadData = function() {
};
/**
* Creates in memory cache of data that is stored in the tables.
*
* @protected
*/
goog.gears.BaseStore.prototype.getCachedData = function() {
};
/**
* Informs other stores that this store exists .
*
* @protected
*/
goog.gears.BaseStore.prototype.informOtherStores = function() {
};
/**
* Makes sure that tables needed for the store exist and are up to date.
*/
goog.gears.BaseStore.prototype.ensureStoreExists = function() {
var persistedVersion = this.getStoreVersion();
if (persistedVersion) {
if (persistedVersion != this.version) {
// update
this.database_.begin();
try {
this.updateStore(persistedVersion);
this.setStoreVersion_(this.version);
this.database_.commit();
} catch (ex) {
this.database_.rollback(ex);
throw Error('Could not update the ' + this.name + ' schema ' +
' from version ' + persistedVersion + ' to ' + this.version +
': ' + (ex.message || 'unknown exception'));
}
}
} else {
// create
this.database_.begin();
try {
// This is rarely necessary, but it's possible if we rolled back a
// release and dropped the schema on version n-1 before installing
// again on version n.
this.dropSchema(this.schema);
this.createSchema(this.schema);
// Ensure that the version info schema exists.
this.createSchema([{
type: goog.gears.BaseStore.SchemaType.TABLE,
name: 'StoreVersionInfo',
columns: [
'StoreName TEXT NOT NULL PRIMARY KEY',
'Version INTEGER NOT NULL'
]}], true);
this.loadData();
this.setStoreVersion_(this.version);
this.database_.commit();
} catch (ex) {
this.database_.rollback(ex);
throw Error('Could not create the ' + this.name + ' schema' +
': ' + (ex.message || 'unknown exception'));
}
}
this.getCachedData();
this.informOtherStores();
};
/**
* Removes the tables for the MessageStore
*/
goog.gears.BaseStore.prototype.removeStore = function() {
this.database_.begin();
try {
this.removeStoreVersion();
this.dropSchema(this.schema);
this.database_.commit();
} catch (ex) {
this.database_.rollback(ex);
throw Error('Could not remove the ' + this.name + ' schema' +
': ' + (ex.message || 'unknown exception'));
}
};
/**
* Returns the name of the store.
*
* @return {string} The name of the store.
*/
goog.gears.BaseStore.prototype.getName = function() {
return this.name;
};
/**
* Returns the version number for the specified store
*
* @return {number} The version number of the store. Returns 0 if the
* store does not exist.
*/
goog.gears.BaseStore.prototype.getStoreVersion = function() {
try {
return /** @type {number} */ (this.database_.queryValue(
'SELECT Version FROM StoreVersionInfo WHERE StoreName=?',
this.name)) || 0;
} catch (ex) {
return 0;
}
};
/**
* Sets the version number for the specified store
*
* @param {number} version The version number for the store.
* @private
*/
goog.gears.BaseStore.prototype.setStoreVersion_ = function(version) {
// TODO(user): Need to determine if we should enforce the fact
// that store versions are monotonically increasing.
this.database_.execute(
'INSERT OR REPLACE INTO StoreVersionInfo ' +
'(StoreName, Version) VALUES(?,?)',
this.name,
version);
};
/**
* Removes the version number for the specified store
*/
goog.gears.BaseStore.prototype.removeStoreVersion = function() {
try {
this.database_.execute(
'DELETE FROM StoreVersionInfo WHERE StoreName=?',
this.name);
} catch (ex) {
// Ignore error - part of bootstrap process.
}
};
/**
* Generates an SQLITE CREATE TRIGGER statement from a definition array.
* @param {string} onStr the type of trigger to create.
* @param {Object} def a schema statement definition.
* @param {string} notExistsStr string to be included in the create
* indicating what to do.
* @return {string} the statement.
* @private
*/
goog.gears.BaseStore.prototype.getCreateTriggerStatement_ =
function(onStr, def, notExistsStr) {
return 'CREATE TRIGGER ' + notExistsStr + def.name + ' ' +
onStr + ' ON ' + def.tableName +
(def.when ? (' WHEN ' + def.when) : '') +
' BEGIN ' + def.actions.join('; ') + '; END';
};
/**
* Generates an SQLITE CREATE statement from a definition object.
* @param {Object} def a schema statement definition.
* @param {boolean=} opt_ifNotExists true if the table or index should be
* created only if it does not exist. Otherwise trying to create a table
* or index that already exists will result in an exception being thrown.
* @return {string} the statement.
* @private
*/
goog.gears.BaseStore.prototype.getCreateStatement_ =
function(def, opt_ifNotExists) {
var notExists = opt_ifNotExists ? 'IF NOT EXISTS ' : '';
switch (def.type) {
case goog.gears.BaseStore.SchemaType.TABLE:
return 'CREATE TABLE ' + notExists + def.name + ' (\n' +
def.columns.join(',\n ') +
')';
case goog.gears.BaseStore.SchemaType.VIRTUAL_TABLE:
return 'CREATE VIRTUAL TABLE ' + notExists + def.name +
' USING FTS2 (\n' + def.columns.join(',\n ') + ')';
case goog.gears.BaseStore.SchemaType.INDEX:
return 'CREATE' + (def.isUnique ? ' UNIQUE' : '') +
' INDEX ' + notExists + def.name + ' ON ' +
def.tableName + ' (\n' + def.columns.join(',\n ') + ')';
case goog.gears.BaseStore.SchemaType.BEFORE_INSERT_TRIGGER:
return this.getCreateTriggerStatement_('BEFORE INSERT', def, notExists);
case goog.gears.BaseStore.SchemaType.AFTER_INSERT_TRIGGER:
return this.getCreateTriggerStatement_('AFTER INSERT', def, notExists);
case goog.gears.BaseStore.SchemaType.BEFORE_UPDATE_TRIGGER:
return this.getCreateTriggerStatement_('BEFORE UPDATE', def, notExists);
case goog.gears.BaseStore.SchemaType.AFTER_UPDATE_TRIGGER:
return this.getCreateTriggerStatement_('AFTER UPDATE', def, notExists);
case goog.gears.BaseStore.SchemaType.BEFORE_DELETE_TRIGGER:
return this.getCreateTriggerStatement_('BEFORE DELETE', def, notExists);
case goog.gears.BaseStore.SchemaType.AFTER_DELETE_TRIGGER:
return this.getCreateTriggerStatement_('AFTER DELETE', def, notExists);
}
return '';
};
/**
* Generates an SQLITE DROP statement from a definition array.
* @param {Object} def a schema statement definition.
* @return {string} the statement.
* @private
*/
goog.gears.BaseStore.prototype.getDropStatement_ = function(def) {
switch (def.type) {
case goog.gears.BaseStore.SchemaType.TABLE:
case goog.gears.BaseStore.SchemaType.VIRTUAL_TABLE:
return 'DROP TABLE IF EXISTS ' + def.name;
case goog.gears.BaseStore.SchemaType.INDEX:
return 'DROP INDEX IF EXISTS ' + def.name;
case goog.gears.BaseStore.SchemaType.BEFORE_INSERT_TRIGGER:
case goog.gears.BaseStore.SchemaType.AFTER_INSERT_TRIGGER:
case goog.gears.BaseStore.SchemaType.BEFORE_UPDATE_TRIGGER:
case goog.gears.BaseStore.SchemaType.AFTER_UPDATE_TRIGGER:
case goog.gears.BaseStore.SchemaType.BEFORE_DELETE_TRIGGER:
case goog.gears.BaseStore.SchemaType.AFTER_DELETE_TRIGGER:
return 'DROP TRIGGER IF EXISTS ' + def.name;
}
return '';
};
/**
* Creates tables and indicies in the target database.
*
* @param {Array} defs definition arrays. This is an array of objects
* where each object describes a database object to create and drop.
* each object contains a 'type' field which of type
* goog.gears.BaseStore.SchemaType. Each object also contains a
* 'name' which contains the name of the object to create.
* A table object contains a 'columns' field which is an array
* that contains the column definitions for the table.
* A virtual table object contains c 'columns' field which contains
* the name of the columns. They are assumed to be of type text.
* An index object contains a 'tableName' field which is the name
* of the table that the index is on. It contains an 'isUnique'
* field which is a boolean indicating whether the index is
* unqiue or not. It also contains a 'columns' field which is
* an array that contains the columns names (possibly along with the
* ordering) that form the index.
* The trigger objects contain a 'tableName' field indicating the
* table the trigger is on. The type indicates the type of trigger.
* The trigger object may include a 'when' field which contains
* the when clause for the trigger. The trigger object also contains
* an 'actions' field which is an array of strings containing
* the actions for this trigger.
* @param {boolean=} opt_ifNotExists true if the table or index should be
* created only if it does not exist. Otherwise trying to create a table
* or index that already exists will result in an exception being thrown.
*/
goog.gears.BaseStore.prototype.createSchema = function(defs, opt_ifNotExists) {
this.database_.begin();
try {
for (var i = 0; i < defs.length; ++i) {
var sql = this.getCreateStatement_(defs[i], opt_ifNotExists);
this.database_.execute(sql);
}
this.database_.commit();
} catch (ex) {
this.database_.rollback(ex);
}
};
/**
* Drops tables and indicies in a target database.
*
* @param {Array} defs Definition arrays.
*/
goog.gears.BaseStore.prototype.dropSchema = function(defs) {
this.database_.begin();
try {
for (var i = defs.length - 1; i >= 0; --i) {
this.database_.execute(this.getDropStatement_(defs[i]));
}
this.database_.commit();
} catch (ex) {
this.database_.rollback(ex);
}
};
/**
* Creates triggers specified in definitions. Will first attempt
* to drop the trigger with this name first.
*
* @param {Array} defs Definition arrays.
*/
goog.gears.BaseStore.prototype.createTriggers = function(defs) {
this.database_.begin();
try {
for (var i = 0; i < defs.length; i++) {
var def = defs[i];
switch (def.type) {
case goog.gears.BaseStore.SchemaType.BEFORE_INSERT_TRIGGER:
case goog.gears.BaseStore.SchemaType.AFTER_INSERT_TRIGGER:
case goog.gears.BaseStore.SchemaType.BEFORE_UPDATE_TRIGGER:
case goog.gears.BaseStore.SchemaType.AFTER_UPDATE_TRIGGER:
case goog.gears.BaseStore.SchemaType.BEFORE_DELETE_TRIGGER:
case goog.gears.BaseStore.SchemaType.AFTER_DELETE_TRIGGER:
this.database_.execute('DROP TRIGGER IF EXISTS ' + def.name);
this.database_.execute(this.getCreateStatement_(def));
break;
}
}
this.database_.commit();
} catch (ex) {
this.database_.rollback(ex);
}
};
/**
* Returns true if the table exists in the database
*
* @param {string} name The table name.
* @return {boolean} Whether the table exists in the database.
*/
goog.gears.BaseStore.prototype.hasTable = function(name) {
return this.hasInSchema_('table', name);
};
/**
* Returns true if the index exists in the database
*
* @param {string} name The index name.
* @return {boolean} Whether the index exists in the database.
*/
goog.gears.BaseStore.prototype.hasIndex = function(name) {
return this.hasInSchema_('index', name);
};
/**
* @param {string} name The name of the trigger.
* @return {boolean} Whether the schema contains a trigger with the given name.
*/
goog.gears.BaseStore.prototype.hasTrigger = function(name) {
return this.hasInSchema_('trigger', name);
};
/**
* Returns true if the database contains the index or table
*
* @private
* @param {string} type The type of object to test for, 'table' or 'index'.
* @param {string} name The table or index name.
* @return {boolean} Whether the database contains the index or table.
*/
goog.gears.BaseStore.prototype.hasInSchema_ = function(type, name) {
return this.database_.queryValue('SELECT 1 FROM SQLITE_MASTER ' +
'WHERE TYPE=? AND NAME=?',
type,
name) != null;
};
/** @override */
goog.gears.BaseStore.prototype.disposeInternal = function() {
goog.gears.BaseStore.superClass_.disposeInternal.call(this);
this.database_ = null;
};
/**
* HACK(arv): The JSCompiler check for undefined properties sees that these
* fields are never set and raises warnings.
* @type {Array.<Object>}
* @private
*/
goog.gears.schemaDefDummy_ = [
{
type: '',
name: '',
when: '',
tableName: '',
actions: [],
isUnique: false
}
];
@@ -0,0 +1,934 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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 This file contains functions for using the Gears database.
*/
goog.provide('goog.gears.Database');
goog.provide('goog.gears.Database.EventType');
goog.provide('goog.gears.Database.TransactionEvent');
goog.require('goog.array');
goog.require('goog.debug');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.require('goog.gears');
goog.require('goog.json');
goog.require('goog.log');
/**
* Class that for accessing a Gears database
*
* @constructor
* @extends {goog.events.EventTarget}
* @param {string} userId the id token for this user.
* @param {string} appName the name of the application creating this database.
*/
goog.gears.Database = function(userId, appName) {
goog.events.EventTarget.call(this);
var factory = goog.gears.getFactory();
try {
/**
* The pointer to the Gears database object
* @private
*/
this.database_ = factory.create('beta.database', '1.0');
} catch (ex) {
// We will fail here if we cannot get a version of the database that is
// compatible with the JS code.
throw Error('Could not create the database. ' + ex.message);
}
if (this.database_ != null) {
var dbId = userId + '-' + appName;
var safeDbId = goog.gears.makeSafeFileName(dbId);
if (dbId != safeDbId) {
goog.log.info(this.logger_, 'database name ' + dbId + '->' + safeDbId);
}
this.safeDbId_ = safeDbId;
this.database_.open(safeDbId);
} else {
throw Error('Could not create the database');
}
};
goog.inherits(goog.gears.Database, goog.events.EventTarget);
/**
* Constants for transaction event names.
* @enum {string}
*/
goog.gears.Database.EventType = {
BEFOREBEGIN: 'beforebegin',
BEGIN: 'begin',
BEFORECOMMIT: 'beforecommit',
COMMIT: 'commit',
BEFOREROLLBACK: 'beforerollback',
ROLLBACK: 'rollback'
};
/**
* Event info for transaction events.
* @extends {goog.events.Event}
* @constructor
* @param {goog.gears.Database.EventType} eventType The type of event.
*/
goog.gears.Database.TransactionEvent = function(eventType) {
goog.events.Event.call(this, eventType);
};
goog.inherits(goog.gears.Database.TransactionEvent, goog.events.Event);
/**
* Logger object
* @type {goog.log.Logger}
* @private
*/
goog.gears.Database.prototype.logger_ =
goog.log.getLogger('goog.gears.Database');
/**
* The safe name of the database.
* @type {string}
* @private
*/
goog.gears.Database.prototype.safeDbId_;
/**
* True if the database will be using transactions
* @private
* @type {boolean}
*/
goog.gears.Database.prototype.useTransactions_ = true;
/**
* Number of currently openned transactions. Use this to allow
* for nested Begin/Commit transactions. Will only do the real
* commit when this equals 0
* @private
* @type {number}
*/
goog.gears.Database.prototype.openTransactions_ = 0;
/**
* True if the outstanding opened transactions need to be rolled back
* @private
* @type {boolean}
*/
goog.gears.Database.prototype.needsRollback_ = false;
/**
* The default type of begin statement to use.
* @type {string}
* @private
*/
goog.gears.Database.prototype.defaultBeginType_ = 'IMMEDIATE';
/**
* Indicaton of the level of the begin. This is used to make sure
* nested begins do not elivate the level of the begin.
* @enum {number}
* @private
*/
goog.gears.Database.BeginLevels_ = {
'DEFERRED': 0,
'IMMEDIATE': 1,
'EXCLUSIVE': 2
};
/**
* The begin level of the currently opened transaction
* @type {goog.gears.Database.BeginLevels_}
* @private
*/
goog.gears.Database.prototype.currentBeginLevel_ =
goog.gears.Database.BeginLevels_['DEFERRED'];
/**
* Returns an array of arrays, where each sub array contains the selected
* values for each row in the result set.
* result values
*
* @param {GearsResultSet} rs the result set returned by execute.
* @return {Array} An array of arrays. Returns an empty array if
* there are no matching rows.
*/
goog.gears.Database.resultSetToArrays = function(rs) {
var rv = [];
if (rs) {
var cols = rs['fieldCount']();
while (rs['isValidRow']()) {
var row = new Array(cols);
for (var i = 0; i < cols; i++) {
row[i] = rs['field'](i);
}
rv.push(row);
rs['next']();
}
}
return rv;
};
/**
* Returns a 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.
*/
goog.gears.Database.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;
};
/**
* Returns an array containing the first item of each row in a result set.
* This is useful for query that returns one column
*
* @param {GearsResultSet} rs the result set returned by execute.
* @return {Array.<Object>} An array containing the values in the first column
* Returns an empty array if there are no matching rows.
*/
goog.gears.Database.resultSetToValueArray = function(rs) {
var rv = [];
if (rs) {
while (rs['isValidRow']()) {
rv.push(rs['field'](0));
rs['next']();
}
}
return rv;
};
/**
* Returns a single value from the results (first column in first row).
*
* @param {GearsResultSet} rs the result set returned by execute.
* @return {(number,string,null)} The first item in the first row of the
* result set. Returns null if there are no matching rows.
*/
goog.gears.Database.resultSetToValue = function(rs) {
if (rs && rs['isValidRow']()) {
return rs['field'](0);
} else {
return null;
}
};
/**
* Returns a single hashed object from the result set (the first row),
* 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 {Object} a hash map with the key-value-pairs from the first row.
* Returns null is there are no matching rows.
*/
goog.gears.Database.resultSetToObject = function(rs) {
if (rs && rs['isValidRow']()) {
var rv = {};
var cols = rs['fieldCount']();
for (var i = 0; i < cols; i++) {
rv[rs['fieldName'](i)] = rs['field'](i);
}
return rv;
} else {
return null;
}
};
/**
* Returns an array of the first row in the result set
*
* @param {GearsResultSet} rs the result set returned by execute.
* @return {Array} An array containing the values in the
* first result set. Returns an empty array if there no
* matching rows.
*/
goog.gears.Database.resultSetToArray = function(rs) {
var rv = [];
if (rs && rs['isValidRow']()) {
var cols = rs['fieldCount']();
for (var i = 0; i < cols; i++) {
rv[i] = rs['field'](i);
}
}
return rv;
};
/**
* Execute a sql statement with a set of arguments
*
* @param {string} sql The sql statement to execute.
* @param {...*} var_args The arguments to execute, either as a single
* array argument or as var_args.
* @return {GearsResultSet} The results.
*/
goog.gears.Database.prototype.execute = function(sql, var_args) {
goog.log.log(this.logger_, goog.log.Level.FINER, 'Executing SQL: ' + sql);
// TODO(user): Remove when Gears adds more rubust type handling.
// Safety measure since Gears behaves very badly if it gets an unexpected
// data type.
sql = String(sql);
var args;
try {
if (arguments.length == 1) {
return this.database_.execute(sql);
}
if (arguments.length == 2 && goog.isArray(arguments[1])) {
args = arguments[1];
} else {
args = goog.array.slice(arguments, 1);
}
goog.log.log(this.logger_, goog.log.Level.FINEST, 'SQL arguments: ' + args);
// TODO(user): Type safety checking for args?
return this.database_.execute(sql, args);
} catch (e) {
if (args) {
sql += ': ' + goog.json.serialize(args);
}
throw goog.debug.enhanceError(e, sql);
}
};
/**
* This is useful to remove all the arguments juggling from inside the
* different helper functions.
*
* @private
* @param {string} sql The SQL statement.
* @param {Object} params An Array or arguments Object containing the query
* params. If the element at startIndex is an array, it will be used as
* the arguments passed to the execute method.
* @param {number} startIndex Where to start getting the query params from
* params.
* @return {GearsResultSet} The results of the command.
*/
goog.gears.Database.prototype.executeVarArgs_ = function(sql, params,
startIndex) {
if (params.length == 0 || startIndex >= params.length) {
return this.execute(sql);
} else {
if (goog.isArray(params[startIndex])) {
return this.execute(sql, params[startIndex]);
}
var args = Array.prototype.slice.call(
/** @type {{length:number}} */ (params), startIndex);
return this.execute(sql, args);
}
};
/**
* Helper methods for queryArrays, queryObjectArray, queryValueArray,
* queryValue, queryObject.
*
* @private
* @param {string} sql The SQL statement.
* @param {Function} f The function to call on the result set.
* @param {Object} params query params as an Array or an arguments object. If
* the element at startIndex is an array, it will be used as the arguments
* passed to the execute method.
* @param {number} startIndex Where to start getting the query params from
* params.
* @return {(Object,number,string,boolean,undefined,null)} whatever 'f'
* returns, which could be any type.
*/
goog.gears.Database.prototype.queryObject_ = function(sql,
f, params, startIndex) {
var rs = this.executeVarArgs_(sql, params, startIndex);
try {
return f(rs);
} finally {
if (rs) {
rs.close();
}
}
};
/**
* This calls query on the database and builds a two dimensional array
* containing the result.
*
* @param {string} sql The SQL statement.
* @param {...*} var_args Query params. An array or multiple arguments.
* @return {Array} An array of arrays containing the results of the query.
*/
goog.gears.Database.prototype.queryArrays = function(sql, var_args) {
return /** @type {Array} */ (this.queryObject_(sql,
goog.gears.Database.resultSetToArrays,
arguments,
1));
};
/**
* This calls query on the database and builds an array containing hashes
*
* @param {string} sql Ths SQL statement.
* @param {...*} var_args query params. An array or multiple arguments.
* @return {Array} An array of hashes containing the results of the query.
*/
goog.gears.Database.prototype.queryObjectArray = function(sql, var_args) {
return /** @type {Array} */ (this.queryObject_(sql,
goog.gears.Database.resultSetToObjectArray,
arguments,
1));
};
/**
* This calls query on the database and returns an array containing the values
* in the first column. This is useful if the result set only contains one
* column.
*
* @param {string} sql SQL statement.
* @param {...*} var_args query params. An array or multiple arguments.
* @return {Array} The values in the first column.
*/
goog.gears.Database.prototype.queryValueArray = function(sql, var_args) {
return /** @type {Array} */ (this.queryObject_(sql,
goog.gears.Database.resultSetToValueArray,
arguments,
1));
};
/**
* This calls query on the database and returns the first value in the first
* row.
*
* @param {string} sql SQL statement.
* @param {...*} var_args query params. An array or multiple arguments.
* @return {(number,string,null)} The first value in
* the first row.
*/
goog.gears.Database.prototype.queryValue = function(sql, var_args) {
return /** @type {(number,string,null)} */ (this.queryObject_(sql,
goog.gears.Database.resultSetToValue,
arguments,
1));
};
/**
* This calls query on the database and returns the first row as a hash map
* where the keys are the column names.
*
* @param {string} sql SQL statement.
* @param {...*} var_args query params. An array or multiple arguments.
* @return {Object} The first row as a hash map.
*/
goog.gears.Database.prototype.queryObject = function(sql, var_args) {
return /** @type {Object} */ (this.queryObject_(sql,
goog.gears.Database.resultSetToObject,
arguments,
1));
};
/**
* This calls query on the database and returns the first row as an array
*
* @param {string} sql SQL statement.
* @param {...*} var_args query params. An array or multiple arguments.
* @return {Array} The first row as an array.
*/
goog.gears.Database.prototype.queryArray = function(sql, var_args) {
return /** @type {Array} */ (this.queryObject_(sql,
goog.gears.Database.resultSetToArray,
arguments,
1));
};
/**
* For each value in the result set f will be called with the following
* parameters; value, rowIndex, columnIndex, columnName. Values will continue
* being processed as long as f returns true.
*
* @param {string} sql The SQL statement to execute.
* @param {Function} f Function to call for each value.
* @param {Object=} opt_this If present f will be called using this object as
* 'this'.
* @param {...*} var_args query params. An array or multiple arguments.
*/
goog.gears.Database.prototype.forEachValue = function(sql,
f, opt_this, var_args) {
var rs = this.executeVarArgs_(sql, arguments, 3);
try {
var rowIndex = 0;
var cols = rs['fieldCount']();
var colNames = [];
for (var i = 0; i < cols; i++) {
colNames.push(rs['fieldName'](i));
}
mainLoop: while (rs['isValidRow']()) {
for (var i = 0; i < cols; i++) {
if (!f.call(opt_this, rs['field'](i), rowIndex, i, colNames[i])) {
break mainLoop;
}
}
rs['next']();
rowIndex++;
}
} finally {
rs.close();
}
};
/**
* For each row in the result set f will be called with the following
* parameters: row (array of values), rowIndex and columnNames. Rows will
* continue being processed as long as f returns true.
*
* @param {string} sql The SQL statement to execute.
* @param {Function} f Function to call for each row.
* @param {Object=} opt_this If present f will be called using this
* object as 'this'.
* @param {...*} var_args query params. An array or multiple arguments.
*/
goog.gears.Database.prototype.forEachRow = function(sql,
f, opt_this, var_args) {
var rs = this.executeVarArgs_(sql, arguments, 3);
try {
var rowIndex = 0;
var cols = rs['fieldCount']();
var colNames = [];
for (var i = 0; i < cols; i++) {
colNames.push(rs['fieldName'](i));
}
var row;
while (rs['isValidRow']()) {
row = [];
for (var i = 0; i < cols; i++) {
row.push(rs['field'](i));
}
if (!f.call(opt_this, row, rowIndex, colNames)) {
break;
}
rs['next']();
rowIndex++;
}
} finally {
rs.close();
}
};
/**
* Executes a function transactionally.
*
* @param {Function} func the function to execute transactionally.
* Takes no params.
* @return {Object|number|boolean|string|null|undefined} the return value
* of 'func()'.
*/
goog.gears.Database.prototype.transact = function(func) {
this.begin();
try {
var result = func();
this.commit();
} catch (e) {
this.rollback(e);
throw e;
}
return result;
};
/**
* Helper that performs either a COMMIT or ROLLBACK command and dispatches
* pre/post commit/rollback events.
*
* @private
*
* @param {boolean} rollback Whether to rollback or commit.
* @return {boolean} True if the transaction was closed, false otherwise.
*/
goog.gears.Database.prototype.closeTransaction_ = function(rollback) {
// Send before rollback/commit event
var cmd;
var eventType;
cmd = rollback ? 'ROLLBACK' : 'COMMIT';
eventType = rollback ? goog.gears.Database.EventType.BEFOREROLLBACK :
goog.gears.Database.EventType.BEFORECOMMIT;
var event = new goog.gears.Database.TransactionEvent(eventType);
var returnValue = this.dispatchEvent(event);
// Only commit/rollback and send events if none of the event listeners
// called preventDefault().
if (returnValue) {
this.database_.execute(cmd);
this.openTransactions_ = 0;
eventType = rollback ? goog.gears.Database.EventType.ROLLBACK :
goog.gears.Database.EventType.COMMIT;
this.dispatchEvent(new goog.gears.Database.TransactionEvent(eventType));
}
return returnValue;
};
/**
* Whether transactions are used for the database
*
* @param {boolean} b Whether to use transactions or not.
*/
goog.gears.Database.prototype.setUseTransactions = function(b) {
this.useTransactions_ = b;
};
/**
* Whether transactions are used for the database
*
* @return {boolean} true if transactions should be used.
*/
goog.gears.Database.prototype.getUseTransactions = function() {
return this.useTransactions_;
};
/**
* Sets the default begin type.
*
* @param {string} beginType The default begin type.
*/
goog.gears.Database.prototype.setDefaultBeginType = function(beginType) {
if (beginType in goog.gears.Database.BeginLevels_) {
this.defaultBeginType_ = beginType;
}
};
/**
* Marks the beginning of a database transaction. Does a real BEGIN operation
* if useTransactions is true for this database and the this is the first
* opened transaction
* @private
*
* @param {string} beginType the type of begin comand.
* @return {boolean} true if the BEGIN has been executed.
*/
goog.gears.Database.prototype.beginTransaction_ = function(beginType) {
if (this.useTransactions_) {
if (this.openTransactions_ == 0) {
this.needsRollback_ = false;
this.dispatchEvent(
new goog.gears.Database.TransactionEvent(
goog.gears.Database.EventType.BEFOREBEGIN));
this.database_.execute('BEGIN ' + beginType);
this.currentBeginLevel_ =
goog.gears.Database.BeginLevels_[beginType];
this.openTransactions_ = 1;
try {
this.dispatchEvent(
new goog.gears.Database.TransactionEvent(
goog.gears.Database.EventType.BEGIN));
} catch (e) {
this.database_.execute('ROLLBACK');
this.openTransactions_ = 0;
throw e;
}
return true;
} else if (this.needsRollback_) {
throw Error(
'Cannot begin a transaction with a rollback pending');
} else if (goog.gears.Database.BeginLevels_[beginType] >
this.currentBeginLevel_) {
throw Error(
'Cannot elevate the level within a nested begin');
} else {
this.openTransactions_++;
}
}
return false;
};
/**
* Marks the beginning of a database transaction using the default begin
* type. Does a real BEGIN operation if useTransactions is true for this
* database and this is the first opened transaction. This will throw
* an exception if this is a nested begin and is trying to elevate the
* begin type from the original BEGIN that was processed.
*
* @return {boolean} true if the BEGIN has been executed.
*/
goog.gears.Database.prototype.begin = function() {
return this.beginTransaction_(this.defaultBeginType_);
};
/**
* Marks the beginning of a deferred database transaction.
* Does a real BEGIN operation if useTransactions is true for this
* database and this is the first opened transaction.
*
* @return {boolean} true if the BEGIN has been executed.
*/
goog.gears.Database.prototype.beginDeferred = function() {
return this.beginTransaction_('DEFERRED');
};
/**
* Marks the beginning of an immediate database transaction.
* Does a real BEGIN operation if useTransactions is true for this
* database and this is the first opened transaction. This will throw
* an exception if this is a nested begin and is trying to elevate the
* begin type from the original BEGIN that was processed.
*
* @return {boolean} true if the BEGIN has been executed.
*/
goog.gears.Database.prototype.beginImmediate = function() {
return this.beginTransaction_('IMMEDIATE');
};
/**
* Marks the beginning of an exclusive database transaction.
* Does a real BEGIN operation if useTransactions is true for this
* database and this is the first opened transaction. This will throw
* an exception if this is a nested begin and is trying to elevate the
* begin type from the original BEGIN that was processed.
*
* @return {boolean} true if the BEGIN has been executed.
*/
goog.gears.Database.prototype.beginExclusive = function() {
return this.beginTransaction_('EXCLUSIVE');
};
/**
* Marks the end of a successful transaction. Will do a real COMMIT
* if this the last outstanding transaction unless a nested transaction
* was closed with a ROLLBACK in which case a real ROLLBACK will be performed.
*
* @return {boolean} true if the COMMIT has been executed.
*/
goog.gears.Database.prototype.commit = function() {
if (this.useTransactions_) {
if (this.openTransactions_ <= 0) {
throw Error('Unbalanced transaction');
}
// Only one left.
if (this.openTransactions_ == 1) {
var closed = this.closeTransaction_(this.needsRollback_);
return !this.needsRollback_ && closed;
} else {
this.openTransactions_--;
}
}
return false;
};
/**
* Marks the end of an unsuccessful transaction. Will do a real ROLLBACK
* if this the last outstanding transaction, otherwise the real ROLLBACK will
* be deferred until the last outstanding transaction is closed.
*
* @param {Error=} opt_e the exception that caused this rollback. If it
* is provided then that exception is rethown if
* the rollback does not take place.
* @return {boolean} true if the ROLLBACK has been executed or if we are not
* using transactions.
*/
goog.gears.Database.prototype.rollback = function(opt_e) {
var closed = true;
if (this.useTransactions_) {
if (this.openTransactions_ <= 0) {
throw Error('Unbalanced transaction');
}
// Only one left.
if (this.openTransactions_ == 1) {
closed = this.closeTransaction_(true);
} else {
this.openTransactions_--;
this.needsRollback_ = true;
if (opt_e) {
throw opt_e;
}
return false;
}
}
return closed;
};
/**
* Returns whether or not we're in a transaction.
*
* @return {boolean} true if a transaction has been started and is not yet
* complete.
*/
goog.gears.Database.prototype.isInTransaction = function() {
return this.useTransactions_ && this.openTransactions_ > 0;
};
/**
* Ensures there is no open transaction upon return. An existing open
* transaction is rolled back.
*
* @param {string=} opt_logMsgPrefix a prefix to the message that is logged when
* an unexpected open transaction is found.
*/
goog.gears.Database.prototype.ensureNoTransaction = function(opt_logMsgPrefix) {
if (this.isInTransaction()) {
goog.log.warning(this.logger_, (opt_logMsgPrefix || 'ensureNoTransaction') +
' - rolling back unexpected transaction');
do {
this.rollback();
} while (this.isInTransaction());
}
};
/**
* Returns whether or not the current transaction has a pending rollback.
* Returns false if there is no current transaction.
*
* @return {boolean} Whether a started transaction has a rollback pending.
*/
goog.gears.Database.prototype.needsRollback = function() {
return this.useTransactions_ &&
this.openTransactions_ > 0 &&
this.needsRollback_;
};
/**
* Returns the time in Ms that database operations have currently
* consumed. This only exists in debug builds, but it still may be useful
* for goog.gears.Trace.
*
* @return {number} The time in Ms that database operations have currently
* consumed.
*/
goog.gears.Database.prototype.getExecutionTime = function() {
return this.database_['executeMsec'] || 0;
};
/**
* @return {number} The id of the last inserted row.
*/
goog.gears.Database.prototype.getLastInsertRowId = function() {
return this.database_['lastInsertRowId'];
};
/**
* Opens the database.
*/
goog.gears.Database.prototype.open = function() {
if (this.database_ && this.safeDbId_) {
this.database_.open(this.safeDbId_);
} else {
throw Error('Could not open the database');
}
};
/**
* Closes the database.
*/
goog.gears.Database.prototype.close = function() {
if (this.database_) {
this.database_.close();
}
};
/** @override */
goog.gears.Database.prototype.disposeInternal = function() {
goog.gears.Database.superClass_.disposeInternal.call(this);
this.database_ = null;
};
/**
* Determines if the exception is a locking error.
* @param {Error|string} ex The exception object or error string.
* @return {boolean} Whether this is a database locked exception.
*/
goog.gears.Database.isLockedException = function(ex) {
// TODO(user): change the test when gears provides a reasonable
// error code to check.
var message = goog.isString(ex) ? ex : ex.message;
return !!message && message.indexOf('database is locked') >= 0;
};
/**
* Removes the database.
* @throws {Error} This requires Gears 0.5 or newer and will throw an error if
* called on a too old version of Gears.
*/
goog.gears.Database.prototype.remove = function() {
this.database_.remove();
};
@@ -0,0 +1,228 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// 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 This file contains functions for using Gears.
*/
// TODO(arv): The Gears team is planning to inject the Gears factory as
// google.gears.factory in the main thread as well. Currently it is only
// injected in the worker threads.
goog.provide('goog.gears');
goog.require('goog.string');
/**
* Returns a new Gears factory object.
* @return {Object} the Gears factory object if available or null otherwise.
*/
goog.gears.getFactory = function() {
if (goog.gears.factory_ != undefined) {
return goog.gears.factory_;
}
// In the worker threads Gears injects google.gears.factory. They also plan
// to do this in the main thread.
var factory = goog.getObjectByName('google.gears.factory');
if (factory) {
return goog.gears.factory_ = factory;
}
// Mozilla
/** @preserveTry */
try {
var gearsFactory = /** @type {Function} */
(goog.getObjectByName('GearsFactory'));
return goog.gears.factory_ = new gearsFactory;
} catch (ex) {
// fall through
}
// MSIE
/** @preserveTry */
try {
factory = new ActiveXObject('Gears.Factory');
var buildInfo = factory.getBuildInfo();
if (buildInfo.indexOf('ie_mobile') != -1) {
factory.privateSetGlobalObject(goog.global);
}
return goog.gears.factory_ = factory;
} catch (ex) {
// fall through
}
return goog.gears.factory_ = goog.gears.tryGearsObject_();
};
/**
* Attempt to create a factory by adding an object element to the DOM.
* This covers the case for safari.
* @return {Function} The factory, or null.
* @private
*/
goog.gears.tryGearsObject_ = function() {
// HACK(arv): Use square bracket notation so this can compile in an
// environment without a DOM.
var win = goog.getObjectByName('window');
// Safari
if (win && win['navigator']['mimeTypes']['application/x-googlegears']) {
/** @preserveTry */
try {
var doc = win['document'];
var factory = doc['getElementById']('gears-factory');
// If missing, create a place for it
if (!factory) {
factory = doc['createElement']('object');
factory['style']['display'] = 'none';
factory['width'] = '0';
factory['height'] = '0';
factory['type'] = 'application/x-googlegears';
factory['id'] = 'gears-factory';
doc['documentElement']['appendChild'](factory);
}
// Even if Gears failed to get created we get an object element. Make
// sure that it has a create method before assuming it is a Gears factory.
if (typeof factory.create != 'undefined') {
return factory;
}
} catch (ex) {
// fall through
}
}
return null;
};
/**
* Cached result of getFactory
* @type {Object|undefined}
* @private
*/
goog.gears.factory_ = undefined;
/**
* Whether we can create a Gears factory object. This does not cache the
* result.
* @return {boolean} true if we can create a Gears factory object.
*/
goog.gears.hasFactory = function() {
if (goog.gears.hasFactory_ != undefined) return goog.gears.hasFactory_;
// Use [] notation so we don't have to put this in externs
var factory = goog.getObjectByName('google.gears.factory');
if (factory || goog.getObjectByName('GearsFactory')) {
return goog.gears.hasFactory_ = true;
}
// Try ActiveXObject for IE
if (typeof ActiveXObject != 'undefined') {
/** @preserveTry */
try {
new ActiveXObject('Gears.Factory');
return goog.gears.hasFactory_ = true;
} catch (ex) {
return goog.gears.hasFactory_ = false;
}
}
// NOTE(user): For safari we have to actually create an object element
// in the DOM. We have to do it in hasFactory so we can reliably know whether
// there actually is a factory, else we can get into a situation, e.g. in
// FF 3.5.3 where the Gears add-on is disabled because it's incompatible
// with FF but application/x-googlegears is still in the mimeTypes object.
//
// HACK(arv): Use object by name so this can compile in an environment without
// a DOM.
var mimeTypes = goog.getObjectByName('navigator.mimeTypes');
if (mimeTypes && mimeTypes['application/x-googlegears']) {
factory = goog.gears.tryGearsObject_();
if (factory) {
// Might as well cache it while we have it.
goog.gears.factory_ = factory;
return goog.gears.hasFactory_ = true;
}
}
return goog.gears.hasFactory_ = false;
};
/**
* Cached result of hasFactory.
* @type {boolean|undefined}
* @private
*/
goog.gears.hasFactory_ = undefined;
/**
* Maximum file name length.
* @type {number}
* @private
*/
goog.gears.MAX_FILE_NAME_LENGTH_ = 64;
/**
* Allow 10 characters for hash (#goog.string.hashCode).
* @type {number}
* @private
*/
goog.gears.MAX_FILE_NAME_PREFIX_LENGTH_ = goog.gears.MAX_FILE_NAME_LENGTH_ - 10;
/**
* Gears only allows file names up to 64 characters, so this function shortens
* file names to fit in this limit. #goog.string.hashCode is used to compress
* the name. This also removes invalid characters.
* @param {string} originalFileName Original (potentially unsafe) file name.
* @return {string} Safe file name. If originalFileName null or empty,
* return originalFileName.
* @throws {Error} If originalFileName is null, empty or contains only
* invalid characters.
*/
goog.gears.makeSafeFileName = function(originalFileName) {
if (!originalFileName) {
throw Error('file name empty');
}
// Safety measure since Gears behaves very badly if it gets an unexpected
// data type.
originalFileName = String(originalFileName);
// TODO(user): This should be removed when the Gears code
// gets fixed to allow for any id to be passed in. Right now
// it fails to create a user specific database if the characters
// sent in are non alphanumeric.
var sanitizedFileName = originalFileName.replace(/[^a-zA-Z0-9\.\-@_]/g, '');
if (!sanitizedFileName) {
throw Error('file name invalid: ' + originalFileName);
}
if (sanitizedFileName.length <= goog.gears.MAX_FILE_NAME_LENGTH_) {
return sanitizedFileName;
}
// Keep as many characters in original as we can, then hash the rest.
var prefix = sanitizedFileName.substring(
0, goog.gears.MAX_FILE_NAME_PREFIX_LENGTH_);
return prefix + String(goog.string.hashCode(originalFileName));
};
@@ -0,0 +1,80 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 This file contains functions for setting up the standard
* goog.net.XmlHttp factory (and, therefore, goog.net.XhrIo) to work using
* the HttpRequest object Gears provides.
*
*/
goog.provide('goog.gears.HttpRequest');
goog.require('goog.Timer');
goog.require('goog.gears');
goog.require('goog.net.WrapperXmlHttpFactory');
goog.require('goog.net.XmlHttp');
/**
* Sets up the Gears HttpRequest's to be the default HttpRequest's used via
* the goog.net.XmlHttp factory.
*/
goog.gears.HttpRequest.setup = function() {
// Set the XmlHttp factory.
goog.net.XmlHttp.setGlobalFactory(
new goog.net.WrapperXmlHttpFactory(
goog.gears.HttpRequest.factory_,
goog.gears.HttpRequest.optionsFactory_));
// Use the Gears timer as the default timer object to ensure that the XhrIo
// timeouts function in the Workers.
goog.Timer.defaultTimerObject = goog.gears.getFactory().create(
'beta.timer', '1.0');
};
/**
* The factory for creating Gears HttpRequest's.
* @return {!(GearsHttpRequest|XMLHttpRequest)} The request object.
* @private
*/
goog.gears.HttpRequest.factory_ = function() {
return goog.gears.getFactory().create('beta.httprequest', '1.0');
};
/**
* The options object for the Gears HttpRequest.
* @type {!Object}
* @private
*/
goog.gears.HttpRequest.options_ = {};
// As of Gears API v.2 (build version 0.1.56.0), setting onreadystatechange to
// null in FF will cause the browser to crash.
goog.gears.HttpRequest.options_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] =
true;
/**
* The factory for creating the options object for Gears HttpRequest's.
* @return {!Object} The options.
* @private
*/
goog.gears.HttpRequest.optionsFactory_ = function() {
return goog.gears.HttpRequest.options_;
};
@@ -0,0 +1,130 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 This class lives on a worker thread and it intercepts the
* goog.debug.Logger objects and sends a LOGGER command to the main thread
* instead.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.gears.LoggerClient');
goog.require('goog.Disposable');
goog.require('goog.debug');
goog.require('goog.debug.Logger');
/**
* Singleton class that overrides the goog.debug.Logger to send log commands
* to the main thread.
* @param {goog.gears.Worker} mainThread The main thread that has the
* logger server running.
* @param {number} logCommandId The command id used for logging.
* @param {string=} opt_workerName This, if present, is added to the error
* object when serializing it.
* @constructor
* @extends {goog.Disposable}
*/
goog.gears.LoggerClient = function(mainThread, logCommandId, opt_workerName) {
if (goog.gears.LoggerClient.instance_) {
return goog.gears.LoggerClient.instance_;
}
goog.Disposable.call(this);
/**
* The main thread object.
* @type {goog.gears.Worker}
* @private
*/
this.mainThread_ = mainThread;
/**
* The command id to use to send log commands to the main thread.
* @type {number}
* @private
*/
this.logCommandId_ = logCommandId;
/**
* The name of the worker thread.
* @type {string}
* @private
*/
this.workerName_ = opt_workerName || '';
var loggerClient = this;
// Override the log method
goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
var name = this.getName();
loggerClient.sendLog_(
name, logRecord.getLevel(), logRecord.getMessage(),
logRecord.getException());
};
goog.gears.LoggerClient.instance_ = this;
};
goog.inherits(goog.gears.LoggerClient, goog.Disposable);
/**
* The singleton instance if any.
* @type {goog.gears.LoggerClient?}
* @private
*/
goog.gears.LoggerClient.instance_ = null;
/**
* Sends a log message to the main thread.
* @param {string} name The name of the logger.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {Object=} opt_exception An exception associated with the message.
* @private
*/
goog.gears.LoggerClient.prototype.sendLog_ = function(name,
level,
msg,
opt_exception) {
var exception;
if (opt_exception) {
var prefix = this.workerName_ ? this.workerName_ + ': ' : '';
exception = {
message: prefix + opt_exception.message,
stack: opt_exception.stack ||
goog.debug.getStacktrace(goog.debug.Logger.prototype.log)
};
// Add messageN to the exception in case it was added using
// goog.debug.enhanceError.
for (var i = 0; 'message' + i in opt_exception; i++) {
exception['message' + i] = String(opt_exception['message' + i]);
}
}
this.mainThread_.sendCommand(
this.logCommandId_,
[name, level.value, msg, exception]);
};
/** @override */
goog.gears.LoggerClient.prototype.disposeInternal = function() {
goog.gears.LoggerClient.superClass_.disposeInternal.call(this);
this.mainThread_ = null;
goog.gears.LoggerClient.instance_ = null;
};
@@ -0,0 +1,157 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 This class lives on the main thread and takes care of incoming
* logger commands from a worker thread.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.gears.LoggerServer');
goog.require('goog.Disposable');
goog.require('goog.gears.Worker.EventType');
goog.require('goog.log');
goog.require('goog.log.Level');
/**
* Creates an object that listens to incoming LOG commands and forwards them
* to a goog.debug.Logger
* @param {goog.gears.Worker} worker The worker thread that
* we are managing the loggers on.
* @param {number} logCommandId The command id used for logging.
* @param {string=} opt_workerName The name of the worker. If present then this
* is added to the log records and to exceptions as {@code workerName}.
* @constructor
* @extends {goog.Disposable}
*/
goog.gears.LoggerServer = function(worker, logCommandId, opt_workerName) {
goog.Disposable.call(this);
/**
* The command id to use to receive log commands from the workers.
* @type {number}
* @private
*/
this.logCommandId_ = logCommandId;
/**
* The worker thread object.
* @type {goog.gears.Worker}
* @private
*/
this.worker_ = worker;
/**
* The name of the worker.
* @type {string}
* @private
*/
this.workerName_ = opt_workerName || '';
/**
* Message prefix containing worker ID.
* @type {string}
* @private
*/
this.msgPrefix_ = '[' + worker.getId() + '] ';
// Listen for command's from the worker to handle the log command.
worker.addEventListener(goog.gears.Worker.EventType.COMMAND,
this.onCommand_, false, this);
};
goog.inherits(goog.gears.LoggerServer, goog.Disposable);
/**
* Whether to show the ID of the worker as a prefix to the shown message.
* @type {boolean}
* @private
*/
goog.gears.LoggerServer.prototype.useMessagePrefix_ = true;
/**
* @return {boolean} * Whether to show the ID of the worker as a prefix to the
* shown message.
*/
goog.gears.LoggerServer.prototype.getUseMessagePrefix = function() {
return this.useMessagePrefix_;
};
/**
* Whether to prefix the message text with the worker ID.
* @param {boolean} b True to prefix the messages.
*/
goog.gears.LoggerServer.prototype.setUseMessagePrefix = function(b) {
this.useMessagePrefix_ = b;
};
/**
* Event handler for the command event of the thread.
* @param {goog.gears.WorkerEvent} e The command event sent by the the
* worker thread.
* @private
*/
goog.gears.LoggerServer.prototype.onCommand_ = function(e) {
var message = /** @type {Array} */ (e.message);
var commandId = message[0];
if (commandId == this.logCommandId_) {
var params = message[1];
var i = 0;
var name = params[i++];
// The old version sent the level name as well. We no longer need it so
// we just step over it.
if (params.length == 5) {
i++;
}
var levelValue = params[i++];
var level = goog.log.Level.getPredefinedLevelByValue(levelValue);
if (level) {
var msg = (this.useMessagePrefix_ ? this.msgPrefix_ : '') + params[i++];
var exception = params[i++];
var logger = goog.log.getLogger(name);
var logRecord = logger.getLogRecord(level, msg, exception);
if (this.workerName_) {
logRecord.workerName = this.workerName_;
// Note that we happen to know that getLogRecord just references the
// exception object so we can continue to modify it as needed.
if (exception) {
exception.workerName = this.workerName_;
}
}
logger.logRecord(logRecord);
}
// ignore others for now
}
};
/** @override */
goog.gears.LoggerServer.prototype.disposeInternal = function() {
goog.gears.LoggerServer.superClass_.disposeInternal.call(this);
// Remove the event listener.
this.worker_.removeEventListener(
goog.gears.Worker.EventType.COMMAND, this.onCommand_, false, this);
this.worker_ = null;
};
@@ -0,0 +1,478 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// 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 This file implements a store for goog.debug.Logger data.
*/
goog.provide('goog.gears.LogStore');
goog.provide('goog.gears.LogStore.Query');
goog.require('goog.async.Delay');
goog.require('goog.debug.LogManager');
goog.require('goog.gears.BaseStore');
goog.require('goog.gears.BaseStore.SchemaType');
goog.require('goog.json');
goog.require('goog.log');
goog.require('goog.log.Level');
goog.require('goog.log.LogRecord');
/**
* Implements a store for goog.debug.Logger data.
* @param {goog.gears.Database} database Database.
* @param {?string=} opt_tableName Name of logging table to use.
* @extends {goog.gears.BaseStore}
* @constructor
*/
goog.gears.LogStore = function(database, opt_tableName) {
goog.gears.BaseStore.call(this, database);
/**
* Name of log table.
* @type {string}
*/
var tableName = opt_tableName || goog.gears.LogStore.DEFAULT_TABLE_NAME_;
this.tableName_ = tableName;
// Override BaseStore schema attribute.
this.schema = [
{
type: goog.gears.BaseStore.SchemaType.TABLE,
name: tableName,
columns: [
// Unique ID.
'id INTEGER PRIMARY KEY AUTOINCREMENT',
// Timestamp.
'millis BIGINT',
// #goog.log.Level value.
'level INTEGER',
// Message.
'msg TEXT',
// Name of logger object.
'logger TEXT',
// Serialized error object.
'exception TEXT',
// Full exception text.
'exceptionText TEXT'
]
},
{
type: goog.gears.BaseStore.SchemaType.INDEX,
name: tableName + 'MillisIndex',
isUnique: false,
tableName: tableName,
columns: ['millis']
},
{
type: goog.gears.BaseStore.SchemaType.INDEX,
name: tableName + 'LevelIndex',
isUnique: false,
tableName: tableName,
columns: ['level']
}
];
/**
* Buffered log records not yet flushed to DB.
* @type {Array.<goog.log.LogRecord>}
* @private
*/
this.records_ = [];
/**
* Save the publish handler so it can be removed.
* @type {Function}
* @private
*/
this.publishHandler_ = goog.bind(this.addLogRecord, this);
};
goog.inherits(goog.gears.LogStore, goog.gears.BaseStore);
/** @override */
goog.gears.LogStore.prototype.version = 1;
/**
* Whether we are currently capturing logger output.
* @type {boolean}
* @private
*/
goog.gears.LogStore.prototype.isCapturing_ = false;
/**
* Size of buffered log data messages.
* @type {number}
* @private
*/
goog.gears.LogStore.prototype.bufferSize_ = 0;
/**
* Scheduler for pruning action.
* @type {goog.async.Delay?}
* @private
*/
goog.gears.LogStore.prototype.delay_ = null;
/**
* Use this to protect against recursive flushing.
* @type {boolean}
* @private
*/
goog.gears.LogStore.prototype.isFlushing_ = false;
/**
* Logger.
* @type {goog.log.Logger}
* @private
*/
goog.gears.LogStore.prototype.logger_ =
goog.log.getLogger('goog.gears.LogStore');
/**
* Default value for how many records we keep when pruning.
* @type {number}
* @private
*/
goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_ = 1000;
/**
* Default value for how often to auto-prune (10 minutes).
* @type {number}
* @private
*/
goog.gears.LogStore.DEFAULT_AUTOPRUNE_INTERVAL_MILLIS_ = 10 * 60 * 1000;
/**
* The name for the log table.
* @type {string}
* @private
*/
goog.gears.LogStore.DEFAULT_TABLE_NAME_ = 'GoogGearsDebugLogStore';
/**
* Max message bytes to buffer before flushing to database.
* @type {number}
* @private
*/
goog.gears.LogStore.MAX_BUFFER_BYTES_ = 200000;
/**
* Flush buffered log records.
*/
goog.gears.LogStore.prototype.flush = function() {
if (this.isFlushing_ || !this.getDatabaseInternal()) {
return;
}
this.isFlushing_ = true;
// Grab local copy of records so database can log during this process.
goog.log.info(this.logger_, 'flushing ' + this.records_.length + ' records');
var records = this.records_;
this.records_ = [];
for (var i = 0; i < records.length; i++) {
var record = records[i];
var exception = record.getException();
var serializedException = exception ? goog.json.serialize(exception) : '';
var statement = 'INSERT INTO ' + this.tableName_ +
' (millis, level, msg, logger, exception, exceptionText)' +
' VALUES (?, ?, ?, ?, ?, ?)';
this.getDatabaseInternal().execute(statement,
record.getMillis(), record.getLevel().value, record.getMessage(),
record.getLoggerName(), serializedException,
record.getExceptionText() || '');
}
this.isFlushing_ = false;
};
/**
* Create new delay object for auto-pruning. Does not stop or
* start auto-pruning, call #startAutoPrune and #startAutoPrune for that.
* @param {?number=} opt_count Number of records of recent hitory to keep.
* @param {?number=} opt_interval Milliseconds to wait before next pruning.
*/
goog.gears.LogStore.prototype.createAutoPruneDelay = function(
opt_count, opt_interval) {
if (this.delay_) {
this.delay_.dispose();
this.delay_ = null;
}
var interval = typeof opt_interval == 'number' ?
opt_interval : goog.gears.LogStore.DEFAULT_AUTOPRUNE_INTERVAL_MILLIS_;
var listener = goog.bind(this.autoPrune_, this, opt_count);
this.delay_ = new goog.async.Delay(listener, interval);
};
/**
* Enable periodic pruning. As a side effect, this also flushes the memory
* buffer.
*/
goog.gears.LogStore.prototype.startAutoPrune = function() {
if (!this.delay_) {
this.createAutoPruneDelay(
goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_,
goog.gears.LogStore.DEFAULT_AUTOPRUNE_INTERVAL_MILLIS_);
}
this.delay_.fire();
};
/**
* Disable scheduled pruning.
*/
goog.gears.LogStore.prototype.stopAutoPrune = function() {
if (this.delay_) {
this.delay_.stop();
}
};
/**
* @return {boolean} True iff auto prune timer is active.
*/
goog.gears.LogStore.prototype.isAutoPruneActive = function() {
return !!this.delay_ && this.delay_.isActive();
};
/**
* Prune, and schedule next pruning.
* @param {?number=} opt_count Number of records of recent hitory to keep.
* @private
*/
goog.gears.LogStore.prototype.autoPrune_ = function(opt_count) {
this.pruneBeforeCount(opt_count);
this.delay_.start();
};
/**
* Keep some number of most recent log records and delete all older ones.
* @param {?number=} opt_count Number of records of recent history to keep. If
* unspecified, we use #goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_.
* Pass in 0 to delete all log records.
*/
goog.gears.LogStore.prototype.pruneBeforeCount = function(opt_count) {
if (!this.getDatabaseInternal()) {
return;
}
var count = typeof opt_count == 'number' ?
opt_count : goog.gears.LogStore.DEFAULT_PRUNE_KEEPER_COUNT_;
goog.log.info(this.logger_, 'pruning before ' + count + ' records ago');
this.flush();
this.getDatabaseInternal().execute('DELETE FROM ' + this.tableName_ +
' WHERE id <= ((SELECT MAX(id) FROM ' + this.tableName_ + ') - ?)',
count);
};
/**
* Delete log record #id and all older records.
* @param {number} sequenceNumber ID before which we delete all records.
*/
goog.gears.LogStore.prototype.pruneBeforeSequenceNumber =
function(sequenceNumber) {
if (!this.getDatabaseInternal()) {
return;
}
goog.log.info(this.logger_,
'pruning before sequence number ' + sequenceNumber);
this.flush();
this.getDatabaseInternal().execute(
'DELETE FROM ' + this.tableName_ + ' WHERE id <= ?',
sequenceNumber);
};
/**
* Whether we are currently capturing logger output.
* @return {boolean} Whether we are currently capturing logger output.
*/
goog.gears.LogStore.prototype.isCapturing = function() {
return this.isCapturing_;
};
/**
* Sets whether we are currently capturing logger output.
* @param {boolean} capturing Whether to capture logger output.
*/
goog.gears.LogStore.prototype.setCapturing = function(capturing) {
if (capturing != this.isCapturing_) {
this.isCapturing_ = capturing;
// Attach or detach handler from the root logger.
var rootLogger = goog.debug.LogManager.getRoot();
if (capturing) {
goog.log.addHandler(rootLogger, this.publishHandler_);
goog.log.info(this.logger_, 'enabled');
} else {
goog.log.info(this.logger_, 'disabling');
goog.log.removeHandler(rootLogger, this.publishHandler_);
}
}
};
/**
* Adds a log record.
* @param {goog.log.LogRecord} logRecord the LogRecord.
*/
goog.gears.LogStore.prototype.addLogRecord = function(logRecord) {
this.records_.push(logRecord);
this.bufferSize_ += logRecord.getMessage().length;
var exceptionText = logRecord.getExceptionText();
if (exceptionText) {
this.bufferSize_ += exceptionText.length;
}
if (this.bufferSize_ >= goog.gears.LogStore.MAX_BUFFER_BYTES_) {
this.flush();
}
};
/**
* Select log records.
* @param {goog.gears.LogStore.Query} query Query object.
* @return {Array.<goog.log.LogRecord>} Selected logs in descending
* order of creation time.
*/
goog.gears.LogStore.prototype.select = function(query) {
if (!this.getDatabaseInternal()) {
// This should only occur if we've been disposed.
return [];
}
this.flush();
// TODO(user) Perhaps have Query object build this SQL string so we can
// omit unneeded WHERE clauses.
var statement =
'SELECT id, millis, level, msg, logger, exception, exceptionText' +
' FROM ' + this.tableName_ +
' WHERE level >= ? AND millis >= ? AND millis <= ?' +
' AND msg like ? and logger like ?' +
' ORDER BY id DESC LIMIT ?';
var rows = this.getDatabaseInternal().queryObjectArray(statement,
query.level.value, query.minMillis, query.maxMillis,
query.msgLike, query.loggerLike, query.limit);
var result = Array(rows.length);
for (var i = rows.length - 1; i >= 0; i--) {
var row = rows[i];
// Parse fields, allowing for invalid values.
var sequenceNumber = Number(row['id']) || 0;
var level = goog.log.Level.getPredefinedLevelByValue(
Number(row['level']) || 0);
var msg = row['msg'] || '';
var loggerName = row['logger'] || '';
var millis = Number(row['millis']) || 0;
var serializedException = row['exception'];
var exception = serializedException ?
goog.json.parse(serializedException) : null;
var exceptionText = row['exceptionText'] || '';
// Create record.
var record = new goog.log.LogRecord(level, msg, loggerName,
millis, sequenceNumber);
if (exception) {
record.setException(exception);
record.setExceptionText(exceptionText);
}
result[i] = record;
}
return result;
};
/** @override */
goog.gears.LogStore.prototype.disposeInternal = function() {
this.flush();
goog.gears.LogStore.superClass_.disposeInternal.call(this);
if (this.delay_) {
this.delay_.dispose();
this.delay_ = null;
}
};
/**
* Query to select log records.
* @constructor
*/
goog.gears.LogStore.Query = function() {
};
/**
* Minimum logging level.
* @type {goog.log.Level}
*/
goog.gears.LogStore.Query.prototype.level = goog.log.Level.ALL;
/**
* Minimum timestamp, inclusive.
* @type {number}
*/
goog.gears.LogStore.Query.prototype.minMillis = -1;
/**
* Maximum timestamp, inclusive.
* @type {number}
*/
goog.gears.LogStore.Query.prototype.maxMillis = Infinity;
/**
* Message 'like' pattern.
* See http://www.sqlite.org/lang_expr.html#likeFunc for 'like' syntax.
* @type {string}
*/
goog.gears.LogStore.Query.prototype.msgLike = '%';
/**
* Logger name 'like' pattern.
* See http://www.sqlite.org/lang_expr.html#likeFunc for 'like' syntax.
* @type {string}
*/
goog.gears.LogStore.Query.prototype.loggerLike = '%';
/**
* Max # recent records to return. -1 means no limit.
* @type {number}
*/
goog.gears.LogStore.Query.prototype.limit = -1;
@@ -0,0 +1,555 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 Simple wrapper around a Gears ManagedResourceStore.
*
*/
goog.provide('goog.gears.ManagedResourceStore');
goog.provide('goog.gears.ManagedResourceStore.EventType');
goog.provide('goog.gears.ManagedResourceStore.UpdateStatus');
goog.provide('goog.gears.ManagedResourceStoreEvent');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.require('goog.gears');
goog.require('goog.log');
goog.require('goog.string');
/**
* Creates a ManagedResourceStore with the specified name and update. This
* follows the Closure event model so the COMPLETE event will fire both for
* SUCCESS and for ERROR. You can use {@code isSuccess} in UPDATE to see if the
* capture was successful or you can just listen to the different events.
*
* This supports PROGRESS events, which are fired any time {@code filesComplete}
* or {@code filesTotal} changes. If the Gears version is 0.3.6 or newer this
* will reflect the numbers returned by the underlying Gears MRS but for older
* Gears versions this will just be {@code 0} or {@code 1}.
*
* NOTE: This relies on at least the 0.2 version of gears (for timer).
*
* @param {string} name The name of the managed store.
* @param {?string} requiredCookie A cookie that must be present for the
* managed store to be active. Should have the form "foo=bar". Can be null
* if not required.
* @param {GearsLocalServer=} opt_localServer Gears local server -- if not set,
* create a new one internally.
*
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.gears.ManagedResourceStore = function(name, requiredCookie,
opt_localServer) {
goog.base(this);
this.localServer_ = opt_localServer ||
goog.gears.getFactory().create('beta.localserver', '1.0');
this.name_ = goog.gears.makeSafeFileName(name);
if (name != this.name_) {
goog.log.info(this.logger_,
'managed resource store name ' + name + '->' + this.name_);
}
this.requiredCookie_ = requiredCookie ? String(requiredCookie) : null;
// Whether Gears natively has "events" on the MRS. If it does not we treat
// the progress as 0 to 1
this.supportsEvents_ = goog.string.compareVersions(
goog.gears.getFactory().version, '0.3.6') >= 0;
};
goog.inherits(goog.gears.ManagedResourceStore, goog.events.EventTarget);
/**
* The amount of time between status checks during an update
* @type {number}
*/
goog.gears.ManagedResourceStore.UPDATE_INTERVAL_MS = 500;
/**
* Enum for possible values of Gears ManagedResourceStore.updatedStatus
* @enum
*/
goog.gears.ManagedResourceStore.UpdateStatus = {
OK: 0,
CHECKING: 1,
DOWNLOADING: 2,
FAILURE: 3
};
/**
* Logger.
* @type {goog.log.Logger}
* @private
*/
goog.gears.ManagedResourceStore.prototype.logger_ =
goog.log.getLogger('goog.gears.ManagedResourceStore');
/**
* The Gears local server object.
* @type {GearsLocalServer}
* @private
*/
goog.gears.ManagedResourceStore.prototype.localServer_;
/**
* The name of the managed store.
* @type {?string}
* @private
*/
goog.gears.ManagedResourceStore.prototype.name_;
/**
* A cookie that must be present for the managed store to be active.
* Should have the form "foo=bar". String cast is a safety measure since
* Gears behaves very badly when it gets an unexpected data type.
* @type {?string}
* @private
*/
goog.gears.ManagedResourceStore.prototype.requiredCookie_;
/**
* The required cookie, if any, for the managed store.
* @type {boolean}
* @private
*/
goog.gears.ManagedResourceStore.prototype.supportsEvents_;
/**
* The Gears ManagedResourceStore instance we are wrapping.
* @type {GearsManagedResourceStore}
* @private
*/
goog.gears.ManagedResourceStore.prototype.gearsStore_;
/**
* The id of the check status timer.
* @type {?number}
* @private
*/
goog.gears.ManagedResourceStore.prototype.timerId_ = null;
/**
* The check status timer.
* @type {Object}
* @private
*/
goog.gears.ManagedResourceStore.prototype.timer_ = null;
/**
* Whether we already have an active update check.
* @type {boolean}
* @private
*/
goog.gears.ManagedResourceStore.prototype.active_ = false;
/**
* Number of files completed. This is 0 or 1 if the Gears version does not
* support progress events. If the Gears version supports progress events
* this will reflect the number of files that have been completed.
* @type {number}
* @private
*/
goog.gears.ManagedResourceStore.prototype.filesComplete_ = 0;
/**
* Number of total files to load. This is 1 if the Gears version does not
* support progress events. If the Gears version supports progress events
* this will reflect the number of files that needs to be loaded.
* @type {number}
* @private
*/
goog.gears.ManagedResourceStore.prototype.filesTotal_ = 0;
/**
* @return {boolean} Whether there is an active request.
*/
goog.gears.ManagedResourceStore.prototype.isActive = function() {
return this.active_;
};
/**
* @return {boolean} Whether the update has completed.
*/
goog.gears.ManagedResourceStore.prototype.isComplete = function() {
return this.filesComplete_ == this.filesTotal_;
};
/**
* @return {boolean} Whether the update completed with a success.
*/
goog.gears.ManagedResourceStore.prototype.isSuccess = function() {
return this.getStatus() == goog.gears.ManagedResourceStore.UpdateStatus.OK;
};
/**
* Number of total files to load. This is always 1 if the Gears version does
* not support progress events. If the Gears version supports progress events
* this will reflect the number of files that needs to be loaded.
* @return {number} The number of files to load.
*/
goog.gears.ManagedResourceStore.prototype.getFilesTotal = function() {
return this.filesTotal_;
};
/**
* Get the last error message.
* @return {string} Last error message.
*/
goog.gears.ManagedResourceStore.prototype.getLastError = function() {
return this.gearsStore_ ? this.gearsStore_.lastErrorMessage : '';
};
/**
* Number of files completed. This is 0 or 1 if the Gears version does not
* support progress events. If the Gears version supports progress events
* this will reflect the number of files that have been completed.
* @return {number} The number of completed files.
*/
goog.gears.ManagedResourceStore.prototype.getFilesComplete = function() {
return this.filesComplete_;
};
/**
* Sets the filesComplete and the filesTotal and dispathces an event when
* either changes.
* @param {number} complete The count of the downloaded files.
* @param {number} total The total number of files.
* @private
*/
goog.gears.ManagedResourceStore.prototype.setFilesCounts_ = function(complete,
total) {
if (this.filesComplete_ != complete || this.filesTotal_ != total) {
this.filesComplete_ = complete;
this.filesTotal_ = total;
this.dispatchEvent(goog.gears.ManagedResourceStore.EventType.PROGRESS);
}
};
/**
* Determine if the ManagedResourceStore has been created in Gears yet
* @return {boolean} true if it has been created.
*/
goog.gears.ManagedResourceStore.prototype.exists = function() {
if (!this.gearsStore_) {
this.gearsStore_ = this.localServer_.openManagedStore(
this.name_, this.requiredCookie_);
}
return !!this.gearsStore_;
};
/**
* Throws an error if the store has not yet been created via create().
* @private
*/
goog.gears.ManagedResourceStore.prototype.assertExists_ = function() {
if (!this.exists()) {
throw Error('Store not yet created');
}
};
/**
* Throws an error if the store has already been created via create().
* @private
*/
goog.gears.ManagedResourceStore.prototype.assertNotExists_ = function() {
if (this.exists()) {
throw Error('Store already created');
}
};
/**
* Create the ManagedResourceStore in gears
* @param {string=} opt_manifestUrl The url of the manifest to associate.
*/
goog.gears.ManagedResourceStore.prototype.create = function(opt_manifestUrl) {
if (!this.exists()) {
this.gearsStore_ = this.localServer_.createManagedStore(
this.name_, this.requiredCookie_);
this.assertExists_();
}
if (opt_manifestUrl) {
// String cast is a safety measure since Gears behaves very badly if it
// gets an unexpected data type (e.g., goog.Uri).
this.gearsStore_.manifestUrl = String(opt_manifestUrl);
}
};
/**
* Starts an asynchronous process to update the ManagedResourcStore
*/
goog.gears.ManagedResourceStore.prototype.update = function() {
if (this.active_) {
// Update already in progress.
return;
}
this.assertExists_();
if (this.supportsEvents_) {
this.gearsStore_.onprogress = goog.bind(this.handleProgress_, this);
this.gearsStore_.oncomplete = goog.bind(this.handleComplete_, this);
this.gearsStore_.onerror = goog.bind(this.handleError_, this);
} else {
this.timer_ = goog.gears.getFactory().create('beta.timer', '1.0');
this.timerId_ = this.timer_.setInterval(
goog.bind(this.checkUpdateStatus_, this),
goog.gears.ManagedResourceStore.UPDATE_INTERVAL_MS);
this.setFilesCounts_(0, 1);
}
this.gearsStore_.checkForUpdate();
this.active_ = true;
};
/**
* @return {string} Store's current manifest URL.
*/
goog.gears.ManagedResourceStore.prototype.getManifestUrl = function() {
this.assertExists_();
return this.gearsStore_.manifestUrl;
};
/**
* @param {string} url Store's new manifest URL.
*/
goog.gears.ManagedResourceStore.prototype.setManifestUrl = function(url) {
this.assertExists_();
// Safety measure since Gears behaves very badly if it gets an unexpected
// data type (e.g., goog.Uri).
this.gearsStore_.manifestUrl = String(url);
};
/**
* @return {?string} The version of the managed store that is currently being
* served.
*/
goog.gears.ManagedResourceStore.prototype.getVersion = function() {
return this.exists() ? this.gearsStore_.currentVersion : null;
};
/**
* @return {goog.gears.ManagedResourceStore.UpdateStatus} The current update
* status.
*/
goog.gears.ManagedResourceStore.prototype.getStatus = function() {
this.assertExists_();
return /** @type {goog.gears.ManagedResourceStore.UpdateStatus} */ (
this.gearsStore_.updateStatus);
};
/**
* @return {boolean} Whether the store is currently enabled to serve local
* content.
*/
goog.gears.ManagedResourceStore.prototype.isEnabled = function() {
this.assertExists_();
return this.gearsStore_.enabled;
};
/**
* Sets whether the store is currently enabled to serve local content.
* @param {boolean} isEnabled True if the store is enabled and false otherwise.
*/
goog.gears.ManagedResourceStore.prototype.setEnabled = function(isEnabled) {
this.assertExists_();
// !! is a safety measure since Gears behaves very badly if it gets an
// unexpected data type.
this.gearsStore_.enabled = !!isEnabled;
};
/**
* Remove managed store.
*/
goog.gears.ManagedResourceStore.prototype.remove = function() {
this.assertExists_();
this.localServer_.removeManagedStore(this.name_, this.requiredCookie_);
this.gearsStore_ = null;
this.assertNotExists_();
};
/**
* Called periodically as the update proceeds. If it has completed, fire an
* approproiate event and cancel further checks.
* @private
*/
goog.gears.ManagedResourceStore.prototype.checkUpdateStatus_ = function() {
var e;
if (this.gearsStore_.updateStatus ==
goog.gears.ManagedResourceStore.UpdateStatus.FAILURE) {
e = new goog.gears.ManagedResourceStoreEvent(
goog.gears.ManagedResourceStore.EventType.ERROR,
this.gearsStore_.lastErrorMessage);
this.setFilesCounts_(0, 1);
} else if (this.gearsStore_.updateStatus ==
goog.gears.ManagedResourceStore.UpdateStatus.OK) {
e = new goog.gears.ManagedResourceStoreEvent(
goog.gears.ManagedResourceStore.EventType.SUCCESS);
this.setFilesCounts_(1, 1);
}
if (e) {
this.cancelStatusCheck_();
this.dispatchEvent(e);
// Fire complete after both error and success
this.dispatchEvent(goog.gears.ManagedResourceStore.EventType.COMPLETE);
this.active_ = false;
}
};
/**
* Cancel periodic status checks.
* @private
*/
goog.gears.ManagedResourceStore.prototype.cancelStatusCheck_ = function() {
if (!this.supportsEvents_ && this.timerId_ != null) {
this.timer_.clearInterval(this.timerId_);
this.timerId_ = null;
this.timer_ = null;
}
};
/**
* Callback for when the Gears managed resource store fires a progress event.
* @param {Object} details An object containg two fields, {@code filesComplete}
* and {@code filesTotal}.
* @private
*/
goog.gears.ManagedResourceStore.prototype.handleProgress_ = function(details) {
// setFilesCounts_ will dispatch the progress event as needed
this.setFilesCounts_(details['filesComplete'], details['filesTotal']);
};
/**
* Callback for when the Gears managed resource store fires a complete event.
* @param {Object} details An object containg one field called
* {@code newVersion}.
* @private
*/
goog.gears.ManagedResourceStore.prototype.handleComplete_ = function(details) {
this.dispatchEvent(goog.gears.ManagedResourceStore.EventType.SUCCESS);
this.dispatchEvent(goog.gears.ManagedResourceStore.EventType.COMPLETE);
this.active_ = false;
};
/**
* Callback for when the Gears managed resource store fires an error event.
* @param {Object} error An object containg one field called
* {@code message}.
* @private
*/
goog.gears.ManagedResourceStore.prototype.handleError_ = function(error) {
this.dispatchEvent(new goog.gears.ManagedResourceStoreEvent(
goog.gears.ManagedResourceStore.EventType.ERROR, error.message));
this.dispatchEvent(goog.gears.ManagedResourceStore.EventType.COMPLETE);
this.active_ = false;
};
/** @override */
goog.gears.ManagedResourceStore.prototype.disposeInternal = function() {
goog.gears.ManagedResourceStore.superClass_.disposeInternal.call(this);
if (this.supportsEvents_ && this.gearsStore_) {
this.gearsStore_.onprogress = null;
this.gearsStore_.oncomplete = null;
this.gearsStore_.onerror = null;
}
this.cancelStatusCheck_();
this.localServer_ = null;
this.gearsStore_ = null;
};
/**
* Enum for event types fired by ManagedResourceStore.
* @enum {string}
*/
goog.gears.ManagedResourceStore.EventType = {
COMPLETE: 'complete',
ERROR: 'error',
PROGRESS: 'progress',
SUCCESS: 'success'
};
/**
* Event used when a ManagedResourceStore update is complete
* @param {string} type The type of the event.
* @param {string=} opt_errorMessage The error message if failure.
* @constructor
* @extends {goog.events.Event}
*/
goog.gears.ManagedResourceStoreEvent = function(type, opt_errorMessage) {
goog.events.Event.call(this, type);
if (opt_errorMessage) {
this.errorMessage = opt_errorMessage;
}
};
goog.inherits(goog.gears.ManagedResourceStoreEvent, goog.events.Event);
/**
* Error message in the case of a failure event.
* @type {?string}
*/
goog.gears.ManagedResourceStoreEvent.prototype.errorMessage = null;
@@ -0,0 +1,204 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// 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 This class provides a builder for building multipart form data
* that is to be usef with Gears BlobBuilder and GearsHttpRequest.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.gears.MultipartFormData');
goog.require('goog.asserts');
goog.require('goog.gears');
goog.require('goog.string');
/**
* Creates a new multipart form data builder.
* @constructor
*/
goog.gears.MultipartFormData = function() {
/**
* The blob builder used to build the blob.
* @type {GearsBlobBuilder}
* @private
*/
this.blobBuilder_ = goog.gears.getFactory().create('beta.blobbuilder');
/**
* The boundary. This should be something that does not occurr in the values.
* @type {string}
* @private
*/
this.boundary_ = '----' + goog.string.getRandomString();
};
/**
* Constant for a carriage return followed by a new line.
* @type {string}
* @private
*/
goog.gears.MultipartFormData.CRLF_ = '\r\n';
/**
* Constant containing two dashes.
* @type {string}
* @private
*/
goog.gears.MultipartFormData.DASHES_ = '--';
/**
* Whether the builder has been closed.
* @type {boolean}
* @private
*/
goog.gears.MultipartFormData.prototype.closed_;
/**
* Whether the builder has any content.
* @type {boolean}
* @private
*/
goog.gears.MultipartFormData.prototype.hasContent_;
/**
* Adds a Gears file to the multipart.
* @param {string} name The name of the value.
* @param {GearsFile} gearsFile The Gears file as returned from openFiles etc.
* @return {goog.gears.MultipartFormData} The form builder itself.
*/
goog.gears.MultipartFormData.prototype.addFile = function(name, gearsFile) {
return this.addBlob(name, gearsFile.name, gearsFile.blob);
};
/**
* Adds some text to the multipart.
* @param {string} name The name of the value.
* @param {*} value The value. This will use toString on the value.
* @return {goog.gears.MultipartFormData} The form builder itself.
*/
goog.gears.MultipartFormData.prototype.addText = function(name, value) {
this.assertNotClosed_();
// Also assert that the value does not contain the boundary.
this.assertNoBoundary_(value);
this.hasContent_ = true;
this.blobBuilder_.append(
goog.gears.MultipartFormData.DASHES_ + this.boundary_ +
goog.gears.MultipartFormData.CRLF_ +
'Content-Disposition: form-data; name="' + name + '"' +
goog.gears.MultipartFormData.CRLF_ +
// The BlobBuilder uses UTF-8 so ensure that we use that at all times.
'Content-Type: text/plain; charset=UTF-8' +
goog.gears.MultipartFormData.CRLF_ +
goog.gears.MultipartFormData.CRLF_ +
value +
goog.gears.MultipartFormData.CRLF_);
return this;
};
/**
* Adds a Gears blob as a file to the multipart.
* @param {string} name The name of the value.
* @param {string} fileName The name of the file.
* @param {GearsBlob} blob The blob to add.
* @return {goog.gears.MultipartFormData} The form builder itself.
*/
goog.gears.MultipartFormData.prototype.addBlob = function(name, fileName,
blob) {
this.assertNotClosed_();
this.hasContent_ = true;
this.blobBuilder_.append(
goog.gears.MultipartFormData.DASHES_ + this.boundary_ +
goog.gears.MultipartFormData.CRLF_ +
'Content-Disposition: form-data; name="' + name + '"' +
'; filename="' + fileName + '"' +
goog.gears.MultipartFormData.CRLF_ +
'Content-Type: application/octet-stream' +
goog.gears.MultipartFormData.CRLF_ +
goog.gears.MultipartFormData.CRLF_);
this.blobBuilder_.append(blob);
this.blobBuilder_.append(goog.gears.MultipartFormData.CRLF_);
return this;
};
/**
* The content type to set on the GearsHttpRequest.
*
* var builder = new MultipartFormData;
* ...
* ghr.setRequestHeader('Content-Type', builder.getContentType());
* ghr.send(builder.getAsBlob());
*
* @return {string} The content type string to be used when posting this with
* a GearsHttpRequest.
*/
goog.gears.MultipartFormData.prototype.getContentType = function() {
return 'multipart/form-data; boundary=' + this.boundary_;
};
/**
* @return {GearsBlob} The blob to use in the send method of the
* GearsHttpRequest.
*/
goog.gears.MultipartFormData.prototype.getAsBlob = function() {
if (!this.closed_ && this.hasContent_) {
this.blobBuilder_.append(
goog.gears.MultipartFormData.DASHES_ +
this.boundary_ +
goog.gears.MultipartFormData.DASHES_ +
goog.gears.MultipartFormData.CRLF_);
this.closed_ = true;
}
return this.blobBuilder_.getAsBlob();
};
/**
* Asserts that we do not try to add any more data to a closed multipart form
* builder.
* @throws {Error} If the multipart form data has already been closed.
* @private
*/
goog.gears.MultipartFormData.prototype.assertNotClosed_ = function() {
goog.asserts.assert(!this.closed_, 'The multipart form builder has been ' +
'closed and no more data can be added to it');
};
/**
* Asserts that the value does not contain the boundary.
* @param {*} v The value to ensure that the string representation does not
* contain the boundary token.
* @throws {Error} If the value contains the boundary.
* @private
*/
goog.gears.MultipartFormData.prototype.assertNoBoundary_ = function(v) {
goog.asserts.assert(String(v).indexOf(this.boundary_) == -1,
'The value cannot contain the boundary');
};
@@ -0,0 +1,37 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 An enum that contains the possible status type's of the Gears
* feature of an application.
*
*/
goog.provide('goog.gears.StatusType');
/**
* The possible status type's for Gears.
* @enum {string}
*/
goog.gears.StatusType = {
NOT_INSTALLED: 'ni',
INSTALLED: 'i',
PAUSED: 'p',
OFFLINE: 'off',
ONLINE: 'on',
SYNCING: 's',
CAPTURING: 'c',
ERROR: 'e'
};
@@ -0,0 +1,370 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 Interface for capturing URLs to a ResourceStore on the
* LocalServer.
*
*/
goog.provide('goog.gears.UrlCapture');
goog.provide('goog.gears.UrlCapture.Event');
goog.provide('goog.gears.UrlCapture.EventType');
goog.require('goog.Uri');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.require('goog.gears');
goog.require('goog.log');
/**
* Class capture URLs to a ResourceStore on the LocalServer.
* @constructor
* @extends {goog.events.EventTarget}
* @param {string} name The name of the ResourceStore to capture the URLs to.
* @param {?string} requiredCookie A cookie that must be present for the
* managed store to be active. Should have the form "foo=bar".
* @param {GearsResourceStore=} opt_localServer The LocalServer for gears.
*/
goog.gears.UrlCapture = function(name, requiredCookie, opt_localServer) {
goog.events.EventTarget.call(this);
/**
* Name of resource store.
* @type {string}
* @private
*/
this.storeName_ = goog.gears.makeSafeFileName(name);
if (name != this.storeName_) {
goog.log.info(this.logger_,
'local store name ' + name + '->' + this.storeName_);
}
/**
* A cookie that must be present for the store to be active.
* Should have the form "foo=bar". String cast is a safety measure since
* Gears behaves very badly when it gets an unexpected data type.
* @type {?string}
* @private
*/
this.requiredCookie_ = requiredCookie ? String(requiredCookie) : null;
/**
* The LocalServer for Gears.
* @type {GearsLocalServer}
* @private
*/
this.localServer_ = opt_localServer ||
goog.gears.getFactory().create('beta.localserver', '1.0');
/**
* Object mapping list of URIs to capture to capture id.
* @type {Object}
* @private
*/
this.uris_ = {};
/**
* Object mapping list of URIs that had errors in the capture to capture id.
* @type {Object}
* @private
*/
this.errorUris_ = {};
/**
* Object mapping number of URLs completed to capture id.
* @type {Object}
* @private
*/
this.numCompleted_ = {};
};
goog.inherits(goog.gears.UrlCapture, goog.events.EventTarget);
/**
* Logger.
* @type {goog.log.Logger}
* @private
*/
goog.gears.UrlCapture.prototype.logger_ =
goog.log.getLogger('goog.gears.UrlCapture');
/**
* The ResourceStore for gears, used to capture URLs.
* @type {GearsResourceStore}
* @private
*/
goog.gears.UrlCapture.prototype.resourceStore_ = null;
/**
* Events fired during URL capture
* @enum {string}
*/
goog.gears.UrlCapture.EventType = {
URL_SUCCESS: 'url_success',
URL_ERROR: 'url_error',
COMPLETE: 'complete',
ABORT: 'abort'
};
/**
* Lazy initializer for resource store.
* @return {GearsResourceStore} Gears resource store.
* @private
*/
goog.gears.UrlCapture.prototype.getResourceStore_ = function() {
if (!this.resourceStore_) {
goog.log.info(this.logger_, 'creating resource store: ' + this.storeName_);
this.resourceStore_ = this.localServer_['createStore'](
this.storeName_, this.requiredCookie_);
}
return this.resourceStore_;
};
/**
* Determine if the UrlCapture has been created.
* @return {boolean} True if it has been created.
*/
goog.gears.UrlCapture.prototype.exists = function() {
if (!this.resourceStore_) {
goog.log.info(this.logger_, 'opening resource store: ' + this.storeName_);
this.resourceStore_ = this.localServer_['openStore'](
this.storeName_, this.requiredCookie_);
}
return !!this.resourceStore_;
};
/**
* Remove this resource store.
*/
goog.gears.UrlCapture.prototype.removeStore = function() {
goog.log.info(this.logger_, 'removing resource store: ' + this.storeName_);
this.localServer_['removeStore'](this.storeName_, this.requiredCookie_);
this.resourceStore_ = null;
};
/**
* Renames a Url that's been captured.
* @param {string|goog.Uri} srcUri The source Uri.
* @param {string|goog.Uri} dstUri The destination Uri.
*/
goog.gears.UrlCapture.prototype.rename = function(srcUri, dstUri) {
this.getResourceStore_()['rename'](srcUri.toString(), dstUri.toString());
};
/**
* Copies a Url that's been captured.
* @param {string|goog.Uri} srcUri The source Uri.
* @param {string|goog.Uri} dstUri The destination Uri.
*/
goog.gears.UrlCapture.prototype.copy = function(srcUri, dstUri) {
this.getResourceStore_()['copy'](srcUri.toString(), dstUri.toString());
};
/**
* Starts the capture of the given URLs. Returns immediately, and fires events
* on success and error.
* @param {Array.<string|goog.Uri>} uris URIs to capture.
* @return {number} The id of the ResourceStore capture. Can be used to
* abort, or identify events.
*/
goog.gears.UrlCapture.prototype.capture = function(uris) {
var count = uris.length;
goog.log.fine(this.logger_, 'capture: count==' + count);
if (!count) {
throw Error('No URIs to capture');
}
// Convert goog.Uri objects to strings since Gears will throw an exception
// for non-strings.
var captureStrings = [];
for (var i = 0; i < count; i++) {
captureStrings.push(uris[i].toString());
}
var id = this.getResourceStore_()['capture'](
captureStrings, goog.bind(this.captureCallback_, this));
goog.log.fine(this.logger_, 'capture started: ' + id);
this.uris_[id] = uris;
this.errorUris_[id] = [];
this.numCompleted_[id] = 0;
return id;
};
/**
* Aborts the capture with the given id. Dispatches abort event.
* @param {number} captureId The id of the capture to abort, from #capture.
*/
goog.gears.UrlCapture.prototype.abort = function(captureId) {
goog.log.fine(this.logger_, 'abort: ' + captureId);
// TODO(user) Remove when Gears adds more rubust type handling.
// Safety measure since Gears behaves very badly if it gets an unexpected
// data type.
if (typeof captureId != 'number') {
throw Error('bad capture ID: ' + captureId);
}
// Only need to abort if the capture is still in progress.
if (this.uris_[captureId] || this.numCompleted_[captureId]) {
goog.log.info(this.logger_, 'aborting capture: ' + captureId);
this.getResourceStore_()['abortCapture'](captureId);
this.cleanupCapture_(captureId);
this.dispatchEvent(new goog.gears.UrlCapture.Event(
goog.gears.UrlCapture.EventType.ABORT, captureId));
}
};
/**
* Checks if a URL is captured.
* @param {string|goog.Uri} uri The URL to check.
* @return {boolean} true if captured, false otherwise.
*/
goog.gears.UrlCapture.prototype.isCaptured = function(uri) {
goog.log.fine(this.logger_, 'isCaptured: ' + uri);
return this.getResourceStore_()['isCaptured'](uri.toString());
};
/**
* Removes the given URI from the store.
* @param {string|goog.Uri} uri The URI to remove from the store.
*/
goog.gears.UrlCapture.prototype.remove = function(uri) {
goog.log.fine(this.logger_, 'remove: ' + uri);
this.getResourceStore_()['remove'](uri.toString());
};
/**
* This is the callback passed into ResourceStore.capture. It gets called
* each time a URL is captured.
* @param {string} url The url from gears, always a string.
* @param {boolean} success True if capture succeeded, false otherwise.
* @param {number} captureId The id of the capture.
* @private
*/
goog.gears.UrlCapture.prototype.captureCallback_ = function(
url, success, captureId) {
goog.log.fine(this.logger_, 'captureCallback_: ' + captureId);
if (!this.uris_[captureId] && !this.numCompleted_[captureId]) {
// This probably means we were aborted and then a capture event came in.
this.cleanupCapture_(captureId);
return;
}
// Dispatch success/error event for the URL
var eventUri = this.usesGoogUri_(captureId) ? new goog.Uri(url) : url;
var eventType = null;
if (success) {
eventType = goog.gears.UrlCapture.EventType.URL_SUCCESS;
} else {
eventType = goog.gears.UrlCapture.EventType.URL_ERROR;
this.errorUris_[captureId].push(eventUri);
}
this.dispatchEvent(new goog.gears.UrlCapture.Event(
eventType, captureId, eventUri));
// Dispatch complete event for the entire capture, if necessary
this.numCompleted_[captureId]++;
if (this.numCompleted_[captureId] == this.uris_[captureId].length) {
this.dispatchEvent(new goog.gears.UrlCapture.Event(
goog.gears.UrlCapture.EventType.COMPLETE, captureId, null,
this.errorUris_[captureId]));
this.cleanupCapture_(captureId);
}
};
/**
* Helper function to cleanup after a capture completes or is aborted.
* @private
* @param {number} captureId The id of the capture to clean up.
*/
goog.gears.UrlCapture.prototype.cleanupCapture_ = function(captureId) {
goog.log.fine(this.logger_, 'cleanupCapture_: ' + captureId);
delete this.uris_[captureId];
delete this.numCompleted_[captureId];
delete this.errorUris_[captureId];
};
/**
* Helper function to check whether a certain capture is using URIs of type
* String or type goog.Uri
* @private
* @param {number} captureId The id of the capture to check.
* @return {boolean} True if the capture uses goog.Uri, false if it uses string
* or there are no URIs associated with the capture.
*/
goog.gears.UrlCapture.prototype.usesGoogUri_ = function(captureId) {
if (this.uris_[captureId] &&
this.uris_[captureId].length > 0 &&
this.uris_[captureId][0] instanceof goog.Uri) {
return true;
}
return false;
};
/**
* An event dispatched by UrlCapture
* @constructor
* @extends {goog.events.Event}
* @param {goog.gears.UrlCapture.EventType} type Type of event to dispatch.
* @param {number} captureId The id of the capture that fired this event.
* @param {string|goog.Uri=} opt_uri The URI for the event.
* @param {Array.<string|goog.Uri>=} opt_errorUris The URIs that failed to load
* correctly.
*/
goog.gears.UrlCapture.Event = function(type, captureId, opt_uri,
opt_errorUris) {
goog.events.Event.call(this, type);
/**
* The id of the capture to dispatch the event for. This id is returned from
* goog.gears.UrlCapture#capture
* @type {number}
*/
this.captureId = captureId;
/**
* The URI the event concerns. Valid for URL_SUCCESS and URL_ERROR events.
* @type {string|goog.Uri|null}
*/
this.uri = opt_uri || null;
/**
* A list of all the URIs that failed to load correctly. Valid for
* COMPLETE event.
* @type {Array.<string|goog.Uri>}
*/
this.errorUris = opt_errorUris || [];
};
goog.inherits(goog.gears.UrlCapture.Event, goog.events.Event);
@@ -0,0 +1,200 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 This represents a Gears worker (background process).
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.gears.Worker');
goog.provide('goog.gears.Worker.EventType');
goog.provide('goog.gears.WorkerEvent');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
/**
* This is an absctraction of workers that can be used with Gears WorkerPool.
* @constructor
* @param {goog.gears.WorkerPool} workerPool WorkerPool object.
* @param {number=} opt_id The id of the worker this represents.
*
* @extends {goog.events.EventTarget}
*/
goog.gears.Worker = function(workerPool, opt_id) {
goog.events.EventTarget.call(this);
/**
* Reference to the worker pool.
* @type {goog.gears.WorkerPool}
* @private
*/
this.workerPool_ = workerPool;
if (opt_id != null) {
this.init(opt_id);
}
};
goog.inherits(goog.gears.Worker, goog.events.EventTarget);
/**
* Called when we receive a message from this worker. The message will
* first be dispatched as a WorkerEvent with type {@code EventType.MESSAGE} and
* then a {@code EventType.COMMAND}. An EventTarget may call
* {@code WorkerEvent.preventDefault()} to stop further dispatches.
* @param {GearsMessageObject} messageObject An object containing all
* information about the message.
*/
goog.gears.Worker.prototype.handleMessage = function(messageObject) {
// First dispatch a message event.
var messageEvent = new goog.gears.WorkerEvent(
goog.gears.Worker.EventType.MESSAGE,
messageObject);
// Allow the user to call prevent default to not process the COMMAND.
if (this.dispatchEvent(messageEvent)) {
if (goog.gears.Worker.isCommandLike(messageObject.body)) {
this.dispatchEvent(new goog.gears.WorkerEvent(
goog.gears.Worker.EventType.COMMAND,
messageObject));
}
}
};
/**
* The ID of the worker we are communicating with.
* @type {?number}
* @private
*/
goog.gears.Worker.prototype.id_ = null;
/**
* Initializes the worker object with a worker id.
* @param {number} id The id of the worker this represents.
*/
goog.gears.Worker.prototype.init = function(id) {
if (this.id_ != null) {
throw Error('Can only set the worker id once');
}
this.id_ = id;
this.workerPool_.registerWorker(this);
};
/**
* Sends a command to the worker.
* @param {number} commandId The ID of the command to
* send.
* @param {Object} params An object to send as the parameters. This object
* must be something that Gears can serialize. This includes JSON as well
* as Gears blobs.
*/
goog.gears.Worker.prototype.sendCommand = function(commandId, params) {
this.sendMessage([commandId, params]);
};
/**
* Sends a message to the worker.
* @param {*} message The message to send to the target worker.
*/
goog.gears.Worker.prototype.sendMessage = function(message) {
this.workerPool_.sendMessage(message, this);
};
/**
* Gets an ID that uniquely identifies this worker. The ID is unique among all
* worker from the same WorkerPool.
*
* @return {number} The ID of the worker. This might be null if the
* worker has not been initialized yet.
*/
goog.gears.Worker.prototype.getId = function() {
if (this.id_ == null) {
throw Error('The worker has not yet been initialized');
}
return this.id_;
};
/**
* Whether an object looks like a command. A command is an array with length 2
* where the first element is a number.
* @param {*} obj The object to test.
* @return {boolean} true if the object looks like a command.
*/
goog.gears.Worker.isCommandLike = function(obj) {
return goog.isArray(obj) && obj.length == 2 &&
goog.isNumber(/** @type {Array} */ (obj)[0]);
};
/** @override */
goog.gears.Worker.prototype.disposeInternal = function() {
goog.gears.Worker.superClass_.disposeInternal.call(this);
this.workerPool_.unregisterWorker(this);
this.workerPool_ = null;
};
/**
* Enum for event types fired by the worker.
* @enum {string}
*/
goog.gears.Worker.EventType = {
MESSAGE: 'message',
COMMAND: 'command'
};
/**
* Event used when the worker recieves a message
* @param {string} type The type of event.
* @param {GearsMessageObject} messageObject The message object.
*
* @constructor
* @extends {goog.events.Event}
*/
goog.gears.WorkerEvent = function(type, messageObject) {
goog.events.Event.call(this, type);
/**
* The message sent from the worker.
* @type {*}
*/
this.message = messageObject.body;
/**
* The JSON object sent from the worker.
* @type {*}
* @deprecated Use message instead.
*/
this.json = this.message;
/**
* The object containing all information about the message.
* @type {GearsMessageObject}
*/
this.messageObject = messageObject;
};
goog.inherits(goog.gears.WorkerEvent, goog.events.Event);
@@ -0,0 +1,205 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// 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 message channel between Gears workers. This is meant to work
* even when the Gears worker has other message listeners. GearsWorkerChannel
* adds a specific prefix to its messages, and handles messages with that
* prefix.
*
*/
goog.provide('goog.gears.WorkerChannel');
goog.require('goog.Disposable');
goog.require('goog.debug');
goog.require('goog.events');
goog.require('goog.gears.Worker');
goog.require('goog.gears.Worker.EventType');
goog.require('goog.gears.WorkerEvent');
goog.require('goog.json');
goog.require('goog.log');
goog.require('goog.messaging.AbstractChannel');
/**
* Creates a message channel for the given Gears worker.
*
* @param {goog.gears.Worker} worker The Gears worker to communicate with. This
* should already be initialized.
* @constructor
* @extends {goog.messaging.AbstractChannel}
*/
goog.gears.WorkerChannel = function(worker) {
goog.base(this);
/**
* The Gears worker to communicate with.
* @type {goog.gears.Worker}
* @private
*/
this.worker_ = worker;
goog.events.listen(this.worker_, goog.gears.Worker.EventType.MESSAGE,
this.deliver_, false, this);
};
goog.inherits(goog.gears.WorkerChannel, goog.messaging.AbstractChannel);
/**
* The flag added to messages that are sent by a GearsWorkerChannel, and are
* meant to be handled by one on the other side.
* @type {string}
*/
goog.gears.WorkerChannel.FLAG = '--goog.gears.WorkerChannel';
/**
* The expected origin of the other end of the worker channel, represented as a
* string of the form SCHEME://DOMAIN[:PORT]. The port may be omitted for
* standard ports (http port 80, https port 443).
*
* If this is set, all GearsWorkerChannel messages are validated to come from
* this origin, and ignored (with a warning) if they don't. Messages that aren't
* in the GearsWorkerChannel format are not validated.
*
* If more complex origin validation is required, the checkMessageOrigin method
* can be overridden.
*
* @type {?string}
*/
goog.gears.WorkerChannel.prototype.peerOrigin;
/**
* Logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.gears.WorkerChannel.prototype.logger =
goog.log.getLogger('goog.gears.WorkerChannel');
/**
* @override
*/
goog.gears.WorkerChannel.prototype.send =
function(serviceName, payload) {
var message = {'serviceName': serviceName, 'payload': payload};
message[goog.gears.WorkerChannel.FLAG] = true;
this.worker_.sendMessage(message);
};
/** @override */
goog.gears.WorkerChannel.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.worker_.dispose();
};
/**
* Delivers a message to the appropriate service handler. If this message isn't
* a GearsWorkerChannel message, it's ignored and passed on to other handlers.
*
* @param {goog.gears.WorkerEvent} e The event.
* @private
*/
goog.gears.WorkerChannel.prototype.deliver_ = function(e) {
var messageObject = e.messageObject || {};
var body = messageObject.body;
if (!goog.isObject(body) || !body[goog.gears.WorkerChannel.FLAG]) {
return;
}
if (!this.checkMessageOrigin(messageObject.origin)) {
return;
}
if (this.validateMessage_(body)) {
this.deliver(body['serviceName'], body['payload']);
}
e.preventDefault();
e.stopPropagation();
};
/**
* Checks whether the message is invalid in some way.
*
* @param {Object} body The contents of the message.
* @return {boolean} True if the message is valid, false otherwise.
* @private
*/
goog.gears.WorkerChannel.prototype.validateMessage_ = function(body) {
if (!('serviceName' in body)) {
goog.log.warning(this.logger, 'GearsWorkerChannel::deliver_(): ' +
'Message object doesn\'t contain service name: ' +
goog.debug.deepExpose(body));
return false;
}
if (!('payload' in body)) {
goog.log.warning(this.logger, 'GearsWorkerChannel::deliver_(): ' +
'Message object doesn\'t contain payload: ' +
goog.debug.deepExpose(body));
return false;
}
return true;
};
/**
* Checks whether the origin for a given message is the expected origin. If it's
* not, a warning is logged and the message is ignored.
*
* This checks that the origin matches the peerOrigin property. It can be
* overridden if more complex origin detection is necessary.
*
* @param {string} messageOrigin The origin of the message, of the form
* SCHEME://HOST[:PORT]. The port is omitted for standard ports (http port
* 80, https port 443).
* @return {boolean} True if the origin is acceptable, false otherwise.
* @protected
*/
goog.gears.WorkerChannel.prototype.checkMessageOrigin = function(
messageOrigin) {
if (!this.peerOrigin) {
return true;
}
// Gears doesn't include standard port numbers, but we want to let the user
// include them, so we'll just edit them out.
var peerOrigin = this.peerOrigin;
if (/^http:/.test(peerOrigin)) {
peerOrigin = peerOrigin.replace(/\:80$/, '');
} else if (/^https:/.test(peerOrigin)) {
peerOrigin = peerOrigin.replace(/\:443$/, '');
}
if (messageOrigin === peerOrigin) {
return true;
}
goog.log.warning(this.logger,
'Message from unexpected origin "' + messageOrigin +
'"; expected only messages from origin "' + peerOrigin +
'"');
return false;
};
@@ -0,0 +1,240 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// 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 This file implements a wrapper around the Gears WorkerPool
* with some extra features.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.gears.WorkerPool');
goog.provide('goog.gears.WorkerPool.Event');
goog.provide('goog.gears.WorkerPool.EventType');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.require('goog.gears');
goog.require('goog.gears.Worker');
/**
* This class implements a wrapper around the Gears Worker Pool.
*
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.gears.WorkerPool = function() {
goog.events.EventTarget.call(this);
/**
* Map from thread id to worker object
* @type {Object}
* @private
*/
this.workers_ = {};
// If we are in a worker thread we get the global google.gears.workerPool,
// otherwise we create a new Gears WorkerPool using the factory
var workerPool = /** @type {GearsWorkerPool} */
(goog.getObjectByName('google.gears.workerPool'));
if (workerPool) {
this.workerPool_ = workerPool;
} else {
// use a protected method to let the sub class override
this.workerPool_ = this.getGearsWorkerPool();
}
this.workerPool_.onmessage = goog.bind(this.handleMessage_, this);
};
goog.inherits(goog.gears.WorkerPool, goog.events.EventTarget);
/**
* Enum for event types fired by the WorkerPool.
* @enum {string}
*/
goog.gears.WorkerPool.EventType = {
UNKNOWN_WORKER: 'uknown_worker'
};
/**
* The Gears WorkerPool object.
* @type {GearsWorkerPool}
* @private
*/
goog.gears.WorkerPool.prototype.workerPool_ = null;
/**
* @return {GearsWorkerPool} A Gears WorkerPool object.
* @protected
*/
goog.gears.WorkerPool.prototype.getGearsWorkerPool = function() {
var factory = goog.gears.getFactory();
return factory.create('beta.workerpool');
};
/**
* Sets a last-chance error handler for a worker pool.
* WARNING: This will only succeed from inside a worker thread. In main thread,
* use window.onerror handler.
* @param {function(!GearsErrorObject):boolean} fn An error handler function
* that gets passed an error object with message and line number attributes.
* Returns whether the error was handled. If true stops propagation.
* @param {Object=} opt_handler This object for the function.
*/
goog.gears.WorkerPool.prototype.setErrorHandler = function(fn, opt_handler) {
this.workerPool_.onerror = goog.bind(fn, opt_handler);
};
/**
* Creates a new worker.
* @param {string} code The code to execute inside the worker.
* @return {goog.gears.Worker} The worker that was just created.
*/
goog.gears.WorkerPool.prototype.createWorker = function(code) {
var workerId = this.workerPool_.createWorker(code);
var worker = new goog.gears.Worker(this, workerId);
this.registerWorker(worker);
return worker;
};
/**
* Creates a new worker from a URL.
* @param {string} url URL from which to get the code to execute inside the
* worker.
* @return {goog.gears.Worker} The worker that was just created.
*/
goog.gears.WorkerPool.prototype.createWorkerFromUrl = function(url) {
var workerId = this.workerPool_.createWorkerFromUrl(url);
var worker = new goog.gears.Worker(this, workerId);
this.registerWorker(worker);
return worker;
};
/**
* Allows the worker who calls this to be used cross origin.
*/
goog.gears.WorkerPool.prototype.allowCrossOrigin = function() {
this.workerPool_.allowCrossOrigin();
};
/**
* Sends a message to a given worker.
* @param {*} message The message to send to the worker.
* @param {goog.gears.Worker} worker The worker to send the message to.
*/
goog.gears.WorkerPool.prototype.sendMessage = function(message, worker) {
this.workerPool_.sendMessage(message, worker.getId());
};
/**
* Callback when this worker recieves a message.
* @param {string} message The message that was sent.
* @param {number} senderId The ID of the worker that sent the message.
* @param {GearsMessageObject} messageObject An object containing all
* information about the message.
* @private
*/
goog.gears.WorkerPool.prototype.handleMessage_ = function(message,
senderId,
messageObject) {
if (!this.isDisposed()) {
var workers = this.workers_;
if (!workers[senderId]) {
// If the worker is unknown, dispatch an event giving users of the class
// the change to register the worker.
this.dispatchEvent(new goog.gears.WorkerPool.Event(
goog.gears.WorkerPool.EventType.UNKNOWN_WORKER,
senderId,
messageObject));
}
var worker = workers[senderId];
if (worker) {
worker.handleMessage(messageObject);
}
}
};
/**
* Registers a worker object.
* @param {goog.gears.Worker} worker The worker to register.
*/
goog.gears.WorkerPool.prototype.registerWorker = function(worker) {
this.workers_[worker.getId()] = worker;
};
/**
* Unregisters a worker object.
* @param {goog.gears.Worker} worker The worker to unregister.
*/
goog.gears.WorkerPool.prototype.unregisterWorker = function(worker) {
delete this.workers_[worker.getId()];
};
/** @override */
goog.gears.WorkerPool.prototype.disposeInternal = function() {
goog.gears.WorkerPool.superClass_.disposeInternal.call(this);
this.workerPool_ = null;
delete this.workers_;
};
/**
* Event used when the workerpool recieves a message
* @param {string} type The type of event.
* @param {number} senderId The id of the sender of the message.
* @param {GearsMessageObject} messageObject The message object.
*
* @constructor
* @extends {goog.events.Event}
*/
goog.gears.WorkerPool.Event = function(
type, senderId, messageObject) {
goog.events.Event.call(this, type);
/**
* The id of the sender of the message.
* @type {number}
*/
this.senderId = senderId;
/**
* The message sent from the worker. This is the same as the
* messageObject.body field and is here for convenience.
* @type {*}
*/
this.message = messageObject.body;
/**
* The object containing all information about the message.
* @type {GearsMessageObject}
*/
this.messageObject = messageObject;
};
goog.inherits(goog.gears.WorkerPool.Event, goog.events.Event);