PHP Classes

File: src/js/browser/Unicache.js

Recommend this page to a friend!
  Classes of Nikos M.   Unicache   src/js/browser/Unicache.js   Download  
File: src/js/browser/Unicache.js
Role: Auxiliary data
Content typex: text/plain
Description: Auxiliary data
Class: Unicache
Store cached data in different storage types
Author: By
Last change:
Date: 2 months ago
Size: 44,719 bytes
 

Contents

Class file image Download
/** * Unicache * An agnostic caching framework for PHP, JavaScript, Python * * @version: 1.3.0 * https://github.com/foo123/Unicache * **/ !function( root, name, factory ){ "use strict"; if (('object'===typeof module)&&module.exports) /* CommonJS */ (module.$deps = module.$deps||{}) && (module.exports = module.$deps[name] = factory.call(root)); else if (('function'===typeof define)&&define.amd&&('function'===typeof require)&&('function'===typeof require.specified)&&require.specified(name) /*&& !require.defined(name)*/) /* AMD */ define(name,['module'],function(module) {factory.moduleUri = module.uri; return factory.call(root);}); else if (!(name in root)) /* Browser/WebWorker/.. */ (root[name] = factory.call(root)||1)&&('function'===typeof(define))&&define.amd&&define(function() {return root[name];} ); }(/* current root */ 'undefined' !== typeof self ? self : this, /* module name */ "UNICACHE", /* module factory */ function ModuleFactory__UNICACHE(undef) { "use strict"; var VERSION = '1.3.0', PROTO = 'prototype', NotImplemented = new Error("Not Implemented!"); var UNICACHE = {}; UNICACHE.Cache = function() {}; UNICACHE.Cache[PROTO] = { constructor: UNICACHE.Cache, dispose: function() { return this; }, supportsSync: function() { // whether this cache type supports sync operations // else a callback needs to be provided (as last argument in standard manner) return false; }, prefix: '', setPrefix: function(prefix) { this.prefix = !!prefix ? String(prefix) : ''; return this; }, // NOTE: All following cache manipulation methods can be "promisified" if wanted // since they use callbacks in standard manner i.e as last arguments with signature: function(err, result) // abstract methods, need implementation get: function(key, cb) { throw NotImplemented; }, put: function(key, data, ttl, cb) { throw NotImplemented; }, remove: function(key, cb) { throw NotImplemented; }, clear: function(cb) { throw NotImplemented; }, gc: function(maxlifetime, cb) { throw NotImplemented; }, // promisified methods getPromise: function(key) { var self = this; if ('function' === typeof Promise) { return new Promise(function(resolve, reject) { self.get(key, function(err, res) { if (err) reject(err); else resolve(res); }); }); } return null; }, putPromise: function(key, data, ttl) { var self = this; if ('function' === typeof Promise) { return new Promise(function(resolve, reject) { self.put(key, data, ttl, function(err, res) { if (err) reject(err); else resolve(res); }); }); } return null; }, removePromise: function(key) { var self = this; if ('function' === typeof Promise) { return new Promise(function(resolve, reject) { self.remove(key, function(err, res) { if (err) reject(err); else resolve(res); }); }); } return null; }, clearPromise: function() { var self = this; if ('function' === typeof Promise) { return new Promise(function(resolve, reject) { self.clear(function(err, res){ if (err) reject(err); else resolve(res); }); }); } return null; }, gcPromise: function(maxlifetime) { var self = this; if ('function' === typeof Promise) { return new Promise(function(resolve, reject) { self.gc(maxlifetime, function(err, res) { if (err) reject(err); else resolve(res); }); }); } return null; } }; var _ = UNICACHE._ = { ESC_RE: /([[\]().?/*{}+$^:\\])/g, TRIM_RE: null, LTRIM_RE: null, RTRIM_RE: null, time: function() { return Math.floor(new Date().getTime() / 1000); }, serialize: function(data) { return JSON.stringify(data); }, unserialize: function(data) { return JSON.parse(data); }, isset: function(o, k, strict) { var exists = !!(o && Object.prototype.hasOwnProperty.call(o, k)); return true === strict ? exists && (null != o[k]) : exists; }, trim: function(str, charlist) { var re; if (2 > arguments.length) { if (!this.TRIM_RE) this.TRIM_RE = new RegExp('^[' + ' \\s\u00A0' + ']+|[' + ' \\s\u00A0' + ']+$', 'g'); re = this.TRIM_RE; } else { charlist = String(charlist).replace(this.ESC_RE, '\\$1'); re = new RegExp('^[' + charlist + ']+|[' + charlist + ']+$', 'g'); } return String(str).replace(re, '') }, ltrim: function(str, charlist) { var re; if (2 > arguments.length) { if (!this.LTRIM_RE) this.LTRIM_RE = new RegExp('^[' + ' \\s\u00A0' + ']+', 'g'); re = this.LTRIM_RE; } else { charlist = String(charlist).replace(this.ESC_RE, '\\$1'); re = new RegExp('^[' + charlist + ']+', 'g'); } return String(str).replace(re, '') }, rtrim: function(str, charlist) { var re; if (2 > arguments.length) { if (!this.RTRIM_RE) this.RTRIM_RE = new RegExp('[' + ' \\s\u00A0' + ']+$', 'g'); re = this.RTRIM_RE; } else { charlist = String(charlist).replace(this.ESC_RE, '\\$1'); re = new RegExp('[' + charlist + ']+$', 'g'); } return String(str).replace(re, '') } }; UNICACHE.Factory = function() {}; UNICACHE.Factory.VERSION = VERSION; UNICACHE.Factory.getCache = function(cfgs) { if ('[object Array]' !== Object[PROTO].toString.call(cfgs)) cfgs = [cfgs]; // try and get the first that is supported for (var i=0,n=cfgs.length; i<n; ++i) { var config = cfgs[i]; if (!config) continue; var backend = _.isset(config, 'cacheType', true) ? String(config['cacheType']).toUpperCase() : 'MEMORY'; var cache = null; switch (backend) { case 'INDEXEDDB': if (!UNICACHE.IndexedDbCache || !UNICACHE.IndexedDbCache.isSupported()) { //throw new ReferenceError('UNICACHE: Cache "'+backend+'" is NOT supported!'); } else { cache = new UNICACHE.IndexedDbCache(); } break; case 'WEBSQL': if (!UNICACHE.WebSqlCache || !UNICACHE.WebSqlCache.isSupported()) { //throw new ReferenceError('UNICACHE: Cache "'+backend+'" is NOT supported!'); } else { cache = new UNICACHE.WebSqlCache(); } break; case 'SESSIONSTORAGE': if (!UNICACHE.SessionStorageCache || !UNICACHE.SessionStorageCache.isSupported()) { //throw new ReferenceError('UNICACHE: Cache "'+backend+'" is NOT supported!'); } else { cache = new UNICACHE.SessionStorageCache(); } break; case 'LOCALSTORAGE': if (!UNICACHE.LocalStorageCache || !UNICACHE.LocalStorageCache.isSupported()) { //throw new ReferenceError('UNICACHE: Cache "'+backend+'" is NOT supported!'); } else { cache = new UNICACHE.LocalStorageCache(); } break; case 'COOKIE': if (!UNICACHE.CookieCache || !UNICACHE.CookieCache.isSupported()) { //throw new ReferenceError('UNICACHE: Cache "'+backend+'" is NOT supported!'); } else { cache = new UNICACHE.CookieCache(); } break; case 'MEMORY': if (!UNICACHE.MemoryCache || !UNICACHE.MemoryCache.isSupported()) { //throw new ReferenceError('UNICACHE: Cache "MEMORY" is NOT supported!'); } else { cache = new UNICACHE.MemoryCache(); } break; } if (cache) { cache.setPrefix( _.isset(config, 'prefix', true) ? config['prefix'] : '' ); return cache; } } return null; }; !function(UNICACHE) { "use strict"; var PROTO = 'prototype', _ = UNICACHE._; var MemoryCache = UNICACHE.MemoryCache = function() { this._cache = {}; }; // extend UNICACHE.Cache class MemoryCache[PROTO] = Object.create(UNICACHE.Cache[PROTO]); MemoryCache.isSupported = function() { return true; }; MemoryCache[PROTO]._cache = null; MemoryCache[PROTO].dispose = function() { this._cache = null; return UNICACHE.Cache[PROTO].dispose.call(this); }; MemoryCache[PROTO].supportsSync = function() { // can read/write/etc using sync operations as well return true; }; MemoryCache[PROTO].put = function(key, data, ttl, cb) { this._cache[this.prefix + key] = [_.time() + ttl, data]; if ('function' === typeof cb) cb(null, true); return true; }; MemoryCache[PROTO].get = function(key, cb) { var ret; if (!_.isset(this._cache, this.prefix + key, true)) { ret = false; } else { var data = this._cache[this.prefix + key]; if (!data || (_.time() > data[0])) { delete this._cache[this.prefix + key]; ret = false; } else { ret = data[1]; } } if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; MemoryCache[PROTO].remove = function(key, cb) { var ret; if (!_.isset(this._cache, this.prefix + key)) { ret = false; } else { delete this._cache[this.prefix + key]; ret = true; } if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; MemoryCache[PROTO].clear = function(cb) { if (!this.prefix.length) { this._cache = {}; } else { for (key in this._cache) { if (!_.isset(this._cache, key)) continue; if (0 === key.indexOf(this.prefix)) { delete this._cache[key]; } } } if ('function' === typeof cb) { cb(null, true); } else { return true; } }; MemoryCache[PROTO].gc = function(maxlifetime, cb) { maxlifetime = +maxlifetime; var currenttime = _.time(), pl = this.prefix.length, data; for (key in this._cache) { if (!_.isset(this._cache, key)) continue; if (!pl || (0 === key.indexOf(this.prefix))) { data = this._cache[key]; if (data[0] < currenttime-maxlifetime) delete this._cache[key]; } } if ('function' === typeof cb) { cb(null, true); } else { return true; } }; return MemoryCache; }(UNICACHE); !function(UNICACHE){ "use strict"; var PROTO = 'prototype', PREFIX = 'UNICACHE_', _ = UNICACHE._; function rawurldecode(str) { return decodeURIComponent( String(str) ); } function rawurlencode(str) { return encodeURIComponent( String(str) ) .split('!').join('%21') .split("'").join('%27') .split('(').join('%28') .split(')').join('%29') .split('*').join('%2A') //.split('~').join('%7E') ; } function urldecode(str) { return rawurldecode( String(str).split('+').join('%20') ); } function urlencode(str) { return rawurlencode( String(str) ).split('%20').join('+'); } function parseCookies(cookies) { var jar = {}, i, l, cookie, key, value, kv; cookies = (cookies || document.cookie || '').split(';'); for (i=0,l=cookies.length; i<l; ++i) { cookie = cookies[i]; if (-1 === cookie.indexOf('=')) { //key = _.trim(cookie); //value = true; continue; } else { kv = _.trim(cookie).split('=', 2); key = _.trim(kv[0]); value = _.trim(kv[1]); } key = urldecode(key); if ((0 === key.indexOf(PREFIX)) && (key.length > PREFIX.length)) { value = urldecode(value); jar[key.slice(PREFIX.length)] = _.unserialize(value); } } return jar; } function setCookie(cookie) { var isRaw = !!cookie.raw, str = (isRaw ? PREFIX+String(cookie.name) : urlencode(PREFIX+String(cookie.name))) + '='; if ((null == cookie.value) || ('' === cookie.value)/* || -1 === cookie.expires*/) { str += 'deleted; expires='+(new Date(/*'D, d-M-Y H:i:s T',*/(_.time() - 31536001)*1000).toUTCString()); } else { str += isRaw ? String(cookie.value) : rawurlencode(cookie.value); if (0 !== cookie.expires /*&& -1 !== cookie.expires*/ ) { str += '; expires='+(new Date(/*'D, d-M-Y H:i:s T',*/1000*cookie.expires).toUTCString()); } } if (cookie.path) { str += '; path='+String(cookie.path); } if (cookie.domain) { str += '; domain='+String(cookie.domain); } /*if (true === cookie.secure) { str += '; secure'; } if (true === cookie.httponly) { str += '; httponly'; } if (null != cookie.sameSite) { str += '; samesite='+cookie.sameSite; }*/ return document.cookie = str; } function removeCookie(name) { setCookie({name:name, value:null}); } var CookieCache = UNICACHE.CookieCache = function() { this._cookie = parseCookies(); }; // extend UNICACHE.Cache class CookieCache[PROTO] = Object.create(UNICACHE.Cache[PROTO]); CookieCache.isSupported = function() { return true; }; CookieCache[PROTO]._cookie = null; CookieCache[PROTO].dispose = function() { this._cookie = null; return UNICACHE.Cache[PROTO].dispose.call(this); }; CookieCache[PROTO].supportsSync = function() { // can read/write/etc using sync operations as well return true; }; CookieCache[PROTO].put = function(key, data, ttl, cb) { var k = this.prefix+key, v = [_.time() + ttl, data]; this._cookie[k] = v; setCookie({name:k, value:_.serialize(v), expires:v[0]}); if ('function' === typeof cb) cb(null, true); return true; }; CookieCache[PROTO].get = function(key, cb) { var ret; if (!_.isset(this._cookie, this.prefix + key, true)) { ret = false; } else { var data = this._cookie[this.prefix + key]; if (!data || (_.time() > data[0])) { delete this._cookie[this.prefix + key]; removeCookie(this.prefix + key); ret = false; } else { ret = data[1]; } } if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; CookieCache[PROTO].remove = function(key, cb) { var ret; if (!_.isset(this._cookie, this.prefix + key)) { ret = false; } else { delete this._cookie[this.prefix + key]; removeCookie(this.prefix + key); ret = true; } if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; CookieCache[PROTO].clear = function(cb) { for (var key in this._cookie) { if (!_.isset(this._cookie, key)) continue; if (!this.prefix.length || (0 === key.indexOf(this.prefix))) { delete this._cookie[key]; removeCookie(key); } } if ('function' === typeof cb) { cb(null, true); } else { return true; } }; CookieCache[PROTO].gc = function(maxlifetime, cb) { maxlifetime = +maxlifetime; var currenttime = _.time(), pl = this.prefix.length, data; for (key in this._cookie) { if (!_.isset(this._cookie, key)) continue; if (!pl || (0 === key.indexOf(this.prefix))) { data = this._cookie[key]; if (data[0] < currenttime-maxlifetime) { delete this._cookie[key]; removeCookie(key); } } } if ('function' === typeof cb) { cb(null, true); } else { return true; } }; return CookieCache; }(UNICACHE); !function(UNICACHE) { "use strict"; var PROTO = 'prototype', PREFIX = 'UNICACHE_', EXPIRE = 'UNICACHEEXPIRES_', _ = UNICACHE._, ROOT = 'undefined' !== typeof window ? window : ('undefined' !== typeof self ? self : this) ; function supportsStorage(type) { type = type || 'localStorage'; if (!(type in ROOT)) return false; try { // Create a test value and attempt to set, get and remove the // value. These are the core functionality required by locache var test_val = "___UNICACHE___"; ROOT[type].setItem(test_val, test_val); ROOT[type].getItem(test_val); ROOT[type].removeItem(test_val); // If any of the checks fail, an exception will be raised. At // that point we can flag the browser as not supporting // localStorage or sessionStorage. return true; } catch (e) { return false; } } function set(key, value, expires) { ROOT.localStorage.setItem(PREFIX + key, value); ROOT.localStorage.setItem(EXPIRE + key, +expires); } function get(key) { return {data: ROOT.localStorage.getItem(PREFIX + key), expires: getExpires(key)}; } function getExpires(key) { var expires = ROOT.localStorage.getItem(EXPIRE + key); return expires ? parseInt(expires, 10) : null; } function del(key) { ROOT.localStorage.removeItem(PREFIX + key); ROOT.localStorage.removeItem(EXPIRE + key); } var LocalStorageCache = UNICACHE.LocalStorageCache = function() { }; // extend UNICACHE.Cache class LocalStorageCache[PROTO] = Object.create(UNICACHE.Cache[PROTO]); LocalStorageCache.isSupported = function() { return supportsStorage('localStorage'); }; LocalStorageCache[PROTO].supportsSync = function() { // can read/write/etc using sync operations as well return true; }; LocalStorageCache[PROTO].put = function(key, data, ttl, cb) { var v = [_.time() + ttl, data]; set(this.prefix + key, _.serialize(v), v[0]); if ('function' === typeof cb) cb(null, true); return true; }; LocalStorageCache[PROTO].get = function(key, cb) { var ret, v = get(this.prefix + key), now = _.time(); if (v.data) { v.data = _.unserialize(v.data); if (!v.data || (v.data[0] < now) || (v.expires < now)) { del(this.prefix + key); ret = false; } else { ret = v.data[1]; } } else { ret = false; } if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; LocalStorageCache[PROTO].remove = function(key, cb) { var ret = true; del(this.prefix + key); if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; LocalStorageCache[PROTO].clear = function(cb) { var todel = []; for(var key,i=0,l=ROOT.localStorage.length; i<l; ++i) { key = ROOT.localStorage.key(i); if (0 !== key.indexOf(PREFIX)) continue; key = key.slice(PREFIX.length); if (!this.prefix.length || (0 === key.indexOf(this.prefix))) { todel.push(key); } } todel.map(function(key) {del(key);}); if ('function' === typeof cb) { cb(null, true); } else { return true; } }; LocalStorageCache[PROTO].gc = function(maxlifetime, cb) { maxlifetime = +maxlifetime; var currenttime = _.time(), pl = this.prefix.length, todel = []; for(var key,i=0,l=ROOT.localStorage.length; i<l; ++i) { key = ROOT.localStorage.key(i); if (0 !== key.indexOf(EXPIRE)) continue; key = key.slice(EXPIRE.length); if (!pl || (0 === key.indexOf(this.prefix))) { if (getExpires(key) < currenttime-maxlifetime) todel.push(key); } } todel.map(function(key) {del(key);}); if ('function' === typeof cb) { cb(null, true); } else { return true; } }; return LocalStorageCache; }(UNICACHE); !function(UNICACHE) { "use strict"; var PROTO = 'prototype', PREFIX = 'UNICACHE_', EXPIRE = 'UNICACHEEXPIRES_', _ = UNICACHE._, ROOT = 'undefined' !== typeof window ? window : ('undefined' !== typeof self ? self : this) ; function supportsStorage(type) { type = type || 'localStorage'; if (!(type in ROOT)) return false; try { // Create a test value and attempt to set, get and remove the // value. These are the core functionality required by locache var test_val = "___UNICACHE___"; ROOT[type].setItem(test_val, test_val); ROOT[type].getItem(test_val); ROOT[type].removeItem(test_val); // If any of the checks fail, an exception will be raised. At // that point we can flag the browser as not supporting // localStorage or sessionStorage. return true; } catch (e) { return false; } } function set(key, value, expires) { ROOT.sessionStorage.setItem(PREFIX + key, value); ROOT.sessionStorage.setItem(EXPIRE + key, +expires); } function get(key) { return {data: ROOT.sessionStorage.getItem(PREFIX + key), expires: getExpires(key)}; } function getExpires(key) { var expires = ROOT.sessionStorage.getItem(EXPIRE + key); return expires ? parseInt(expires, 10) : null; } function del(key) { ROOT.sessionStorage.removeItem(PREFIX + key); ROOT.sessionStorage.removeItem(EXPIRE + key); } var SessionStorageCache = UNICACHE.SessionStorageCache = function() { }; // extend UNICACHE.Cache class SessionStorageCache[PROTO] = Object.create(UNICACHE.Cache[PROTO]); SessionStorageCache.isSupported = function() { return supportsStorage('sessionStorage'); }; SessionStorageCache[PROTO].supportsSync = function() { // can read/write/etc using sync operations as well return true; }; SessionStorageCache[PROTO].put = function(key, data, ttl, cb) { var v = [_.time() + ttl, data]; set(this.prefix + key, _.serialize(v), v[0]); if ('function' === typeof cb) cb(null, true); return true; }; SessionStorageCache[PROTO].get = function(key, cb) { var ret, v = get(this.prefix + key), now = _.time(); if (v.data) { v.data = _.unserialize(v.data); if (!v.data || (v.data[0] < now) || (v.expires < now)) { del(this.prefix + key); ret = false; } else { ret = v.data[1]; } } else { ret = false; } if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; SessionStorageCache[PROTO].remove = function(key, cb) { var ret = true; del(this.prefix + key); if ('function' === typeof cb) { cb(null, ret); } else { return ret; } }; SessionStorageCache[PROTO].clear = function(cb) { var todel = []; for(var key,i=0,l=ROOT.sessionStorage.length; i<l; ++i) { key = ROOT.sessionStorage.key(i); if (0 !== key.indexOf(PREFIX)) continue; key = key.slice(PREFIX.length); if (!this.prefix.length || (0 === key.indexOf(this.prefix))) { todel.push(key); } } todel.map(function(key) {del(key);}); if ('function' === typeof cb) { cb(null, true); } else { return true; } }; SessionStorageCache[PROTO].gc = function(maxlifetime, cb) { maxlifetime = +maxlifetime; var currenttime = _.time(), pl = this.prefix.length, todel = []; for(var key,i=0,l=ROOT.sessionStorage.length; i<l; ++i) { key = ROOT.sessionStorage.key(i); if (0 !== key.indexOf(EXPIRE)) continue; key = key.slice(EXPIRE.length); if (!pl || (0 === key.indexOf(this.prefix))) { if (getExpires(key) < currenttime-maxlifetime) todel.push(key); } } todel.map(function(key) {del(key);}); if ('function' === typeof cb) { cb(null, true); } else { return true; } }; return SessionStorageCache; }(UNICACHE); !function(UNICACHE){ "use strict"; var PROTO = 'prototype', DB_NAME = 'unicache_websql_db', DB_STORE_NAME = 'unicache_', DB_VER = '1.0', MAX_SIZE = 2 * 1024 * 1024 /* 2MB */, _ = UNICACHE._; var WebSqlCache = UNICACHE.WebSqlCache = function() { this.queue = []; }; function tryExecuteSql(t, sqlStatement, args, cb) { t.executeSql(sqlStatement, args, function(t, res) { if ('function' === typeof cb) cb(null, res); }, function(t, err) { if ('function' === typeof cb) cb(err, null); }); } function set(db, prefix, key, value, expires, cb) { db.transaction(function(t) { // insert or update, based if key exists already tryExecuteSql(t, 'SELECT * FROM "'+DB_STORE_NAME + prefix+'" WHERE key=? LIMIT 1', [key], function(err, res) { if (err) { if ('function' === typeof cb) cb(err, null); return; } if (!res || !res.rows.length) { tryExecuteSql(t, 'INSERT INTO "'+DB_STORE_NAME + prefix+'" (key,value,expires) VALUES(?,?,?)', [key,value,+expires], function(err, res) { if ('function' === typeof cb) cb(err, res); }); } else { tryExecuteSql(t, 'UPDATE "'+DB_STORE_NAME + prefix+'" SET value=?,expires=? WHERE key=?', [value,+expires,key], function(err, res) { if ('function' === typeof cb) cb(err, res); }); } }); }, function(err) { if ('function' === typeof cb) cb(err, null); }); } function get(db, prefix, key, expires, cb) { db.transaction(function(t) { if (null == expires) { tryExecuteSql(t, 'SELECT * FROM "'+DB_STORE_NAME + prefix+'" WHERE key = ?', [key], function(err, res) { if ('function' === typeof cb) cb(err, res && res.rows.length ? {value:res.rows.item(0).value, expires:res.rows.item(0).expires} : null); }); } else { tryExecuteSql(t, 'SELECT * FROM "'+DB_STORE_NAME + prefix+'" WHERE key = ? AND expires >= ?', [key,+expires], function(err, res) { if ('function' === typeof cb) cb(err, res && res.rows.length ? {value:res.rows.item(0).value, expires:res.rows.item(0).expires} : null); }); } }, function(err) { if ('function' === typeof cb) cb(err, null); }); } function del(db, prefix, key, cb) { db.transaction(function(t) { // insert or update, based if key exists already tryExecuteSql(t, 'DELETE FROM "'+DB_STORE_NAME + prefix+'" WHERE key=?', [key], function(err, res) { if ('function' === typeof cb) cb(err, res); }); }, function(err) { if ('function' === typeof cb) cb(err, null); }); } function clear(db, prefix, cb) { db.transaction(function(t) { // insert or update, based if key exists already tryExecuteSql(t, 'DELETE FROM "'+DB_STORE_NAME + prefix+'"', [], function(err, res) { if ('function' === typeof cb) cb(err, res); }); }, function(err) { if ('function' === typeof cb) cb(err, null); }); } function gc(db, prefix, maxlifetime, currenttime, cb) { if (null == currenttime) currenttime = _.time(); db.transaction(function(t) { // insert or update, based if key exists already tryExecuteSql(t, 'DELETE FROM "'+DB_STORE_NAME + prefix+'" WHERE expires < ?', [currenttime-maxlifetime], function(err, res) { if ('function' === typeof cb) cb(err, res); }); }, function(err) { if ('function' === typeof cb) cb(err, null); }); } // extend UNICACHE.Cache class WebSqlCache[PROTO] = Object.create(UNICACHE.Cache[PROTO]); WebSqlCache.isSupported = function() { return 'function' === typeof openDatabase; }; WebSqlCache[PROTO].db = null; WebSqlCache[PROTO].err = null; WebSqlCache[PROTO].queue = null; WebSqlCache[PROTO].open = function(cb) { var self = this; if (null == self.db) { if ('function' === typeof cb) self.queue.push(cb); try{ self.db = openDatabase(DB_NAME, DB_VER, 'UNICACHE Cache Database', MAX_SIZE); } catch(e) { self.err = e; self.db = false; } if (self.db) { self.db.transaction(function(t) { // create db table related to passed cache prefix // NOTE: cannot change prefix in the middle of operations // need to instantiate new instance of WebSqlCache with new prefix // this is done to avoid having to run create table on every sql query, in case prefix has changed tryExecuteSql(t, 'CREATE TABLE IF NOT EXISTS "'+DB_STORE_NAME + self.prefix+'" (id INTEGER PRIMARY KEY, key unique, value, expires INTEGER)', [], function(err, res) { if (err) self.err = err; var queue = self.queue; self.queue = null; queue.map(function(cb) {cb(self.err, self.db);}); }); }, function(err) { self.err = err; var queue = self.queue; self.queue = null; queue.map(function(cb) {cb(self.err, self.db);}); }); } else { var queue = self.queue; self.queue = null; queue.map(function(cb) {cb(self.err, self.db);}); } } else if (self.queue) { if ('function' === typeof cb) self.queue.push(cb); } else { if ('function' === typeof cb) cb(self.err, self.db); } return this; }; WebSqlCache[PROTO].close = function() { var self = this; if (self.db) { self.db = null; } return this; }; WebSqlCache[PROTO].drop = function(prefix, cb) { var self = this; if (self.db) { if (null == prefix) prefix = self.prefix; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); return; } db.transaction(function(t) { tryExecuteSql(t, 'DROP TABLE IF EXISTS "'+DB_STORE_NAME + prefix+'"', [], function(err, res) { if ('function' === typeof cb) cb(err, res); }); },function(err) { if ('function' === typeof cb) cb(err, null); }); }); } }; WebSqlCache[PROTO].dispose = function() { this.close(); this.db = null; this.err = null; this.queue = null; return UNICACHE.Cache[PROTO].dispose.call(this); }; WebSqlCache[PROTO].put = function(key, value, ttl, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); return; } ttl = +ttl; set(db, self.prefix, key, _.serialize(value), _.time() + ttl, cb); }); }; WebSqlCache[PROTO].get = function(key, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); return; } var currenttime = _.time(); get(db, self.prefix, key, currenttime, function(err, res) { if (err) { if ('function' === typeof cb) cb(err, null); return; } if (res && (res.expires < currenttime)) { del(db, self.prefix, key); } if ('function' === typeof cb) { if (!res || (res.expires < currenttime)) { cb(null, false); } else { cb(null, _.unserialize(res.value)); } } }); }); }; WebSqlCache[PROTO].remove = function(key, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); return; } del(db, self.prefix, key, cb); }); }; WebSqlCache[PROTO].clear = function(cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); return; } clear(db, self.prefix, cb); }); }; WebSqlCache[PROTO].gc = function(maxlifetime, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); return; } maxlifetime = +maxlifetime; var currenttime = _.time(); gc(db, self.prefix, maxlifetime, currenttime, cb); }); }; return WebSqlCache; }(UNICACHE); !function(UNICACHE) { "use strict"; var PROTO = 'prototype', _ = UNICACHE._, DB_NAME = 'unicache-indexeddb-cache-db', DB_VERSION = 1, // Use a long long for this value (don't use a float) DB_STORE_NAME = 'unicache_db_store_', ROOT = 'undefined' !== typeof window ? window : ('undefined' !== typeof self ? self : this), IDB = getIDB() ; // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB function getIDB() { var idb = {db:null, transaction: {READ_WRITE:"readwrite"}, keyrange:null}; /* global indexedDB,webkitIndexedDB,mozIndexedDB,OIndexedDB,msIndexedDB */ try { if (typeof ROOT.indexedDB !== 'undefined') { idb.db = ROOT.indexedDB; } else if (typeof ROOT.webkitIndexedDB !== 'undefined') { idb.db = ROOT.webkitIndexedDB; } else if (typeof ROOT.mozIndexedDB !== 'undefined') { idb.db = ROOT.mozIndexedDB; } else if (typeof ROOT.OIndexedDB !== 'undefined') { idb.db = ROOT.OIndexedDB; } else if (typeof ROOT.msIndexedDB !== 'undefined') { idb.db = ROOT.msIndexedDB; } } catch (e) { } try { if (typeof ROOT.IDBTransaction !== 'undefined') { idb.transaction = ROOT.IDBTransaction; } else if (typeof ROOT.webkitIDBTransaction !== 'undefined') { idb.transaction = ROOT.webkitIDBTransaction; } else if (typeof ROOT.msIDBTransaction !== 'undefined') { idb.transaction = ROOT.msIDBTransaction } } catch (e) { } try { if (typeof ROOT.IDBKeyRange !== 'undefined') { idb.keyrange = ROOT.IDBKeyRange; } else if (typeof ROOT.webkitIDBKeyRange !== 'undefined') { idb.keyrange = ROOT.webkitIDBKeyRange; } else if (typeof ROOT.msIDBKeyRange !== 'undefined') { idb.keyrange = ROOT.msIDBKeyRange } } catch (e) { } return idb; } var IndexedDbCache = UNICACHE.IndexedDbCache = function() { this.queue = []; }; function set(db, store, key, value, expires, cb) { var request = db.transaction([store], "readwrite").objectStore(store).put([_.time()+expires, value], key); request.onerror = function(event) { if ('function' === typeof cb) cb(new Error('Setting "'+key+'" failed!'), null); }; request.onsuccess = function(event) { if ('function' === typeof cb) cb(null, true); }; } function get(db, store, key, cb) { var request = db.transaction([store], "readonly").objectStore(store).get(key); request.onerror = function(event) { if ('function' === typeof cb) cb(new Error('Retrieving "'+key+'" failed!'), null); }; request.onsuccess = function(event) { if ('function' === typeof cb) cb(null, request.result); }; } function del(db, store, key, cb) { var request = db.transaction([store], "readwrite").objectStore(store).delete(key); request.onerror = function(event) { if ('function' === typeof cb) cb(new Error('Deleting "'+key+'" failed!'), null); }; request.onsuccess = function(event) { if ('function' === typeof cb) cb(null, true); }; } // extend UNICACHE.Cache class IndexedDbCache[PROTO] = Object.create(UNICACHE.Cache[PROTO]); IndexedDbCache.isSupported = function( ) { return !!IDB.db; }; IndexedDbCache[PROTO].request = null; IndexedDbCache[PROTO].db = null; IndexedDbCache[PROTO].queue = null; IndexedDbCache[PROTO].open = function(cb) { var self = this; if (!self.request) { if (self.queue && ('function' === typeof cb)) self.queue.push(cb); self.request = IDB.db.open(DB_NAME, DB_VERSION); self.request.onerror = function(event) { self.db = false; if (self.queue) { var err = new Error('IndexedDB Open Error with code '+self.request.errorCode); var queue = self.queue; self.queue = null; queue.map(function(cb) {cb(err, null);}); } }; self.request.onblocked = function(event) { // If some other tab is loaded with the database, then it needs to be closed // before we can proceed. console.warn("UNICACHE: Please close all other tabs with this site open!"); }; self.request.onupgradeneeded = function(event) { var db = event.target.result; // Create an objectStore to hold information about any javascript value even primitives. var objectStore = db.createObjectStore(DB_STORE_NAME + self.prefix); }; self.request.onsuccess = function(event) { self.db = event.target.result; self.db.onclose = function(event) { self.db = null; //alert("A new version of this page is ready. Please reload or close this tab!"); }; self.db.onversionchange = function(event) { self.db.close(); self.db = null; //alert("A new version of this page is ready. Please reload or close this tab!"); }; if (self.queue) { var queue = self.queue; self.queue = null; queue.map(function(cb) {cb(null, self.db);}); } }; } else if (null == self.db) { if (self.queue && ('function' === typeof cb)) self.queue.push(cb); } else if (false === self.db) { if ('function' === typeof cb) cb(new Error('IndexedDB cannot be opened'), null); } else if (self.db) { if ('function' === typeof cb) cb(null, self.db); } return this; }; IndexedDbCache[PROTO].close = function() { if (this.db) { this.db.close(); this.db = null; } return this; }; IndexedDbCache[PROTO].dispose = function() { this.close(); this.db = null; this.request = null; this.queue = null; return UNICACHE.Cache[PROTO].dispose.call(this); }; IndexedDbCache[PROTO].supportsSync = function() { return false; }; IndexedDbCache[PROTO].put = function(key, data, ttl, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); } else { set(db, DB_STORE_NAME + self.prefix, key, data, +ttl, function(err, res) { if ('function' === typeof cb) cb(err, res); }); } }); }; IndexedDbCache[PROTO].get = function(key, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); } else { get(db, DB_STORE_NAME + self.prefix, key, function(err, data) { if ('function' === typeof cb) { if (err) cb(err, null); else if (!data) cb(null, false); else if (data[0] < _.time()) { del(db, DB_STORE_NAME + self.prefix, key); cb(null, false); } else cb(null, data[1]); } else { if (!err && data && (data[0] < _.time())) { del(db, DB_STORE_NAME + self.prefix, key); } } }); } }); }; IndexedDbCache[PROTO].remove = function(key, cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); } else { del(db, DB_STORE_NAME + self.prefix, key, function(err, res) { if ('function' === typeof cb) cb(err, res); }); } }); }; IndexedDbCache[PROTO].clear = function(cb) { var self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); } else { var req = db.transaction(DB_STORE_NAME + self.prefix, "readwrite").objectStore(DB_STORE_NAME + self.prefix).clear(); req.onerror = function(event) { if ('function' === typeof cb) cb(new Error('Error clearing store ' + event.target.errorCode), null); }; req.onsuccess = function(event) { if ('function' === typeof cb) cb(null, true); }; } }); }; IndexedDbCache[PROTO].gc = function(maxlifetime, cb) { maxlifetime = +maxlifetime; var currenttime = _.time(), self = this; self.open(function(err, db) { if (err) { if ('function' === typeof cb) cb(err, null); } else { var cursor = db.transaction(DB_STORE_NAME + self.prefix, "readwrite").objectStore(DB_STORE_NAME + self.prefix).openCursor(); var todel = []; cursor.onerror = function(event) { if ('function' === typeof cb) cb(new Error('Error looping through store ' + event.target.errorCode), null); }; cursor.onsuccess = function(event) { var cursor = event.target.result; if (cursor) { var expires = cursor.value[0]; if (expires < currenttime-maxlifetime) todel.push(cursor.key); cursor.continue(); } else { // no more entries setTimeout(function() { todel.map(function(key) { del(db, DB_STORE_NAME + self.prefix, key); }); if ('function' === typeof cb) cb(null, true); }, 10); } }; } }); }; return IndexedDbCache; }(UNICACHE); // export it return UNICACHE; });