From 44c3a10487e90d4215a7f5a7b991b45830e4ce16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 20 Jun 2013 21:08:50 +0200 Subject: [PATCH] Due to a bug in Firefox which forbid state of size greater that 320k. The cache is not outside. re #73 --- index.php | 1 + resources/lru/lru.js | 249 +++++++++++++++++++++++++++++++++++++++++++ util.js | 8 +- 3 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 resources/lru/lru.js diff --git a/index.php b/index.php index 24200d3..00da962 100644 --- a/index.php +++ b/index.php @@ -40,6 +40,7 @@ " media="screen" /> + diff --git a/resources/lru/lru.js b/resources/lru/lru.js new file mode 100644 index 0000000..d014942 --- /dev/null +++ b/resources/lru/lru.js @@ -0,0 +1,249 @@ +/** + * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most + * recently used items while discarding least recently used items when its limit + * is reached. + * + * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson + * See README.md for details. + * + * Illustration of the design: + * + * entry entry entry entry + * ______ ______ ______ ______ + * | head |.newer => | |.newer => | |.newer => | tail | + * | A | | B | | C | | D | + * |______| <= older.|______| <= older.|______| <= older.|______| + * + * removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added + */ +function LRUCache (limit) { + // Current size of the cache. (Read-only). + this.size = 0; + // Maximum number of items this cache can hold. + this.limit = limit; + this._keymap = {}; +} + +/** + * Put into the cache associated with . Returns the entry which was + * removed to make room for the new entry. Otherwise undefined is returned + * (i.e. if there was enough room already). + */ +LRUCache.prototype.put = function(key, value) { + var entry = {key:key, value:value}; + // Note: No protection agains replacing, and thus orphan entries. By design. + this._keymap[key] = entry; + if (this.tail) { + // link previous tail to the new tail (entry) + this.tail.newer = entry; + entry.older = this.tail; + } else { + // we're first in -- yay + this.head = entry; + } + // add new entry to the end of the linked list -- it's now the freshest entry. + this.tail = entry; + if (this.size === this.limit) { + // we hit the limit -- remove the head + return this.shift(); + } else { + // increase the size counter + this.size++; + } +} + +/** + * Purge the least recently used (oldest) entry from the cache. Returns the + * removed entry or undefined if the cache was empty. + * + * If you need to perform any form of finalization of purged items, this is a + * good place to do it. Simply override/replace this function: + * + * var c = new LRUCache(123); + * c.shift = function() { + * var entry = LRUCache.prototype.shift.call(this); + * doSomethingWith(entry); + * return entry; + * } + */ +LRUCache.prototype.shift = function() { + // todo: handle special case when limit == 1 + var entry = this.head; + if (entry) { + if (this.head.newer) { + this.head = this.head.newer; + this.head.older = undefined; + } else { + this.head = undefined; + } + // Remove last strong reference to and remove links from the purged + // entry being returned: + entry.newer = entry.older = undefined; + // delete is slow, but we need to do this to avoid uncontrollable growth: + delete this._keymap[entry.key]; + } + return entry; +} + +/** + * Get and register recent use of . Returns the value associated with + * or undefined if not in cache. + */ +LRUCache.prototype.get = function(key, returnEntry) { + // First, find our cache entry + var entry = this._keymap[key]; + if (entry === undefined) return; // Not cached. Sorry. + // As was found in the cache, register it as being requested recently + if (entry === this.tail) { + // Already the most recenlty used entry, so no need to update the list + return entry.value; + } + // HEAD--------------TAIL + // <.older .newer> + // <--- add direction -- + // A B C E + if (entry.newer) { + if (entry === this.head) + this.head = entry.newer; + entry.newer.older = entry.older; // C <-- E. + } + if (entry.older) + entry.older.newer = entry.newer; // C. --> E + entry.newer = undefined; // D --x + entry.older = this.tail; // D. --> E + if (this.tail) + this.tail.newer = entry; // E. <-- D + this.tail = entry; + return returnEntry ? entry : entry.value; +} + +// ---------------------------------------------------------------------------- +// Following code is optional and can be removed without breaking the core +// functionality. + +/** + * Check if is in the cache without registering recent use. Feasible if + * you do not want to chage the state of the cache, but only "peek" at it. + * Returns the entry associated with if found, or undefined if not found. + */ +LRUCache.prototype.find = function(key) { + return this._keymap[key]; +} + +/** + * Update the value of entry with . Returns the old value, or undefined if + * entry was not in the cache. + */ +LRUCache.prototype.set = function(key, value) { + var oldvalue, entry = this.get(key, true); + if (entry) { + oldvalue = entry.value; + entry.value = value; + } else { + oldvalue = this.put(key, value); + if (oldvalue) oldvalue = oldvalue.value; + } + return oldvalue; +} + +/** + * Remove entry from cache and return its value. Returns undefined if not + * found. + */ +LRUCache.prototype.remove = function(key) { + var entry = this._keymap[key]; + if (!entry) return; + delete this._keymap[entry.key]; // need to do delete unfortunately + if (entry.newer && entry.older) { + // relink the older entry with the newer entry + entry.older.newer = entry.newer; + entry.newer.older = entry.older; + } else if (entry.newer) { + // remove the link to us + entry.newer.older = undefined; + // link the newer entry to head + this.head = entry.newer; + } else if (entry.older) { + // remove the link to us + entry.older.newer = undefined; + // link the newer entry to head + this.tail = entry.older; + } else {// if(entry.older === undefined && entry.newer === undefined) { + this.head = this.tail = undefined; + } + + this.size--; + return entry.value; +} + +/** Removes all entries */ +LRUCache.prototype.removeAll = function() { + // This should be safe, as we never expose strong refrences to the outside + this.head = this.tail = undefined; + this.size = 0; + this._keymap = {}; +} + +/** + * Return an array containing all keys of entries stored in the cache object, in + * arbitrary order. + */ +if (typeof Object.keys === 'function') { + LRUCache.prototype.keys = function() { return Object.keys(this._keymap); } +} else { + LRUCache.prototype.keys = function() { + var keys = []; + for (var k in this._keymap) keys.push(k); + return keys; + } +} + +/** + * Call `fun` for each entry. Starting with the newest entry if `desc` is a true + * value, otherwise starts with the oldest (head) enrty and moves towards the + * tail. + * + * `fun` is called with 3 arguments in the context `context`: + * `fun.call(context, Object key, Object value, LRUCache self)` + */ +LRUCache.prototype.forEach = function(fun, context, desc) { + if (context === true) { desc = true; context = undefined; } + else if (typeof context !== 'object') context = this; + if (desc) { + var entry = this.tail; + while (entry) { + fun.call(context, entry.key, entry.value, this); + entry = entry.older; + } + } else { + var entry = this.head; + while (entry) { + fun.call(context, entry.key, entry.value, this); + entry = entry.newer; + } + } +} + +/** Returns a JSON (array) representation */ +LRUCache.prototype.toJSON = function() { + var s = [], entry = this.head; + while (entry) { + s.push({key:entry.key.toJSON(), value:entry.value.toJSON()}); + entry = entry.newer; + } + return s; +} + +/** Returns a String representation */ +LRUCache.prototype.toString = function() { + var s = '', entry = this.head; + while (entry) { + s += String(entry.key)+':'+entry.value; + if (entry = entry.newer) + s += ' < '; + } + return s; +} + +// Export ourselves +if (typeof this === 'object') this.LRUCache = LRUCache; diff --git a/util.js b/util.js index 1e07a47..0b57d91 100644 --- a/util.js +++ b/util.js @@ -1,5 +1,7 @@ var templatePage, templateBookDetail, templateMain, currentData, before; +var cache = new LRUCache(30); + var DEBUG = true; var isEink = /Kobo|Kindle|EBRD1101/i.test(navigator.userAgent); var isPushStateEnabled = window.history && window.history.pushState && window.history.replaceState && @@ -65,7 +67,8 @@ function navigateTo (url) { before = new Date (); var jsonurl = url.replace ("index", "getJSON"); $.getJSON(jsonurl, function(data) { - history.pushState(data, "", url); + history.pushState(jsonurl, "", url); + cache.put (jsonurl, data); updatePage (data); }); } @@ -163,7 +166,8 @@ function ajaxifyLinks () { window.onpopstate = function(event) { before = new Date (); - updatePage (event.state); + var data = cache.get (event.state) + updatePage (data); }; $(document).keydown(function(e){