moneta 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Yehuda Katz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,51 @@
1
+ Moneta: A unified interface for key/value stores
2
+ ================================================
3
+
4
+ Moneta provides a standard interface for interacting with various kinds of key/value stores.
5
+
6
+ Out of the box, it supports:
7
+
8
+ * File store for xattr
9
+ * Basic File Store
10
+ * Memcache store
11
+ * In-memory store
12
+ * The xattrs in a file system
13
+ * DataMapper
14
+ * S3
15
+ * Berkeley DB
16
+ * Redis
17
+ * SDBM
18
+ * Tokyo
19
+ * CouchDB
20
+
21
+ All stores support key expiration, but only memcache supports it natively. All other stores
22
+ emulate expiration.
23
+
24
+ The Moneta API is purposely extremely similar to the Hash API. In order so support an
25
+ identical API across stores, it does not support iteration or partial matches, but that
26
+ might come in a future release.
27
+
28
+ The API:
29
+
30
+ #initialize(options):: options differs per-store, and is used to set up the store
31
+
32
+ #[](key):: retrieve a key. if the key is not available, return nil
33
+
34
+ #[]=(key, value):: set a value for a key. if the key is already used, clobber it.
35
+ keys set using []= will never expire
36
+
37
+ #delete(key):: delete the key from the store and return the current value
38
+
39
+ #key?(key):: true if the key exists, false if it does not
40
+
41
+ #has_key?(key):: alias for key?
42
+
43
+ #store(key, value, options):: same as []=, but you can supply an :expires_in option,
44
+ which will specify a number of seconds before the key
45
+ should expire. In order to support the same features
46
+ across all stores, only full seconds are supported
47
+
48
+ #update_key(key, options):: updates an existing key with a new :expires_in option.
49
+ if the key has already expired, it will not be updated.
50
+
51
+ #clear:: clear all keys in this store
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'spec/rake/spectask'
5
+ require 'date'
6
+
7
+ GEM = "moneta"
8
+ GEM_VERSION = "0.6.0"
9
+ AUTHOR = "Yehuda Katz"
10
+ EMAIL = "wycats@gmail.com"
11
+ HOMEPAGE = "http://www.yehudakatz.com"
12
+ SUMMARY = "A unified interface to key/value stores"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = GEM
16
+ s.version = GEM_VERSION
17
+ s.platform = Gem::Platform::RUBY
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
20
+ s.summary = SUMMARY
21
+ s.description = s.summary
22
+ s.author = AUTHOR
23
+ s.email = EMAIL
24
+ s.homepage = HOMEPAGE
25
+
26
+ # Uncomment this to add a dependency
27
+ # s.add_dependency "foo"
28
+
29
+ s.require_path = 'lib'
30
+ s.autorequire = GEM
31
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
32
+ end
33
+
34
+ Rake::GemPackageTask.new(spec) do |pkg|
35
+ pkg.gem_spec = spec
36
+ end
37
+
38
+ desc "install the gem locally"
39
+ task :install => [:package] do
40
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
41
+ end
42
+
43
+ desc "create a gemspec file"
44
+ task :make_spec do
45
+ File.open("#{GEM}.gemspec", "w") do |file|
46
+ file.puts spec.to_ruby
47
+ end
48
+ end
49
+
50
+ desc "Run all examples (or a specific spec with TASK=xxxx)"
51
+ Spec::Rake::SpecTask.new('spec') do |t|
52
+ t.spec_opts = ["-cfs"]
53
+ t.spec_files = begin
54
+ if ENV["TASK"]
55
+ ENV["TASK"].split(',').map { |task| "spec/**/#{task}_spec.rb" }
56
+ else
57
+ FileList['spec/**/*_spec.rb']
58
+ end
59
+ end
60
+ end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ TODO:
2
+ Fix LICENSE with your name
3
+ Fix Rakefile with your name and contact info
4
+ Add your code to lib/<%= name %>.rb
data/lib/moneta.rb ADDED
@@ -0,0 +1,76 @@
1
+ module Moneta
2
+ module Expires
3
+ def check_expired(key)
4
+ if @expiration[key] && Time.now > @expiration[key]
5
+ @expiration.delete(key)
6
+ self.delete(key)
7
+ end
8
+ end
9
+
10
+ def key?(key)
11
+ check_expired(key)
12
+ super
13
+ end
14
+
15
+ def [](key)
16
+ check_expired(key)
17
+ super
18
+ end
19
+
20
+ def fetch(key, default = nil, &blk)
21
+ check_expired(key)
22
+ super
23
+ end
24
+
25
+ def delete(key)
26
+ check_expired(key)
27
+ super
28
+ end
29
+
30
+ def update_key(key, options)
31
+ update_options(key, options)
32
+ end
33
+
34
+ def store(key, value, options = {})
35
+ ret = super(key, value)
36
+ update_options(key, options)
37
+ ret
38
+ end
39
+
40
+ private
41
+ def update_options(key, options)
42
+ if options[:expires_in]
43
+ @expiration[key] = (Time.now + options[:expires_in])
44
+ end
45
+ end
46
+ end
47
+
48
+ module StringExpires
49
+ include Expires
50
+
51
+ def check_expired(key)
52
+ if @expiration[key] && Time.now > Time.at(@expiration[key].to_i)
53
+ @expiration.delete(key)
54
+ delete(key)
55
+ end
56
+ end
57
+
58
+ private
59
+ def update_options(key, options)
60
+ if options[:expires_in]
61
+ @expiration[key] = (Time.now + options[:expires_in]).to_i.to_s
62
+ end
63
+ end
64
+ end
65
+
66
+ module Defaults
67
+ def fetch(key, value = nil)
68
+ value ||= block_given? ? yield(key) : default
69
+ self[key] || value
70
+ end
71
+
72
+ def store(key, value, options = {})
73
+ self[key] = value
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,111 @@
1
+ #
2
+ # Basic File Store
3
+ # by Hampton Catlin
4
+ #
5
+ # This cache simply uses a directory that it creates
6
+ # and manages to keep your file stores.
7
+ #
8
+ # Specify :skip_expires => true if you aren't using
9
+ # expiration as this will slightly decrease your file size
10
+ # and memory footprint of the library
11
+ #
12
+ # You can optionally also specify a :namespace
13
+ # option that will create a subfolder.
14
+ #
15
+
16
+
17
+ require 'fileutils'
18
+ require File.join(File.dirname(__FILE__), "..", "moneta.rb")
19
+
20
+ module Moneta
21
+ class BasicFile
22
+ include Defaults
23
+
24
+ def initialize(options = {})
25
+ @namespace = options[:namespace]
26
+ @directory = ::File.join(options[:path], @namespace.to_s)
27
+
28
+ @expires = !options[:skip_expires]
29
+
30
+ ensure_directory_created(@directory)
31
+ end
32
+
33
+ def key?(key)
34
+ !self[key].nil?
35
+ end
36
+
37
+ alias has_key? key?
38
+
39
+ def [](key)
40
+ if ::File.exist?(path(key))
41
+ data = raw_get(key)
42
+ if @expires
43
+ if data[:expires_at].nil? || data[:expires_at] > Time.now
44
+ data[:value]
45
+ else
46
+ delete!(key)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def raw_get(key)
53
+ Marshal.load(::File.read(path(key)))
54
+ end
55
+
56
+ def []=(key, value)
57
+ store(key, value)
58
+ end
59
+
60
+ def store(key, value, options = {})
61
+ ensure_directory_created(::File.dirname(path(key)))
62
+ ::File.open(path(key), "w") do |file|
63
+ if @expires
64
+ data = {:value => value}
65
+ if options[:expires_in]
66
+ data[:expires_at] = Time.now + options[:expires_in]
67
+ end
68
+ contents = Marshal.dump(data)
69
+ else
70
+ contents = Marshal.dump(value)
71
+ end
72
+ file.puts(contents)
73
+ end
74
+ end
75
+
76
+ def update_key(key, options)
77
+ store(key, self[key], options)
78
+ end
79
+
80
+ def delete!(key)
81
+ FileUtils.rm(path(key))
82
+ nil
83
+ rescue Errno::ENOENT
84
+ end
85
+
86
+ def delete(key)
87
+ value = self[key]
88
+ delete!(key)
89
+ value
90
+ end
91
+
92
+ def clear
93
+ FileUtils.rm_rf(@directory)
94
+ FileUtils.mkdir(@directory)
95
+ end
96
+
97
+ private
98
+ def path(key)
99
+ ::File.join(@directory, key.to_s)
100
+ end
101
+
102
+ def ensure_directory_created(directory_path)
103
+ if ::File.file?(directory_path)
104
+ raise StandardError, "The path you supplied #{directory_path} is a file"
105
+ elsif !::File.exists?(directory_path)
106
+ FileUtils.mkdir_p(directory_path)
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,53 @@
1
+ begin
2
+ require 'bdb'
3
+ rescue LoadError
4
+ puts "You need bdb gem to use Bdb moneta store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+
10
+ class Berkeley
11
+ include Defaults
12
+
13
+ def initialize(options={})
14
+ file = @file = options[:file]
15
+ @db = Bdb::Db.new()
16
+ @db.open(nil, file, nil, Bdb::Db::BTREE, Bdb::DB_CREATE, 0)
17
+ unless options[:skip_expires]
18
+ @expiration = Moneta::Berkeley.new(:file => "#{file}_expiration", :skip_expires => true )
19
+ self.extend(StringExpires)
20
+ end
21
+ end
22
+
23
+ module Implementation
24
+ def key?(key)
25
+ nil | self[key]
26
+ end
27
+
28
+ alias has_key? key?
29
+
30
+ def []=(key,value)
31
+ @db[key] = value
32
+ end
33
+
34
+ def [](key)
35
+ @db[key]
36
+ end
37
+
38
+ def delete(key)
39
+ value = self[key]
40
+ @db.del(nil,key,0) if value
41
+ value
42
+ end
43
+
44
+ def clear
45
+ @db.truncate(nil)
46
+ end
47
+ end
48
+
49
+ include Implementation
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,63 @@
1
+ begin
2
+ require "couchrest"
3
+ rescue LoadError
4
+ puts "You need the couchrest gem to use the CouchDB store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ class Couch
10
+ include Defaults
11
+
12
+ def initialize(options = {})
13
+ @db = ::CouchRest.database!(options[:db])
14
+ unless options[:skip_expires]
15
+ @expiration = Moneta::Couch.new(:db => "#{options[:db]}_expiration", :skip_expires => true)
16
+ self.extend(StringExpires)
17
+ end
18
+ end
19
+
20
+ def key?(key)
21
+ !self[key].nil?
22
+ rescue RestClient::ResourceNotFound
23
+ false
24
+ end
25
+
26
+ alias has_key? key?
27
+
28
+ def [](key)
29
+ @db.get(key)["data"]
30
+ rescue RestClient::ResourceNotFound
31
+ nil
32
+ end
33
+
34
+ def []=(key, value)
35
+ @db.save_doc("_id" => key, :data => value)
36
+ rescue RestClient::RequestFailed
37
+ self[key]
38
+ end
39
+
40
+ def delete(key)
41
+ value = @db.get(key)
42
+ @db.delete_doc({"_id" => value["_id"], "_rev" => value["_rev"]}) if value
43
+ value["data"]
44
+ rescue RestClient::ResourceNotFound
45
+ nil
46
+ end
47
+
48
+ def update_key(key, options = {})
49
+ val = self[key]
50
+ self.store(key, val, options)
51
+ rescue RestClient::ResourceNotFound
52
+ nil
53
+ end
54
+
55
+ def clear
56
+ @db.recreate!
57
+ end
58
+
59
+ def delete_store
60
+ @db.delete!
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,117 @@
1
+ begin
2
+ gem "dm-core", "0.9.10"
3
+ require "dm-core"
4
+ rescue LoadError
5
+ puts "You need the dm-core gem in order to use the DataMapper moneta store"
6
+ exit
7
+ end
8
+
9
+ class MonetaHash
10
+ include DataMapper::Resource
11
+
12
+ property :the_key, String, :key => true
13
+ property :value, Object, :lazy => false
14
+ property :expires, Time
15
+
16
+ def self.value(key)
17
+ obj = self.get(key)
18
+ obj && obj.value
19
+ end
20
+ end
21
+
22
+ module Moneta
23
+ class DataMapper
24
+ class Expiration
25
+ def initialize(klass, repository)
26
+ @klass = klass
27
+ @repository = repository
28
+ end
29
+
30
+ def [](key)
31
+ if obj = get(key)
32
+ obj.expires
33
+ end
34
+ end
35
+
36
+ def []=(key, value)
37
+ obj = get(key)
38
+ obj.expires = value
39
+ obj.save(@repository)
40
+ end
41
+
42
+ def delete(key)
43
+ obj = get(key)
44
+ obj.expires = nil
45
+ obj.save(@repository)
46
+ end
47
+
48
+ private
49
+ def get(key)
50
+ repository(@repository) { @klass.get(key) }
51
+ end
52
+ end
53
+
54
+ def initialize(options = {})
55
+ @repository = options.delete(:repository) || :moneta
56
+ ::DataMapper.setup(@repository, options[:setup])
57
+ repository_context { MonetaHash.auto_upgrade! }
58
+ @hash = MonetaHash
59
+ @expiration = Expiration.new(MonetaHash, @repository)
60
+ end
61
+
62
+ module Implementation
63
+ def key?(key)
64
+ repository_context { !!@hash.get(key) }
65
+ end
66
+
67
+ def has_key?(key)
68
+ repository_context { !!@hash.get(key) }
69
+ end
70
+
71
+ def [](key)
72
+ repository_context { @hash.value(key) }
73
+ end
74
+
75
+ def []=(key, value)
76
+ repository_context {
77
+ obj = @hash.get(key)
78
+ if obj
79
+ obj.update(key, value)
80
+ else
81
+ @hash.create(:the_key => key, :value => value)
82
+ end
83
+ }
84
+ end
85
+
86
+ def fetch(key, value = nil)
87
+ repository_context {
88
+ value ||= block_given? ? yield(key) : default
89
+ self[key] || value
90
+ }
91
+ end
92
+
93
+ def delete(key)
94
+ repository_context {
95
+ value = self[key]
96
+ @hash.all(:the_key => key).destroy!
97
+ value
98
+ }
99
+ end
100
+
101
+ def store(key, value, options = {})
102
+ repository_context { self[key] = value }
103
+ end
104
+
105
+ def clear
106
+ repository_context { @hash.all.destroy! }
107
+ end
108
+
109
+ private
110
+ def repository_context
111
+ repository(@repository) { yield }
112
+ end
113
+ end
114
+ include Implementation
115
+ include Expires
116
+ end
117
+ end
@@ -0,0 +1,91 @@
1
+ begin
2
+ require "xattr"
3
+ rescue LoadError
4
+ puts "You need the xattr gem to use the File moneta store"
5
+ exit
6
+ end
7
+ require "fileutils"
8
+
9
+ module Moneta
10
+ class File
11
+ class Expiration
12
+ def initialize(directory)
13
+ @directory = directory
14
+ end
15
+
16
+ def [](key)
17
+ attrs = xattr(key)
18
+ ret = Marshal.load(attrs.get("moneta_expires"))
19
+ rescue Errno::ENOENT, SystemCallError
20
+ end
21
+
22
+ def []=(key, value)
23
+ attrs = xattr(key)
24
+ attrs.set("moneta_expires", Marshal.dump(value))
25
+ end
26
+
27
+ def delete(key)
28
+ attrs = xattr(key)
29
+ attrs.remove("moneta_expires")
30
+ end
31
+
32
+ private
33
+ def xattr(key)
34
+ ::Xattr.new(::File.join(@directory, key))
35
+ end
36
+ end
37
+
38
+ def initialize(options = {})
39
+ @directory = options[:path]
40
+ if ::File.file?(@directory)
41
+ raise StandardError, "The path you supplied #{@directory} is a file"
42
+ elsif !::File.exists?(@directory)
43
+ FileUtils.mkdir_p(@directory)
44
+ end
45
+
46
+ @expiration = Expiration.new(@directory)
47
+ end
48
+
49
+ module Implementation
50
+ def key?(key)
51
+ ::File.exist?(path(key))
52
+ end
53
+
54
+ alias has_key? key?
55
+
56
+ def [](key)
57
+ if ::File.exist?(path(key))
58
+ Marshal.load(::File.read(path(key)))
59
+ end
60
+ end
61
+
62
+ def []=(key, value)
63
+ ::File.open(path(key), "w") do |file|
64
+ contents = Marshal.dump(value)
65
+ file.puts(contents)
66
+ end
67
+ end
68
+
69
+ def delete(key)
70
+ value = self[key]
71
+ FileUtils.rm(path(key))
72
+ value
73
+ rescue Errno::ENOENT
74
+ end
75
+
76
+ def clear
77
+ FileUtils.rm_rf(@directory)
78
+ FileUtils.mkdir(@directory)
79
+ end
80
+
81
+ private
82
+ def path(key)
83
+ ::File.join(@directory, key.to_s)
84
+ end
85
+ end
86
+ include Implementation
87
+ include Defaults
88
+ include Expires
89
+
90
+ end
91
+ end
data/lib/moneta/lmc.rb ADDED
@@ -0,0 +1,52 @@
1
+ begin
2
+ require "localmemcache"
3
+ rescue LoadError
4
+ puts "You need the localmemcache gem to use the LMC moneta store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ class Expiration
10
+ def initialize(hash)
11
+ @hash = hash
12
+ end
13
+
14
+ def [](key) @hash["#{key}__!__expiration"] end
15
+ def []=(key, value) @hash["#{key}__!__expiration"] = value end
16
+
17
+ def delete(key)
18
+ key = "#{key}__!__expiration"
19
+ value = @hash[key]
20
+ @hash.delete(key)
21
+ value
22
+ end
23
+ end
24
+
25
+ class LMC
26
+ include Defaults
27
+
28
+ module Implementation
29
+ def initialize(options = {})
30
+ @hash = LocalMemCache.new(:filename => options[:filename])
31
+ @expiration = Expiration.new(@hash)
32
+ end
33
+
34
+ def [](key) @hash[key] end
35
+ def []=(key, value) @hash[key] = value end
36
+ def clear() @hash.clear end
37
+
38
+ def key?(key)
39
+ @hash.keys.include?(key)
40
+ end
41
+
42
+ def delete(key)
43
+ value = @hash[key]
44
+ @hash.delete(key)
45
+ value
46
+ end
47
+ end
48
+ include Implementation
49
+ include StringExpires
50
+
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ begin
2
+ require "memcached"
3
+ rescue LoadError
4
+ require "memcache"
5
+ rescue LoadError
6
+ puts "You need the memcache gem to use the Memcache moneta store"
7
+ exit
8
+ end
9
+
10
+ module Moneta
11
+ class Memcache
12
+ include Defaults
13
+
14
+ def initialize(options = {})
15
+ @cache = MemCache.new(options.delete(:server), options)
16
+ end
17
+
18
+ def key?(key)
19
+ !self[key].nil?
20
+ end
21
+
22
+ alias has_key? key?
23
+
24
+ def [](key)
25
+ @cache.get(key)
26
+ end
27
+
28
+ def []=(key, value)
29
+ store(key, value)
30
+ end
31
+
32
+ def delete(key)
33
+ value = self[key]
34
+ @cache.delete(key) if value
35
+ value
36
+ end
37
+
38
+ def store(key, value, options = {})
39
+ args = [key, value, options[:expires_in]].compact
40
+ @cache.set(*args)
41
+ end
42
+
43
+ def update_key(key, options = {})
44
+ val = self[key]
45
+ self.store(key, val, options)
46
+ end
47
+
48
+ def clear
49
+ @cache.flush_all
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ module Moneta
2
+ class Memory < Hash
3
+ include Expires
4
+
5
+ def initialize(*args)
6
+ @expiration = {}
7
+ super
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ begin
2
+ require "mongo"
3
+ rescue LoadError
4
+ puts "You need the mongo gem to use the MongoDB moneta store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ class MongoDB
10
+ include Defaults
11
+
12
+ def initialize(options = {})
13
+ options = {
14
+ :host => ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
15
+ :port => ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT,
16
+ :db => 'cache',
17
+ :collection => 'cache'
18
+ }.update(options)
19
+ conn = XGen::Mongo::Driver::Mongo.new(options[:host], options[:port])
20
+ @cache = conn.db(options[:db]).collection(options[:collection])
21
+ end
22
+
23
+ def key?(key)
24
+ !!self[key]
25
+ end
26
+
27
+ def [](key)
28
+ res = @cache.find_first('_id' => key)
29
+ res = nil if res && res['expires'] && Time.now > res['expires']
30
+ res && res['data']
31
+ end
32
+
33
+ def []=(key, value)
34
+ store(key, value)
35
+ end
36
+
37
+ def delete(key)
38
+ value = self[key]
39
+ @cache.remove('_id' => key) if value
40
+ value
41
+ end
42
+
43
+ def store(key, value, options = {})
44
+ exp = options[:expires_in] ? (Time.now + options[:expires_in]) : nil
45
+ @cache.repsert({ '_id' => key }, { '_id' => key, 'data' => value, 'expires' => exp })
46
+ end
47
+
48
+ def update_key(key, options = {})
49
+ val = self[key]
50
+ self.store(key, val, options)
51
+ end
52
+
53
+ def clear
54
+ @cache.clear
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,49 @@
1
+ begin
2
+ require "redis"
3
+ rescue LoadError
4
+ puts "You need the redis gem to use the Redis store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ class Redis
10
+ include Defaults
11
+
12
+ def initialize(options = {})
13
+ @cache = ::Redis.new(options)
14
+ end
15
+
16
+ def key?(key)
17
+ !@cache[key].nil?
18
+ end
19
+
20
+ alias has_key? key?
21
+
22
+ def [](key)
23
+ @cache.get(key)
24
+ end
25
+
26
+ def []=(key, value)
27
+ store(key, value)
28
+ end
29
+
30
+ def delete(key)
31
+ value = @cache[key]
32
+ @cache.delete(key) if value
33
+ value
34
+ end
35
+
36
+ def store(key, value, options = {})
37
+ @cache.set(key, value, options[:expires_in])
38
+ end
39
+
40
+ def update_key(key, options = {})
41
+ val = @cache[key]
42
+ self.store(key, val, options)
43
+ end
44
+
45
+ def clear
46
+ @cache.flush_db
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ begin
2
+ require "rufus/tokyo"
3
+ rescue LoadError
4
+ puts "You need the rufus gem to use the Rufus moneta store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ class BasicRufus < ::Rufus::Tokyo::Cabinet
10
+ include Defaults
11
+
12
+ def initialize(options = {})
13
+ file = options[:file]
14
+ super("#{file}.tch")
15
+ end
16
+
17
+ def key?(key)
18
+ !!self[key]
19
+ end
20
+
21
+ def [](key)
22
+ if val = super
23
+ Marshal.load(val.unpack("m")[0])
24
+ end
25
+ end
26
+
27
+ def []=(key, value)
28
+ super(key, [Marshal.dump(value)].pack("m"))
29
+ end
30
+ end
31
+
32
+ class Rufus < BasicRufus
33
+ include Expires
34
+
35
+ def initialize(options = {})
36
+ file = options[:file]
37
+ @expiration = BasicRufus.new(:file => "#{file}_expires")
38
+ super
39
+ end
40
+ end
41
+ end
data/lib/moneta/s3.rb ADDED
@@ -0,0 +1,162 @@
1
+ begin
2
+ require "right_aws"
3
+ rescue LoadError
4
+ puts "You need the RightScale AWS gem to use the S3 moneta store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ # An S3 implementation of Moneta
10
+ #
11
+ # Example usage:
12
+ #
13
+ # require 'rubygems'
14
+ # require 'moneta'
15
+ # require 'moneta/s3'
16
+ #
17
+ # store = Moneta::S3.new(
18
+ # :access_key_id => 'ACCESS_KEY_ID',
19
+ # :secret_access_key => 'SECRET_ACCESS_KEY',
20
+ # :bucket => 'a_bucket'
21
+ # )
22
+ # store['somefile']
23
+ class S3
24
+ # Initialize the Moneta::S3 store.
25
+ #
26
+ # Required values passed in the options hash:
27
+ # * <tt>:access_key_id</tt>: The access id key
28
+ # * <tt>:secret_access_key</tt>: The secret key
29
+ # * <tt>:bucket</tt>: The name of bucket. Will be created if it doesn't
30
+ # exist.
31
+ # * <tt>:multi_thread</tt>: Set to true if using threading
32
+ def initialize(options = {})
33
+ validate_options(options)
34
+ s3 = RightAws::S3.new(
35
+ options[:access_key_id],
36
+ options[:secret_access_key],
37
+ {
38
+ :logger => logger,
39
+ :multi_thread => options.delete(:multi_thread) || false
40
+ }
41
+ )
42
+ @bucket = s3.bucket(options.delete(:bucket), true)
43
+ end
44
+
45
+ def key?(key)
46
+ !s3_key(key).nil?
47
+ end
48
+
49
+ alias has_key? key?
50
+
51
+ def [](key)
52
+ get(key)
53
+ end
54
+
55
+ def []=(key, value)
56
+ store(key, value)
57
+ end
58
+
59
+ def delete(key)
60
+ k = s3_key(key)
61
+ if k
62
+ value = k.get
63
+ k.delete
64
+ value
65
+ end
66
+ end
67
+
68
+ # Store the key/value pair.
69
+ #
70
+ # Options:
71
+ # *<tt>:meta_headers</tt>: Meta headers passed to S3
72
+ # *<tt>:perms</tt>: Permissions passed to S3
73
+ # *<tt>:headers</tt>: Headers sent as part of the PUT request
74
+ # *<tt>:expires_in</tt>: Number of seconds until expiration
75
+ def store(key, value, options = {})
76
+ debug "store(key=#{key}, value=#{value}, options=#{options.inspect})"
77
+ meta_headers = meta_headers_from_options(options)
78
+ perms = options[:perms]
79
+ headers = options[:headers] || {}
80
+
81
+ case value
82
+ when IO
83
+ @bucket.put(key, value.read, meta_headers, perms, headers)
84
+ else
85
+ @bucket.put(key, value, meta_headers, perms, headers)
86
+ end
87
+ end
88
+
89
+ def update_key(key, options = {})
90
+ debug "update_key(key=#{key}, options=#{options.inspect})"
91
+ k = s3_key(key, false)
92
+ k.save_meta(meta_headers_from_options(options)) unless k.nil?
93
+ end
94
+
95
+ def clear
96
+ @bucket.clear
97
+ end
98
+
99
+ protected
100
+ def logger
101
+ @logger ||= begin
102
+ logger = Logger.new(STDOUT)
103
+ logger.level = Logger::FATAL
104
+ logger
105
+ end
106
+ end
107
+
108
+ private
109
+ def validate_options(options)
110
+ unless options[:access_key_id]
111
+ raise RuntimeError, ":access_key_id is required in options"
112
+ end
113
+ unless options[:secret_access_key]
114
+ raise RuntimeError, ":secret_access_key is required in options"
115
+ end
116
+ unless options[:bucket]
117
+ raise RuntimeError, ":bucket is required in options"
118
+ end
119
+ end
120
+
121
+ def get(key)
122
+ k = s3_key(key)
123
+ k.nil? ? nil : k.get
124
+ end
125
+
126
+ def s3_key(key, nil_if_expired=true)
127
+ begin
128
+ s3_key = @bucket.key(key, true)
129
+ if s3_key.exists?
130
+ logger.debug "[Moneta::S3] key exists: #{key}"
131
+ if s3_key.meta_headers.has_key?('expires-at')
132
+ expires_at = Time.parse(s3_key.meta_headers['expires-at'])
133
+ if Time.now > expires_at && nil_if_expired
134
+ # TODO delete the object?
135
+ debug "key expired: #{key} (@#{s3_key.meta_headers['expires-at']})"
136
+ return nil
137
+ end
138
+ end
139
+ return s3_key
140
+ else
141
+ debug "key does not exist: #{key}"
142
+ end
143
+ rescue RightAws::AwsError => e
144
+ debug "key does not exist: #{key}"
145
+ end
146
+ nil
147
+ end
148
+
149
+ def meta_headers_from_options(options={})
150
+ meta_headers = options[:meta_headers] || {}
151
+ if options[:expires_in]
152
+ meta_headers['expires-at'] = (Time.now + options[:expires_in]).rfc2822
153
+ end
154
+ debug "setting expires-at: #{meta_headers['expires-at']}"
155
+ meta_headers
156
+ end
157
+
158
+ def debug(message)
159
+ logger.debug "[Moneta::S3] #{message}"
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,33 @@
1
+ require "sdbm"
2
+
3
+ module Moneta
4
+ class BasicSDBM < ::SDBM
5
+ include Defaults
6
+
7
+ def [](key)
8
+ if val = super
9
+ Marshal.load(val)
10
+ end
11
+ end
12
+
13
+ def []=(key, value)
14
+ super(key, Marshal.dump(value))
15
+ end
16
+
17
+ def delete(key)
18
+ if val = super
19
+ Marshal.load(val)
20
+ end
21
+ end
22
+ end
23
+
24
+ class SDBM < BasicSDBM
25
+ include Expires
26
+
27
+ def initialize(options = {})
28
+ raise "No :file option specified" unless file = options[:file]
29
+ @expiration = BasicSDBM.new("#{file}_expires")
30
+ super(file)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ begin
2
+ require "rufus/tokyo/tyrant"
3
+ rescue LoadError
4
+ puts "You need the rufus gem to use the Tyrant moneta store"
5
+ exit
6
+ end
7
+
8
+ module Moneta
9
+ class Tyrant < ::Rufus::Tokyo::Tyrant
10
+ include Defaults
11
+
12
+ module Implementation
13
+ def initialize(options = {})
14
+ host = options[:host]
15
+ port = options[:port]
16
+ super(host, port)
17
+ end
18
+
19
+ def key?(key)
20
+ !!self[key]
21
+ end
22
+
23
+ def [](key)
24
+ if val = super
25
+ Marshal.load(val.unpack("m")[0])
26
+ end
27
+ end
28
+
29
+ def []=(key, value)
30
+ super(key, [Marshal.dump(value)].pack("m"))
31
+ end
32
+ end
33
+
34
+ include Implementation
35
+ include Expires
36
+
37
+ def initialize(options = {})
38
+ super
39
+ @expiration = Expiration.new(options)
40
+ end
41
+
42
+ class Expiration < ::Rufus::Tokyo::Tyrant
43
+ include Implementation
44
+
45
+ def [](key)
46
+ super("#{key}__expiration")
47
+ end
48
+
49
+ def []=(key, value)
50
+ super("#{key}__expiration", value)
51
+ end
52
+
53
+ def delete(key)
54
+ super("#{key}__expiration")
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ begin
2
+ require "xattr"
3
+ rescue LoadError
4
+ puts "You need the xattr gem to use the Xattr moneta store"
5
+ exit
6
+ end
7
+ require "fileutils"
8
+
9
+ module Moneta
10
+ class Xattr
11
+ include Defaults
12
+
13
+ def initialize(options = {})
14
+ file = options[:file]
15
+ @hash = ::Xattr.new(file)
16
+ FileUtils.mkdir_p(::File.dirname(file))
17
+ FileUtils.touch(file)
18
+ unless options[:skip_expires]
19
+ @expiration = Moneta::Xattr.new(:file => "#{file}_expiration", :skip_expires => true)
20
+ self.extend(Expires)
21
+ end
22
+ end
23
+
24
+ module Implementation
25
+
26
+ def key?(key)
27
+ @hash.list.include?(key)
28
+ end
29
+
30
+ alias has_key? key?
31
+
32
+ def [](key)
33
+ return nil unless key?(key)
34
+ Marshal.load(@hash.get(key))
35
+ end
36
+
37
+ def []=(key, value)
38
+ @hash.set(key, Marshal.dump(value))
39
+ end
40
+
41
+ def delete(key)
42
+ return nil unless key?(key)
43
+ value = self[key]
44
+ @hash.remove(key)
45
+ value
46
+ end
47
+
48
+ def clear
49
+ @hash.list.each do |item|
50
+ @hash.remove(item)
51
+ end
52
+ end
53
+
54
+ end
55
+ include Implementation
56
+
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moneta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Yehuda Katz
8
+ autorequire: moneta
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-25 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A unified interface to key/value stores
17
+ email: wycats@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - LICENSE
25
+ - TODO
26
+ files:
27
+ - LICENSE
28
+ - README
29
+ - Rakefile
30
+ - TODO
31
+ - lib/moneta/basic_file.rb
32
+ - lib/moneta/berkeley.rb
33
+ - lib/moneta/couch.rb
34
+ - lib/moneta/datamapper.rb
35
+ - lib/moneta/file.rb
36
+ - lib/moneta/lmc.rb
37
+ - lib/moneta/memcache.rb
38
+ - lib/moneta/memory.rb
39
+ - lib/moneta/mongodb.rb
40
+ - lib/moneta/redis.rb
41
+ - lib/moneta/rufus.rb
42
+ - lib/moneta/s3.rb
43
+ - lib/moneta/sdbm.rb
44
+ - lib/moneta/tyrant.rb
45
+ - lib/moneta/xattr.rb
46
+ - lib/moneta.rb
47
+ has_rdoc: true
48
+ homepage: http://www.yehudakatz.com
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.4
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: A unified interface to key/value stores
75
+ test_files: []
76
+