burry.js

#

Burry.js Storage v0.1

#

(c) 2012 Yiorgis Gozadinos, Riot AS. Burry.js is distributed under the MIT license. http://github.com/ggozad/burry.js

#

AMD/global registrations

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
#

AMD. Register as an anonymous module.

        define([], function () {
            return factory();
        });
    } else {
#

Browser globals

        root.Burry = factory();
    }
}(this, function () {
#

Construct a new Burry store with an optional namespace and an optional default ttl.

    var Burry = {

        Store: function (ns, default_ttl) {
            var stores = Burry.stores();
            if (ns) {
                this._CACHE_SUFFIX = this._CACHE_SUFFIX + ns;
                this._EXPIRY_KEY = this._EXPIRY_KEY + ns;
                if (stores.indexOf(ns) === -1)
                    stores.push(ns);
            }
            localStorage.setItem('_burry_stores_', JSON.stringify(stores));
            this.default_ttl = default_ttl;
        },
#

Time resolution in minutes

        _EXPIRY_UNITS: 60 * 1000,
#

Calculate the time since Epoch in minutes

        _mEpoch: function () {
            return Math.floor((new Date().getTime())/Burry._EXPIRY_UNITS);
        },

        stores: function () {
            var stores = localStorage.getItem('_burry_stores_');
            if (stores) {
                stores = JSON.parse(stores);
            } else {
                stores = [''];
            }
            return stores;
        },
#

Checks for localStorage & JSON support.

        isSupported: function () {
            try {
                localStorage.setItem('_burry_', '_burry_');
                localStorage.removeItem('_burry_');
            } catch (e) {
                return false;
            }
            if (!JSON) {
                return false;
            }
            return true;
        },

        flushExpired: function () {
            var i, match, key, val, ns,
                remove = [],
                now = Burry._mEpoch();

            for (i=0; i< localStorage.length; i++) {
                key = localStorage.key(i);
                match = key.match(/(.+)-_burry_exp_(.*)/);
                if (match) {
                    val = localStorage.getItem(key);
                    if (val < now) {
                        key = match[1]; ns = match[2];
                        remove.push(key + Burry.Store.prototype._CACHE_SUFFIX + ns);
                        remove.push(key + Burry.Store.prototype._EXPIRY_KEY + ns);
                    }
                }
            }
            for (i=0; i< remove.length; i++) {
                localStorage.removeItem(remove[i]);
            }
        }
    };
#

Instance methods

    Burry.Store.prototype = {
#

Suffix to all keys in the cache

        _CACHE_SUFFIX: '-_burry_',
#

Key used to store expiration data

        _EXPIRY_KEY: '-_burry_exp_',
#

Return the internally used suffixed key.

        _internalKey: function (key) {
            return key + this._CACHE_SUFFIX;
        },
#

Return the internally used suffixed expiration key.

        _expirationKey: function (key) {
            return key + this._EXPIRY_KEY;
        },
#

Check if a key is a valid internal key

        _isInternalKey: function (key) {
            if (key.slice(-this._CACHE_SUFFIX.length) === this._CACHE_SUFFIX)
                return key.slice(0, -this._CACHE_SUFFIX.length);
            return false;
        },
#

Check if a key is a valid expiration key

        _isExpirationKey: function (key) {
            if (key.slice(-this._EXPIRY_KEY.length) === this._EXPIRY_KEY)
                return key.slice(0, -this._EXPIRY_KEY.length);
            return false;
        },
#

Returns in how many minutes after Epoch the key expires, or undefined if it does not expire.

        _expiresOn: function (key) {
            var expires = localStorage.getItem(this._expirationKey(key));
            if (expires) {
                return parseInt(expires, 10);
            }
        },
#

Parse the value of a key as an integer.

        _getCounter: function (bkey) {
            var value = localStorage.getItem(bkey);
            if (value === null) return 0;

            return parseInt(value, 10);
        },
#

Returns the value of key from the cache, undefined if the key has expired or is not stored.

        get: function (key) {
            var value = localStorage.getItem(this._internalKey(key));
            if (value === null) {
                return undefined;
            }
            if (this.hasExpired(key)) {
                this.remove(key);
                return undefined;
            }
            try {
                value = JSON.parse(value);
            } catch (e) {
                return undefined;
            }
            return value;
        },
#

Sets a key/value on the cache. Optionally, sets the expiration in ttl minutes.

        set: function (key, value, ttl) {
            var i, bkey, expires = {};
            ttl = ttl || this.default_ttl;
            if (ttl) ttl = parseInt(ttl, 10);
            if (typeof key === undefined || typeof value === undefined) return;
            value = JSON.stringify(value);
            try {
                localStorage.setItem(this._internalKey(key), value);
                if (ttl) {
                    localStorage.setItem(this._expirationKey(key), Burry._mEpoch() + ttl);
                } else {
                    localStorage.removeItem(this._expirationKey(key));
                }
            } catch (e) {
                if (e.name === 'QUOTA_EXCEEDED_ERR' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
#

No space left on localStorage, let's flush expired items and try agagin.

                    Burry.flushExpired();
                    try {
                        localStorage.setItem(this._internalKey(key), value);
                        if (ttl) {
                            localStorage.setItem(this._expirationKey(key), Burry._mEpoch() + ttl);
                        } else {
                            localStorage.removeItem(this._expirationKey(key));
                        }
                    }
                    catch (e) {
#

Oh well. Let's forget about it.

                    }
                }
            }
        },
#

Sets a key/value on the cache as does set but only if the key does not already exist or has expired.

        add: function (key, value, ttl) {
            if (localStorage.getItem(this._internalKey(key)) === null || this.hasExpired(key)) {
                this.set(key, value, ttl);
            }
        },
#

Sets a key/value on the cache as does set but only if the key already exist and has not expired.

        replace: function (key, value, ttl) {
            if (localStorage.getItem(this._internalKey(key)) !== null && !this.hasExpired(key)) {
                this.set(key, value, ttl);
            }
        },
#

Removes an item from the cache.

        remove: function (key) {
            localStorage.removeItem(this._internalKey(key));
            localStorage.removeItem(this._expirationKey(key));
        },
#

Increments the integer value of key by 1

        incr: function (key) {
            var bkey = this._internalKey(key),
                value = this._getCounter(bkey);
            value++;
            localStorage.setItem(bkey, value);
        },
#

Decrements the integer value of key by 1

        decr: function (key) {
            var bkey = this._internalKey(key),
                value = this._getCounter(bkey);
            value--;
            localStorage.setItem(bkey, value);
        },
#

Returns whether key has expired.

        hasExpired: function (key) {
            var expireson = this._expiresOn(key);
            if (expireson && (expireson < Burry._mEpoch())) {
                return true;
            }
            return false;
        },
#

Returns a list of all the cached keys

        keys: function () {
            var i, bkey, key, results = [];
            for (i=0; i < localStorage.length ; i++) {
                bkey = localStorage.key(i);
                key = this._isInternalKey(bkey);
                if (key) {
                    results.push(key);
                }
            }
            return results;
        },
#

Returns an object with all the expirable keys. The values are the ttl in minutes since Epoch.

        expirableKeys: function () {
            var i, bkey, key, results = {};
            for (i=0; i < localStorage.length ; i++) {
                bkey = localStorage.key(i);
                key = this._isExpirationKey(bkey);
                if (key) {
                    results[key] = parseInt(localStorage.getItem(bkey), 10);
                }
            }
            return results;
        },
#

Removes all Burry items from localStorage.

        flush: function () {
            var i, key, remove = [];
            for (i=0; i < localStorage.length ; i++) {
                key = localStorage.key(i);
                if (this._isInternalKey(key) || this._isExpirationKey(key)) {
                    remove.push(key);
                }
            }
            for (i=0; i<remove.length; i++)
                localStorage.removeItem(remove[i]);
        },
#

Removes all expired items.

        flushExpired: function () {
            var expirable = this.expirableKeys(), now = Burry._mEpoch(), key, val;
            for (key in expirable) {
                val = expirable[key];
                if (val < now) this.remove(key);
            }
        }
    };

    return Burry;
}));