Path: blob/trunk/third_party/closure/goog/labs/storage/boundedcollectablestorage.js
2868 views
// Copyright 2013 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Provides a convenient API for data persistence with data16* expiration and number of items limit.17*18* Setting and removing values keeps a max number of items invariant.19* Collecting values can be user initiated. If oversize, first removes20* expired items, if still oversize than removes the oldest items until a size21* constraint is fulfilled.22*23*/2425goog.provide('goog.labs.storage.BoundedCollectableStorage');2627goog.require('goog.array');28goog.require('goog.asserts');29goog.require('goog.iter');30goog.require('goog.storage.CollectableStorage');31goog.require('goog.storage.ErrorCode');32goog.require('goog.storage.ExpiringStorage');33343536/**37* Provides a storage with bounded number of elements, expiring keys and38* a collection method.39*40* @param {!goog.storage.mechanism.IterableMechanism} mechanism The underlying41* storage mechanism.42* @param {number} maxItems Maximum number of items in storage.43* @constructor44* @struct45* @extends {goog.storage.CollectableStorage}46* @final47*/48goog.labs.storage.BoundedCollectableStorage = function(mechanism, maxItems) {49goog.labs.storage.BoundedCollectableStorage.base(50this, 'constructor', mechanism);5152/**53* A maximum number of items that should be stored.54* @private {number}55*/56this.maxItems_ = maxItems;57};58goog.inherits(59goog.labs.storage.BoundedCollectableStorage,60goog.storage.CollectableStorage);616263/**64* An item key used to store a list of keys.65* @const66* @private67*/68goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_ =69'bounded-collectable-storage';707172/**73* Recreates a list of keys in order of creation.74*75* @return {!Array<string>} a list of unexpired keys.76* @private77*/78goog.labs.storage.BoundedCollectableStorage.prototype.rebuildIndex_ =79function() {80var keys = [];81goog.iter.forEach(82/** @type {goog.storage.mechanism.IterableMechanism} */ (this.mechanism)83.__iterator__(true),84function(key) {85if (goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_ == key) {86return;87}8889var wrapper;9091try {92wrapper = this.getWrapper(key, true);93} catch (ex) {94if (ex == goog.storage.ErrorCode.INVALID_VALUE) {95// Skip over bad wrappers and continue.96return;97}98// Unknown error, escalate.99throw ex;100}101goog.asserts.assert(wrapper);102103var creationTime =104goog.storage.ExpiringStorage.getCreationTime(wrapper);105keys.push({key: key, created: creationTime});106},107this);108109goog.array.sort(keys, function(a, b) { return a.created - b.created; });110111return goog.array.map(keys, function(v) { return v.key; });112};113114115/**116* Gets key list from a local storage. If an item does not exist,117* may recreate it.118*119* @param {boolean} rebuild Whether to rebuild a index if no index item exists.120* @return {!Array<string>} a list of keys if index exist, otherwise undefined.121* @private122*/123goog.labs.storage.BoundedCollectableStorage.prototype.getKeys_ = function(124rebuild) {125var keys =126goog.labs.storage.BoundedCollectableStorage.superClass_.get.call(127this, goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_) ||128null;129if (!keys || !goog.isArray(keys)) {130if (rebuild) {131keys = this.rebuildIndex_();132} else {133keys = [];134}135}136return /** @type {!Array<string>} */ (keys);137};138139140/**141* Saves a list of keys in a local storage.142*143* @param {Array<string>} keys a list of keys to save.144* @private145*/146goog.labs.storage.BoundedCollectableStorage.prototype.setKeys_ = function(147keys) {148goog.labs.storage.BoundedCollectableStorage.superClass_.set.call(149this, goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_, keys);150};151152153/**154* Remove subsequence from a sequence.155*156* @param {!Array<string>} keys is a sequence.157* @param {!Array<string>} keysToRemove subsequence of keys, the order must158* be kept.159* @return {!Array<string>} a keys sequence after removing keysToRemove.160* @private161*/162goog.labs.storage.BoundedCollectableStorage.removeSubsequence_ = function(163keys, keysToRemove) {164if (keysToRemove.length == 0) {165return goog.array.clone(keys);166}167var keysToKeep = [];168var keysIdx = 0;169var keysToRemoveIdx = 0;170171while (keysToRemoveIdx < keysToRemove.length && keysIdx < keys.length) {172var key = keysToRemove[keysToRemoveIdx];173while (keysIdx < keys.length && keys[keysIdx] != key) {174keysToKeep.push(keys[keysIdx]);175++keysIdx;176}177++keysToRemoveIdx;178}179180goog.asserts.assert(keysToRemoveIdx == keysToRemove.length);181goog.asserts.assert(keysIdx < keys.length);182return goog.array.concat(keysToKeep, goog.array.slice(keys, keysIdx + 1));183};184185186/**187* Keeps the number of items in storage under maxItems. Removes elements in the188* order of creation.189*190* @param {!Array<string>} keys a list of keys in order of creation.191* @param {number} maxSize a number of items to keep.192* @return {!Array<string>} keys left after removing oversize data.193* @private194*/195goog.labs.storage.BoundedCollectableStorage.prototype.collectOversize_ =196function(keys, maxSize) {197if (keys.length <= maxSize) {198return goog.array.clone(keys);199}200var keysToRemove = goog.array.slice(keys, 0, keys.length - maxSize);201goog.array.forEach(keysToRemove, function(key) {202goog.labs.storage.BoundedCollectableStorage.superClass_.remove.call(203this, key);204}, this);205return goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(206keys, keysToRemove);207};208209210/**211* Cleans up the storage by removing expired keys.212*213* @param {boolean=} opt_strict Also remove invalid keys.214* @override215*/216goog.labs.storage.BoundedCollectableStorage.prototype.collect = function(217opt_strict) {218var keys = this.getKeys_(true);219var keysToRemove = this.collectInternal(keys, opt_strict);220keys = goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(221keys, keysToRemove);222this.setKeys_(keys);223};224225226/**227* Ensures that we keep only maxItems number of items in a local storage.228* @param {boolean=} opt_skipExpired skip removing expired items first.229* @param {boolean=} opt_strict Also remove invalid keys.230*/231goog.labs.storage.BoundedCollectableStorage.prototype.collectOversize =232function(opt_skipExpired, opt_strict) {233var keys = this.getKeys_(true);234if (!opt_skipExpired) {235var keysToRemove = this.collectInternal(keys, opt_strict);236keys = goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(237keys, keysToRemove);238}239keys = this.collectOversize_(keys, this.maxItems_);240this.setKeys_(keys);241};242243244/**245* Set an item in the storage.246*247* @param {string} key The key to set.248* @param {*} value The value to serialize to a string and save.249* @param {number=} opt_expiration The number of miliseconds since epoch250* (as in goog.now()) when the value is to expire. If the expiration251* time is not provided, the value will persist as long as possible.252* @override253*/254goog.labs.storage.BoundedCollectableStorage.prototype.set = function(255key, value, opt_expiration) {256goog.labs.storage.BoundedCollectableStorage.base(257this, 'set', key, value, opt_expiration);258var keys = this.getKeys_(true);259goog.array.remove(keys, key);260261if (goog.isDef(value)) {262keys.push(key);263if (keys.length >= this.maxItems_) {264var keysToRemove = this.collectInternal(keys);265keys = goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(266keys, keysToRemove);267keys = this.collectOversize_(keys, this.maxItems_);268}269}270this.setKeys_(keys);271};272273274/**275* Remove an item from the data storage.276*277* @param {string} key The key to remove.278* @override279*/280goog.labs.storage.BoundedCollectableStorage.prototype.remove = function(key) {281goog.labs.storage.BoundedCollectableStorage.base(this, 'remove', key);282283var keys = this.getKeys_(false);284if (goog.isDef(keys)) {285goog.array.remove(keys, key);286this.setKeys_(keys);287}288};289290291