Skip to content

Commit

Permalink
added Memcached plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Edward de Groot committed Mar 21, 2012
1 parent f9592d0 commit a3b8aa9
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 21 deletions.
4 changes: 3 additions & 1 deletion History.md
@@ -1,9 +1,11 @@
0.3.0 / 2011-10-03
0.3.0 / 2012-03-21
=======================

* Unstable experimental release
* Code has been completely refactored
* Supports plugins for cache storage (defaults to MemoryStorage)
* Added RedisStorage plugin
* Added MemcachedStorage plugin
* Switched to mocha testing framework

0.2.0 / 2011-10-03
Expand Down
18 changes: 18 additions & 0 deletions examples/multi-memcached.coffee
@@ -0,0 +1,18 @@
pantry = require '../src/pantry'
MemcachedStorage = require '../src/pantry-memcached'

delay = (ms, func) -> setTimeout func, ms

test = (id, sleep)->
pantry.fetch "http://app.canada.com/southparc/query.svc/content/#{id}?format=json", (error, item) ->
delay sleep, -> test id, sleep

pantry.configure { shelfLife: 10, maxLife: 30, caseSensitive: false, verbosity: 'DEBUG'}
pantry.storage = new MemcachedStorage 'localhost:11211', {}, 'DEBUG'

pantry.fetch "http://app.canada.com/southparc/query.svc/relatedcontent/764023?format=json", (error, list) ->
max = if list.length > 30 then 30 else list.length
for x in [0...max]
do (x) ->
sleep = (x + 1) * 500
delay sleep, -> test list[x].ID, sleep
2 changes: 1 addition & 1 deletion examples/multi-redis.coffee
Expand Up @@ -8,7 +8,7 @@ test = (id, sleep)->
delay sleep, -> test id, sleep

pantry.configure { shelfLife: 10, maxLife: 30, caseSensitive: false, verbosity: 'DEBUG'}
pantry.storage = new RedisStorage {verbosity: 'DEBUG'}
pantry.storage = new RedisStorage 6379, 'localhost', null, 'DEBUG'

pantry.fetch "http://app.canada.com/southparc/query.svc/relatedcontent/764023?format=json", (error, list) ->
max = if list.length > 30 then 30 else list.length
Expand Down
2 changes: 1 addition & 1 deletion examples/multi.coffee
@@ -1,7 +1,7 @@
pantry = require '../src/pantry'
MemoryStorage = require '../src/pantry-memory'

pantry.storage = new MemoryStorage({capacity: 18, ideal: 12, verbosity: 'DEBUG'})
pantry.storage = new MemoryStorage({capacity: 18, ideal: 12}, 'DEBUG')

delay = (ms, func) -> setTimeout func, ms

Expand Down
43 changes: 43 additions & 0 deletions src/pantry-memcached.coffee
@@ -0,0 +1,43 @@
Log = require 'coloured-log'
Memcached = require 'memcached'

module.exports = class MemcachedStorage
constructor: (servers = "localhost:11211", options = {}, verbosity = 'DEBUG') ->
# configure the log
@log = new Log(verbosity)

# connect to redis server
@client = new Memcached(servers, options)

@client.on 'issue', (details) ->
@log.warning details

@client.on 'failure', (details) ->
@log.error details

@client.on 'reconnecting', (details) ->
@log.notice details

@client.on 'reconnected', (details) ->
@log.info details

@client.on 'remove', (details) ->
@log.notice details

@log.notice "New memcached storage created"
@log.info "Servers: #{servers}"

# retrieve a specific resource
get: (key, callback) ->
@client.get key, (err, results) ->
if err then @log.error err
callback err, if err then null else JSON.parse(results)

# save a specific resource
put: (resource, callback) ->
@client.set resource.options.key, JSON.stringify(resource), resource.options.maxLife, (err, results) =>
if err then @log.error err
callback(err, results) if callback?

#allow chaining, mostly for testing
return @
6 changes: 3 additions & 3 deletions src/pantry-memory.coffee
Expand Up @@ -2,15 +2,15 @@ Log = require 'coloured-log'

module.exports = class MemoryStorage

constructor: (options = {}) ->
constructor: (options = {}, verbosity = 'ERROR') ->
# default configuration
@config = {capacity: 1000, verbosity: 'ERROR'}
@config = {capacity: 1000}

# update configuration and defaults
@config[k] = v for k, v of options

#configure the log
@log = new Log(@config.verbosity)
@log = new Log(verbosity)

# recalculate new ideal capacity (unless alternate and valid ideal has been specified)
@config.ideal = (@config.capacity * 0.9) unless options.ideal and @config.ideal <= (@config.capacity * 0.9)
Expand Down
23 changes: 10 additions & 13 deletions src/pantry-redis.coffee
Expand Up @@ -2,38 +2,35 @@ Log = require 'coloured-log'
redis = require 'redis'

module.exports = class RedisStorage
constructor: (options = {}) ->
# default configuration
@config = {host: 'localhost', port: 6379, auth: null, verbosity: 'ERROR'}

# update configuration and defaults
@config[k] = v for k, v of options

constructor: (port = 6379, host = 'localhost', options = {}, verbosity = 'ERROR') ->
# configure the log
@log = new Log(@config.verbosity)
@log = new Log(verbosity)

# connect to redis server
@client = redis.createClient(@config.port, @config.host)
@client = redis.createClient(port, host)

@client.on 'error', (err) =>
@log.error err

@client.on 'ready', =>
@log.notice "New redis storage created"
@log.info "Host: #{@config.host}, Port: #{@config.port}"
@log.info "Connected to Host: #{host}, Port: #{port}"

# optionally send password
@client.auth @config.auth if @config.auth?
@client.auth options.auth if options.auth?

@log.notice "New redis storage created"

# retrieve a specific resource
get: (key, callback) ->
@client.get key, (err, results) ->
@log.error err if err
callback err, if err then null else JSON.parse(results)

# save a specific resource
put: (resource, callback) ->
@client.set resource.options.key, JSON.stringify(resource), (err, results) =>
if err? or resource.options.maxLife is 0
if err?
@log.error err
callback(err, results) if callback?
else
#expire item from cache when spoiled (no need to wait)
Expand Down
8 changes: 6 additions & 2 deletions src/pantry.coffee
Expand Up @@ -148,9 +148,13 @@ inProgress = {} # holds requests in progress
resource.spoilsOn.setSeconds resource.spoilsOn.getSeconds() + resource.options.maxLife

# cache the results in storage for future use
@storage.put resource, (error) ->
# execute the registered callbacks
if resource.options.maxLife is 0
# this resource should not be cached
stock.emit 'done', error, resource.results
else
@storage.put resource, (error) ->
# execute the registered callbacks
stock.emit 'done', error, resource.results

# creates a unique and predicable key based on the requested uri
@generateKey = (options) ->
Expand Down
18 changes: 18 additions & 0 deletions test/pantry-memcached-test.coffee
@@ -0,0 +1,18 @@
should = require 'should'

Storage = require '../src/pantry-memcached'
MockResource = require '../mocks/resource-mock'

describe 'pantry-memcached', ->
describe 'get/put', ->
describe 'when adding an item to storage', ->
storage = new Storage
resource = new MockResource 'fresh', "Hello World #{new Date()}"
it 'should not return an error', (done) ->
storage.put resource, (err, results) ->
done err
it 'should be retrievable', (done) ->
storage.get resource.options.key, (err, item) ->
item.options.should.have.property 'key', resource.options.key
item.should.have.property 'results', resource.results
done err
2 changes: 2 additions & 0 deletions test/pantry-test.coffee
Expand Up @@ -12,6 +12,8 @@ describe 'pantry', ->

it 'should allow configuration overides', ->
config = pantry.configure { caseSensitive: false}
config.should.have.property 'shelfLife', 60
config.should.have.property 'maxLife', 300
config.should.have.property 'caseSensitive', false
config.should.have.property 'verbosity', 'ERROR'

Expand Down

0 comments on commit a3b8aa9

Please sign in to comment.