// 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;