aeden-moneta 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +50 -0
- data/Rakefile +60 -0
- data/TODO +4 -0
- data/lib/moneta.rb +76 -0
- data/lib/moneta/berkeley.rb +53 -0
- data/lib/moneta/couch.rb +63 -0
- data/lib/moneta/datamapper.rb +117 -0
- data/lib/moneta/file.rb +91 -0
- data/lib/moneta/lmc.rb +52 -0
- data/lib/moneta/memcache.rb +52 -0
- data/lib/moneta/memory.rb +11 -0
- data/lib/moneta/mongodb.rb +58 -0
- data/lib/moneta/redis.rb +49 -0
- data/lib/moneta/rufus.rb +41 -0
- data/lib/moneta/s3.rb +164 -0
- data/lib/moneta/sdbm.rb +33 -0
- data/lib/moneta/tyrant.rb +58 -0
- data/lib/moneta/xattr.rb +58 -0
- metadata +74 -0
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,50 @@
|
|
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
|
9
|
+
* Memcache store
|
10
|
+
* In-memory store
|
11
|
+
* The xattrs in a file system
|
12
|
+
* DataMapper
|
13
|
+
* S3
|
14
|
+
* Berkeley DB
|
15
|
+
* Redis
|
16
|
+
* SDBM
|
17
|
+
* Tokyo
|
18
|
+
* CouchDB
|
19
|
+
|
20
|
+
All stores support key expiration, but only memcache supports it natively. All other stores
|
21
|
+
emulate expiration.
|
22
|
+
|
23
|
+
The Moneta API is purposely extremely similar to the Hash API. In order so support an
|
24
|
+
identical API across stores, it does not support iteration or partial matches, but that
|
25
|
+
might come in a future release.
|
26
|
+
|
27
|
+
The API:
|
28
|
+
|
29
|
+
#initialize(options):: options differs per-store, and is used to set up the store
|
30
|
+
|
31
|
+
#[](key):: retrieve a key. if the key is not available, return nil
|
32
|
+
|
33
|
+
#[]=(key, value):: set a value for a key. if the key is already used, clobber it.
|
34
|
+
keys set using []= will never expire
|
35
|
+
|
36
|
+
#delete(key):: delete the key from the store and return the current value
|
37
|
+
|
38
|
+
#key?(key):: true if the key exists, false if it does not
|
39
|
+
|
40
|
+
#has_key?(key):: alias for key?
|
41
|
+
|
42
|
+
#store(key, value, options):: same as []=, but you can supply an :expires_in option,
|
43
|
+
which will specify a number of seconds before the key
|
44
|
+
should expire. In order to support the same features
|
45
|
+
across all stores, only full seconds are supported
|
46
|
+
|
47
|
+
#update_key(key, options):: updates an existing key with a new :expires_in option.
|
48
|
+
if the key has already expired, it will not be updated.
|
49
|
+
|
50
|
+
#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.2"
|
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
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,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
|
data/lib/moneta/couch.rb
ADDED
@@ -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
|
data/lib/moneta/file.rb
ADDED
@@ -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)
|
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,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
|
+
|
data/lib/moneta/redis.rb
ADDED
@@ -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
|
data/lib/moneta/rufus.rb
ADDED
@@ -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,164 @@
|
|
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
|
+
include Defaults
|
25
|
+
|
26
|
+
# Initialize the Moneta::S3 store.
|
27
|
+
#
|
28
|
+
# Required values passed in the options hash:
|
29
|
+
# * <tt>:access_key_id</tt>: The access id key
|
30
|
+
# * <tt>:secret_access_key</tt>: The secret key
|
31
|
+
# * <tt>:bucket</tt>: The name of bucket. Will be created if it doesn't
|
32
|
+
# exist.
|
33
|
+
# * <tt>:multi_thread</tt>: Set to true if using threading
|
34
|
+
def initialize(options = {})
|
35
|
+
validate_options(options)
|
36
|
+
s3 = RightAws::S3.new(
|
37
|
+
options[:access_key_id],
|
38
|
+
options[:secret_access_key],
|
39
|
+
{
|
40
|
+
:logger => logger,
|
41
|
+
:multi_thread => options.delete(:multi_thread) || false
|
42
|
+
}
|
43
|
+
)
|
44
|
+
@bucket = s3.bucket(options.delete(:bucket), true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def key?(key)
|
48
|
+
!s3_key(key).nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
alias has_key? key?
|
52
|
+
|
53
|
+
def [](key)
|
54
|
+
get(key)
|
55
|
+
end
|
56
|
+
|
57
|
+
def []=(key, value)
|
58
|
+
store(key, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete(key)
|
62
|
+
k = s3_key(key)
|
63
|
+
if k
|
64
|
+
value = k.get
|
65
|
+
k.delete
|
66
|
+
value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Store the key/value pair.
|
71
|
+
#
|
72
|
+
# Options:
|
73
|
+
# *<tt>:meta_headers</tt>: Meta headers passed to S3
|
74
|
+
# *<tt>:perms</tt>: Permissions passed to S3
|
75
|
+
# *<tt>:headers</tt>: Headers sent as part of the PUT request
|
76
|
+
# *<tt>:expires_in</tt>: Number of seconds until expiration
|
77
|
+
def store(key, value, options = {})
|
78
|
+
debug "store(key=#{key}, value=#{value}, options=#{options.inspect})"
|
79
|
+
meta_headers = meta_headers_from_options(options)
|
80
|
+
perms = options[:perms]
|
81
|
+
headers = options[:headers] || {}
|
82
|
+
|
83
|
+
case value
|
84
|
+
when IO
|
85
|
+
@bucket.put(key, value.read, meta_headers, perms, headers)
|
86
|
+
else
|
87
|
+
@bucket.put(key, value, meta_headers, perms, headers)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_key(key, options = {})
|
92
|
+
debug "update_key(key=#{key}, options=#{options.inspect})"
|
93
|
+
k = s3_key(key, false)
|
94
|
+
k.save_meta(meta_headers_from_options(options)) unless k.nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
def clear
|
98
|
+
@bucket.clear
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
def logger
|
103
|
+
@logger ||= begin
|
104
|
+
logger = Logger.new(STDOUT)
|
105
|
+
logger.level = Logger::FATAL
|
106
|
+
logger
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def validate_options(options)
|
112
|
+
unless options[:access_key_id]
|
113
|
+
raise RuntimeError, ":access_key_id is required in options"
|
114
|
+
end
|
115
|
+
unless options[:secret_access_key]
|
116
|
+
raise RuntimeError, ":secret_access_key is required in options"
|
117
|
+
end
|
118
|
+
unless options[:bucket]
|
119
|
+
raise RuntimeError, ":bucket is required in options"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get(key)
|
124
|
+
k = s3_key(key)
|
125
|
+
k.nil? ? nil : k.get
|
126
|
+
end
|
127
|
+
|
128
|
+
def s3_key(key, nil_if_expired=true)
|
129
|
+
begin
|
130
|
+
s3_key = @bucket.key(key, true)
|
131
|
+
if s3_key.exists?
|
132
|
+
logger.debug "[Moneta::S3] key exists: #{key}"
|
133
|
+
if s3_key.meta_headers.has_key?('expires-at')
|
134
|
+
expires_at = Time.parse(s3_key.meta_headers['expires-at'])
|
135
|
+
if Time.now > expires_at && nil_if_expired
|
136
|
+
# TODO delete the object?
|
137
|
+
debug "key expired: #{key} (@#{s3_key.meta_headers['expires-at']})"
|
138
|
+
return nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return s3_key
|
142
|
+
else
|
143
|
+
debug "key does not exist: #{key}"
|
144
|
+
end
|
145
|
+
rescue RightAws::AwsError => e
|
146
|
+
debug "key does not exist: #{key}"
|
147
|
+
end
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
|
151
|
+
def meta_headers_from_options(options={})
|
152
|
+
meta_headers = options[:meta_headers] || {}
|
153
|
+
if options[:expires_in]
|
154
|
+
meta_headers['expires-at'] = (Time.now + options[:expires_in]).rfc2822
|
155
|
+
end
|
156
|
+
debug "setting expires-at: #{meta_headers['expires-at']}"
|
157
|
+
meta_headers
|
158
|
+
end
|
159
|
+
|
160
|
+
def debug(message)
|
161
|
+
logger.debug "[Moneta::S3] #{message}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/lib/moneta/sdbm.rb
ADDED
@@ -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
|
data/lib/moneta/xattr.rb
ADDED
@@ -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,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aeden-moneta
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yehuda Katz
|
8
|
+
autorequire: moneta
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-03 00:00:00 -07: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
|
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
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.2.0
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: A unified interface to key/value stores
|
73
|
+
test_files: []
|
74
|
+
|