Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Pruning until an item "fits"... #3

Open
wants to merge 1 commit into from

2 participants

@clintandrewhall

Came across this via a tweet from @joemccann ... saw your TODO, thought perhaps this fix would be applicable? :-) Any thoughts?

Cheers!

  • Clint
@pamelafox
Owner

Thanks for this! - A few questions:
1) Can you explain line 55 to me further? When does that situation happen?
2) Have you tested this out? It would be useful to add a test HTML file that actually demonstrates going over quota and what happens as a result. (And yes, it's something I should have added myself- but since you suggested this change, perhaps you've put a test together).

@clintandrewhall

Howdy!

  1. I don't know if that would actually happen or not, but it seemed like an edge case to cover?
  2. I'm working on an HTML file to test, though I did test in the console. I'm hoping to have it this weekend when I have more time?

I noticed a few other folks had pull requests on this topic; you might look over them and arrive at some kind of hybrid?

In any case, this is going to come in handy for me later down the road... hopefully this helps! Cheers!

@dylanvee dylanvee referenced this pull request from a commit in dylanvee/lscache
@Jakobo Jakobo Alternate solution for pamelafox#3 this uses an LRU for the entire sy…
…stem. The cost is an additional key per item, and recording the "last touched" state when keys are read. This mirrors the cache expiry methods used in memcache (minus the slab)
b9b5844
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 48 additions and 41 deletions.
  1. +48 −41 lscache.js
View
89 lscache.js
@@ -2,35 +2,35 @@
var lscache = function() {
var CACHESUFFIX = '-cacheexpiration';
-
+
function supportsStorage() {
return ('localStorage' in window) && window['localStorage'] !== null;
}
-
+
function supportsJSON() {
return ('JSON' in window) && window['JSON'] !== null;
}
-
+
function expirationKey(key) {
return key + CACHESUFFIX;
}
-
+
function currentTime() {
// Get number of minutes since epoch
return Math.floor((new Date().getTime())/60000);
}
-
+
return {
-
+
/**
* Stores the value in localStorage. Expires after specified number of minutes.
* @param {string} key
* @param {Object|string} value
- * @param {number} time
+ * @param {number} time
*/
set: function(key, value, time) {
if (!supportsStorage()) return;
-
+
// If we don't get a string value, try to stringify
// In future, localStorage may properly support storing non-strings
// and this can be removed.
@@ -44,35 +44,43 @@ var lscache = function() {
return;
}
}
-
- try {
- localStorage.setItem(key, value);
- } catch (e) {
- if (e.name === 'QUOTA_EXCEEDED_ERR') {
- // If we exceeded the quota, then we will sort
- // by the expire time, and then remove the N oldest
- var storedKeys = [];
- for (var i = 0; i < localStorage.length; i++) {
- storedKey = localStorage.key(i);
- if (storedKey.indexOf(CACHESUFFIX) > -1) {
- var mainKey = storedKey.split(CACHESUFFIX)[0];
- storedKeys.push({key: mainKey, expiration: parseInt(localStorage[storedKey])});
+
+ // Use this function to loop until the value "fits" into available space.
+ function setItem(key, value) {
+ try {
+ return localStorage.setItem(key, value);
+ } catch (e) {
+ if (e.name === 'QUOTA_EXCEEDED_ERR') {
+ // If the quota is exceeded, but the localStorage is empty, we can't store the item.
+ if(localStorage.length <= 0) {
+ return null;
}
+ // If we exceeded the quota, then we will sort
+ // by the expire time, and then remove the N oldest
+ var storedKeys = [];
+ for (var i = 0; i < localStorage.length; i++) {
+ var storedKey = localStorage.key(i);
+ if (storedKey.indexOf(CACHESUFFIX) > -1) {
+ var mainKey = storedKey.split(CACHESUFFIX)[0];
+ storedKeys.push({key: mainKey, expiration: parseInt(localStorage[storedKey])});
+ }
+ }
+ storedKeys.sort(function(a, b) { return (a.expiration-b.expiration); });
+
+ for (var i = 0, len = Math.min(30, storedKeys.length); i < len; i++) {
+ localStorage.removeItem(storedKeys[i].key);
+ localStorage.removeItem(expirationKey(storedKeys[i].key));
+ }
+ return setItem(key, value);
+ } else {
+ // If it was some other error, just give up.
+ return null;
}
- storedKeys.sort(function(a, b) { return (a.expiration-b.expiration); });
-
- for (var i = 0, len = Math.min(30, storedKeys.length); i < len; i++) {
- localStorage.removeItem(storedKeys[i].key);
- localStorage.removeItem(expirationKey(storedKeys[i].key));
- }
- // TODO: This could still error if the items we removed were small and this is large
- localStorage.setItem(key, value);
- } else {
- // If it was some other error, just give up.
- return;
}
}
-
+
+ setItem(key, value);
+
if (time) {
localStorage.setItem(expirationKey(key), (currentTime() + time));
} else {
@@ -80,7 +88,7 @@ var lscache = function() {
localStorage.removeItem(expirationKey(key));
}
},
-
+
/**
* Retrieves specified value from localStorage, if not expired.
* @param {string} key
@@ -88,13 +96,12 @@ var lscache = function() {
*/
get: function(key) {
if (!supportsStorage()) return null;
-
+
function parsedStorage(key) {
if (supportsJSON()) {
try {
// We can't tell if its JSON or a string, so we try to parse
- var value = JSON.parse(localStorage.getItem(key));
- return value;
+ return JSON.parse(localStorage.getItem(key));
} catch(e) {
// If we can't parse, it's probably because it isn't an object
return localStorage.getItem(key);
@@ -103,7 +110,7 @@ var lscache = function() {
return localStorage.getItem(key);
}
}
-
+
if (localStorage.getItem(expirationKey(key))) {
var expirationTime = parseInt(localStorage.getItem(expirationKey(key)));
if (currentTime() > expirationTime) {
@@ -111,14 +118,14 @@ var lscache = function() {
localStorage.removeItem(expirationKey(key));
return null;
} else {
- return parsedStorage(key);
+ return parsedStorage(key);
}
} else if (localStorage.getItem(key)) {
return parsedStorage(key);
}
return null;
- },
-
+ },
+
/**
* Removes a value from localStorage.
* Equivalent to 'delete' in memcache, but that's a keyword in JS.
Something went wrong with that request. Please try again.