diff --git a/node_modules/c9/cache.js b/node_modules/c9/cache.js new file mode 100644 index 00000000..a4cfca52 --- /dev/null +++ b/node_modules/c9/cache.js @@ -0,0 +1,82 @@ + +var Cache = function(maxSize, age) { + this._maxSize = (parseInt(maxSize, 10) === maxSize) ? maxSize : 1000; + this._maxAge = (parseInt(age, 10) === age) ? age : 5000; + this._items = {}; + this._keys = []; +}; + +var proto = Cache.prototype; + +proto.set = function(key, value) { + var item = this._items[key]; + if (item) { + this.delete(key); + } + + this._keys.push(key); + this._items[key] = { + value: value, + lastAccessed: Date.now() + }; + + this.purge(); +}; + +proto.has = function(key) { + var item = this._items[key]; + return (item && !this._hasExpired(item)); +}; + +proto.get = function(key) { + var item = this._items[key]; + if (item && !this._hasExpired(item)) { + return item.value; + } + return null; +}; + +proto.delete = function(key) { + delete this._items[key]; + this._keys.splice(this._keys.indexOf(key), 1); +}; + +proto.purge = function() { + if (this._keys.length > this._maxSize) { + this._purgeItems(); + } +}; + +proto.size = function() { + return this._keys.length; +}; + +proto._purgeItems = function() { + var maxSize = this._maxSize * 0.75; + + // Remove epired items + this._keys.forEach(function(key) { + var item = this._items[key]; + if (this._hasExpired(item)) { + this.delete(key); + } + }, this); + + // Remove least used items + if (this._keys.length > maxSize) { + this._keys = this._keys.sort(function(a, b) { + return this._items[b].lastAccessed - this._items[a].lastAccessed; + }.bind(this)); + + while (this._keys.length > maxSize) { + var key = this._keys.pop(); + this.delete(key); + } + } +}; + +proto._hasExpired = function(item) { + return (Date.now() - item.lastAccessed) > this._maxAge; +}; + +module.exports = Cache; \ No newline at end of file diff --git a/node_modules/c9/cache_test.js b/node_modules/c9/cache_test.js new file mode 100644 index 00000000..446ce83a --- /dev/null +++ b/node_modules/c9/cache_test.js @@ -0,0 +1,188 @@ +"use strict"; +"use server"; + + +var assert = require("assert"); +var Cache = require("c9/cache"); + +require("c9/inline-mocha")(module); + +describe("Cache", function() { + var cache; + var maxSize = 5; + var age = 10; + + beforeEach(function() { + cache = new Cache(maxSize, age) ; + }); + + describe("set", function() { + it("should add the item to the cache", function(){ + var item = { a: 1 }; + var key = "foo/bar"; + + cache.set(key, item); + var cachedItem = cache.get(key); + assert.equal(cachedItem, item); + }); + it("should increment size", function() { + var item = { a: 1 }; + + cache.set("foo/bar", item); + cache.set("foo/baz", item); + cache.set("foo/bir", item); + cache.set("foo/biz", item); + cache.set("foo/bur", item); + + assert(cache.size(), 5); + }); + }); + + describe("get", function() { + it("should return item when called within age", function() { + var item = { a: 1 }; + var key = "foo/bar"; + + cache.set(key, item); + var cachedItem = cache.get(key); + assert.equal(cachedItem, item); + }); + it("should return `null` if item expired", function(done) { + var item = { a: 1 }; + var key = "foo/bar"; + + var cache = new Cache(maxSize, 0) ; + cache.set(key, item); + + // Do we use sinon to make this sync? + setTimeout(function() { + var cachedItem = cache.get(key); + assert.equal(cachedItem, null); + done(); + }, 0); + }); + }); + + describe("delete", function() { + it("should remove item from cache", function() { + var item = { a: 1 }; + var key = "foo/bar"; + + cache.set(key, item); + cache.delete(key); + var cachedItem = cache.get(key); + assert.equal(cachedItem, null); + }); + + it("should decrement size", function() { + var item = { a: 1 }; + var key = "foo/bar"; + + cache.set(key, item); + cache.delete(key); + assert.equal(cache.size(), 0); + }); + }); + + describe("purge", function() { + it("should not purge when maxSize is not reached", function() { + var item = { a: 1 }; + + var cache = new Cache(7) ; + + cache.set("foo/bar", item); + cache.set("foo/baz", item); + cache.set("foo/bir", item); + cache.set("foo/biz", item); + + assert.equal(cache.size(), 4); + }); + + it("should purge when maxSize is reached", function() { + var item = { a: 1 }; + + var cache = new Cache(10) ; + + cache.set("1", item); + cache.set("2", item); + cache.set("3", item); + cache.set("4", item); + cache.set("5", item); + cache.set("6", item); + cache.set("7", item); + cache.set("8", item); + cache.set("9", item); + cache.set("10", item); + cache.set("11", item); + + assert.equal(cache.size(), 7); + }); + + it("should purge the least recently used items", function(done) { + var item = { a: 1 }; + + var cache = new Cache(10, 0) ; + + cache.set("1", item); + cache.set("2", item); + cache.set("3", item); + cache.set("4", item); + cache.set("5", item); + cache.set("6", item); + cache.set("7", item); + cache.set("8", item); + cache.set("9", item); + cache.set("10", item); + + // New cache hits + setTimeout(function() { + cache.get("1"); + cache.get("2"); + cache.get("10"); + cache.get("4"); + cache.get("5"); + cache.get("6"); + + // Trigger purge + cache.set("11", item); + + var cachedItem = cache.get("7"); + assert.equal(cachedItem, null); + cachedItem = cache.get("8"); + assert.equal(cachedItem, null); + cachedItem = cache.get("9"); + assert.equal(cachedItem, null); + cachedItem = cache.get("3"); + assert.equal(cachedItem, null); + + done(); + }, 1); + }); + + it("should purge expired items", function(done) { + var item = { a: 1 }; + + var cache = new Cache(5, 0) ; + + cache.set("1", item); + cache.set("2", item); + cache.set("3", item); + cache.set("4", item); + + setTimeout(function() { + cache.set("5", item); + + var cachedItem = cache.get("1"); + assert.equal(cachedItem, null); + cachedItem = cache.get("2"); + assert.equal(cachedItem, null); + cachedItem = cache.get("3"); + assert.equal(cachedItem, null); + cachedItem = cache.get("4"); + assert.equal(cachedItem, null); + + done(); + }, 2); + }); + }); +}); \ No newline at end of file