Adding float-no-zero branch hosted build
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user