123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- // Copyright 2013 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 Provides a convenient API for data persistence with data
- * expiration and number of items limit.
- *
- * Setting and removing values keeps a max number of items invariant.
- * Collecting values can be user initiated. If oversize, first removes
- * expired items, if still oversize than removes the oldest items until a size
- * constraint is fulfilled.
- *
- */
- goog.provide('goog.labs.storage.BoundedCollectableStorage');
- goog.require('goog.array');
- goog.require('goog.asserts');
- goog.require('goog.iter');
- goog.require('goog.storage.CollectableStorage');
- goog.require('goog.storage.ErrorCode');
- goog.require('goog.storage.ExpiringStorage');
- /**
- * Provides a storage with bounded number of elements, expiring keys and
- * a collection method.
- *
- * @param {!goog.storage.mechanism.IterableMechanism} mechanism The underlying
- * storage mechanism.
- * @param {number} maxItems Maximum number of items in storage.
- * @constructor
- * @struct
- * @extends {goog.storage.CollectableStorage}
- * @final
- */
- goog.labs.storage.BoundedCollectableStorage = function(mechanism, maxItems) {
- goog.labs.storage.BoundedCollectableStorage.base(
- this, 'constructor', mechanism);
- /**
- * A maximum number of items that should be stored.
- * @private {number}
- */
- this.maxItems_ = maxItems;
- };
- goog.inherits(
- goog.labs.storage.BoundedCollectableStorage,
- goog.storage.CollectableStorage);
- /**
- * An item key used to store a list of keys.
- * @const
- * @private
- */
- goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_ =
- 'bounded-collectable-storage';
- /**
- * Recreates a list of keys in order of creation.
- *
- * @return {!Array<string>} a list of unexpired keys.
- * @private
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.rebuildIndex_ =
- function() {
- var keys = [];
- goog.iter.forEach(
- /** @type {goog.storage.mechanism.IterableMechanism} */ (this.mechanism)
- .__iterator__(true),
- function(key) {
- if (goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_ == key) {
- return;
- }
- var wrapper;
- try {
- wrapper = this.getWrapper(key, true);
- } catch (ex) {
- if (ex == goog.storage.ErrorCode.INVALID_VALUE) {
- // Skip over bad wrappers and continue.
- return;
- }
- // Unknown error, escalate.
- throw ex;
- }
- goog.asserts.assert(wrapper);
- var creationTime =
- goog.storage.ExpiringStorage.getCreationTime(wrapper);
- keys.push({key: key, created: creationTime});
- },
- this);
- goog.array.sort(keys, function(a, b) { return a.created - b.created; });
- return goog.array.map(keys, function(v) { return v.key; });
- };
- /**
- * Gets key list from a local storage. If an item does not exist,
- * may recreate it.
- *
- * @param {boolean} rebuild Whether to rebuild a index if no index item exists.
- * @return {!Array<string>} a list of keys if index exist, otherwise undefined.
- * @private
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.getKeys_ = function(
- rebuild) {
- var keys =
- goog.labs.storage.BoundedCollectableStorage.superClass_.get.call(
- this, goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_) ||
- null;
- if (!keys || !goog.isArray(keys)) {
- if (rebuild) {
- keys = this.rebuildIndex_();
- } else {
- keys = [];
- }
- }
- return /** @type {!Array<string>} */ (keys);
- };
- /**
- * Saves a list of keys in a local storage.
- *
- * @param {Array<string>} keys a list of keys to save.
- * @private
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.setKeys_ = function(
- keys) {
- goog.labs.storage.BoundedCollectableStorage.superClass_.set.call(
- this, goog.labs.storage.BoundedCollectableStorage.KEY_LIST_KEY_, keys);
- };
- /**
- * Remove subsequence from a sequence.
- *
- * @param {!Array<string>} keys is a sequence.
- * @param {!Array<string>} keysToRemove subsequence of keys, the order must
- * be kept.
- * @return {!Array<string>} a keys sequence after removing keysToRemove.
- * @private
- */
- goog.labs.storage.BoundedCollectableStorage.removeSubsequence_ = function(
- keys, keysToRemove) {
- if (keysToRemove.length == 0) {
- return goog.array.clone(keys);
- }
- var keysToKeep = [];
- var keysIdx = 0;
- var keysToRemoveIdx = 0;
- while (keysToRemoveIdx < keysToRemove.length && keysIdx < keys.length) {
- var key = keysToRemove[keysToRemoveIdx];
- while (keysIdx < keys.length && keys[keysIdx] != key) {
- keysToKeep.push(keys[keysIdx]);
- ++keysIdx;
- }
- ++keysToRemoveIdx;
- }
- goog.asserts.assert(keysToRemoveIdx == keysToRemove.length);
- goog.asserts.assert(keysIdx < keys.length);
- return goog.array.concat(keysToKeep, goog.array.slice(keys, keysIdx + 1));
- };
- /**
- * Keeps the number of items in storage under maxItems. Removes elements in the
- * order of creation.
- *
- * @param {!Array<string>} keys a list of keys in order of creation.
- * @param {number} maxSize a number of items to keep.
- * @return {!Array<string>} keys left after removing oversize data.
- * @private
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.collectOversize_ =
- function(keys, maxSize) {
- if (keys.length <= maxSize) {
- return goog.array.clone(keys);
- }
- var keysToRemove = goog.array.slice(keys, 0, keys.length - maxSize);
- goog.array.forEach(keysToRemove, function(key) {
- goog.labs.storage.BoundedCollectableStorage.superClass_.remove.call(
- this, key);
- }, this);
- return goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(
- keys, keysToRemove);
- };
- /**
- * Cleans up the storage by removing expired keys.
- *
- * @param {boolean=} opt_strict Also remove invalid keys.
- * @override
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.collect = function(
- opt_strict) {
- var keys = this.getKeys_(true);
- var keysToRemove = this.collectInternal(keys, opt_strict);
- keys = goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(
- keys, keysToRemove);
- this.setKeys_(keys);
- };
- /**
- * Ensures that we keep only maxItems number of items in a local storage.
- * @param {boolean=} opt_skipExpired skip removing expired items first.
- * @param {boolean=} opt_strict Also remove invalid keys.
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.collectOversize =
- function(opt_skipExpired, opt_strict) {
- var keys = this.getKeys_(true);
- if (!opt_skipExpired) {
- var keysToRemove = this.collectInternal(keys, opt_strict);
- keys = goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(
- keys, keysToRemove);
- }
- keys = this.collectOversize_(keys, this.maxItems_);
- this.setKeys_(keys);
- };
- /**
- * Set an item in the storage.
- *
- * @param {string} key The key to set.
- * @param {*} value The value to serialize to a string and save.
- * @param {number=} opt_expiration The number of miliseconds since epoch
- * (as in goog.now()) when the value is to expire. If the expiration
- * time is not provided, the value will persist as long as possible.
- * @override
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.set = function(
- key, value, opt_expiration) {
- goog.labs.storage.BoundedCollectableStorage.base(
- this, 'set', key, value, opt_expiration);
- var keys = this.getKeys_(true);
- goog.array.remove(keys, key);
- if (goog.isDef(value)) {
- keys.push(key);
- if (keys.length >= this.maxItems_) {
- var keysToRemove = this.collectInternal(keys);
- keys = goog.labs.storage.BoundedCollectableStorage.removeSubsequence_(
- keys, keysToRemove);
- keys = this.collectOversize_(keys, this.maxItems_);
- }
- }
- this.setKeys_(keys);
- };
- /**
- * Remove an item from the data storage.
- *
- * @param {string} key The key to remove.
- * @override
- */
- goog.labs.storage.BoundedCollectableStorage.prototype.remove = function(key) {
- goog.labs.storage.BoundedCollectableStorage.base(this, 'remove', key);
- var keys = this.getKeys_(false);
- if (goog.isDef(keys)) {
- goog.array.remove(keys, key);
- this.setKeys_(keys);
- }
- };
|