wycats-moneta 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +44 -0
- data/Rakefile +60 -0
- data/TODO +4 -0
- data/lib/moneta.rb +50 -0
- data/lib/moneta/datamapper.rb +107 -0
- data/lib/moneta/file.rb +100 -0
- data/lib/moneta/memcache.rb +52 -0
- data/lib/moneta/memory.rb +11 -0
- data/lib/moneta/xattr.rb +67 -0
- metadata +65 -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,44 @@
|
|
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
|
+
|
14
|
+
All stores support key expiration, but only memcache supports it natively. All other stores
|
15
|
+
emulate expiration.
|
16
|
+
|
17
|
+
The Moneta API is purposely extremely similar to the Hash API. In order so support an
|
18
|
+
identical API across stores, it does not support iteration or partial matches, but that
|
19
|
+
might come in a future release.
|
20
|
+
|
21
|
+
The API:
|
22
|
+
|
23
|
+
#initialize(options):: options differs per-store, and is used to set up the store
|
24
|
+
|
25
|
+
#[](key):: retrieve a key. if the key is not available, return nil
|
26
|
+
|
27
|
+
#[]=(key, value):: set a value for a key. if the key is already used, clobber it.
|
28
|
+
keys set using []= will never expire
|
29
|
+
|
30
|
+
#delete(key):: delete the key from the store and return the current value
|
31
|
+
|
32
|
+
#key?(key):: true if the key exists, false if it does not
|
33
|
+
|
34
|
+
#has_key?(key):: alias for key?
|
35
|
+
|
36
|
+
#store(key, value, options):: same as []=, but you can supply an :expires_in option,
|
37
|
+
which will specify a number of seconds before the key
|
38
|
+
should expire. In order to support the same features
|
39
|
+
across all stores, only full seconds are supported
|
40
|
+
|
41
|
+
#update_key(key, options):: updates an existing key with a new :expires_in option.
|
42
|
+
if the key has already expired, it will not be updated.
|
43
|
+
|
44
|
+
#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.5.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
data/lib/moneta.rb
ADDED
@@ -0,0 +1,50 @@
|
|
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 has_key?(key)
|
11
|
+
check_expired(key)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def key?(key)
|
16
|
+
has_key?(key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
check_expired(key)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(key, default)
|
25
|
+
check_expired(key)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(key)
|
30
|
+
check_expired(key)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_key(key, options)
|
35
|
+
update_options(key, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def store(key, value, options = {})
|
39
|
+
super(key, value)
|
40
|
+
update_options(key, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def update_options(key, options)
|
45
|
+
if options[:expires_in]
|
46
|
+
@expiration[key] = (Time.now + options[:expires_in])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,107 @@
|
|
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
|
+
def self.default_repository_name
|
13
|
+
:moneta
|
14
|
+
end
|
15
|
+
|
16
|
+
property :the_key, String, :key => true
|
17
|
+
property :value, Object, :lazy => false
|
18
|
+
property :expires, Time
|
19
|
+
|
20
|
+
def self.value(key)
|
21
|
+
obj = self.get(key)
|
22
|
+
obj && obj.value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Moneta
|
27
|
+
class DataMapper
|
28
|
+
class Expiration
|
29
|
+
def initialize(klass)
|
30
|
+
@klass = klass
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
if obj = get(key)
|
35
|
+
obj.expires
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def []=(key, value)
|
40
|
+
obj = get(key)
|
41
|
+
obj.expires = value
|
42
|
+
obj.save
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(key)
|
46
|
+
obj = get(key)
|
47
|
+
obj.expires = nil
|
48
|
+
obj.save
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def get(key)
|
53
|
+
@klass.get(key)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(options = {})
|
58
|
+
::DataMapper.setup(:moneta, options[:setup])
|
59
|
+
MonetaHash.auto_upgrade!
|
60
|
+
@hash = MonetaHash
|
61
|
+
@expiration = Expiration.new(MonetaHash)
|
62
|
+
end
|
63
|
+
|
64
|
+
module Implementation
|
65
|
+
def key?(key)
|
66
|
+
!!@hash.get(key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_key?(key)
|
70
|
+
!!@hash.get(key)
|
71
|
+
end
|
72
|
+
|
73
|
+
def [](key)
|
74
|
+
@hash.value(key)
|
75
|
+
end
|
76
|
+
|
77
|
+
def []=(key, value)
|
78
|
+
obj = @hash.get(key)
|
79
|
+
if obj
|
80
|
+
obj.update(key, value)
|
81
|
+
else
|
82
|
+
@hash.create(:the_key => key, :value => value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def fetch(key, default)
|
87
|
+
self[key] || default
|
88
|
+
end
|
89
|
+
|
90
|
+
def delete(key)
|
91
|
+
value = self[key]
|
92
|
+
@hash.all(:the_key => key).destroy!
|
93
|
+
value
|
94
|
+
end
|
95
|
+
|
96
|
+
def store(key, value, options = {})
|
97
|
+
self[key] = value
|
98
|
+
end
|
99
|
+
|
100
|
+
def clear
|
101
|
+
@hash.all.destroy!
|
102
|
+
end
|
103
|
+
end
|
104
|
+
include Implementation
|
105
|
+
include Expires
|
106
|
+
end
|
107
|
+
end
|
data/lib/moneta/file.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
begin
|
2
|
+
require "fileutils"
|
3
|
+
require "xattr"
|
4
|
+
rescue LoadError
|
5
|
+
puts "You need the xattr gem to use the File moneta store"
|
6
|
+
exit
|
7
|
+
end
|
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(@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
|
+
def has_key?(key)
|
55
|
+
::File.exist?(path(key))
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](key)
|
59
|
+
if ::File.exist?(path(key))
|
60
|
+
Marshal.load(::File.read(path(key)))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def []=(key, value)
|
65
|
+
::File.open(path(key), "w") do |file|
|
66
|
+
contents = Marshal.dump(value)
|
67
|
+
file.puts(contents)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def fetch(key, default)
|
72
|
+
self[key] || default
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete(key)
|
76
|
+
value = self[key]
|
77
|
+
FileUtils.rm(path(key))
|
78
|
+
value
|
79
|
+
rescue Errno::ENOENT
|
80
|
+
end
|
81
|
+
|
82
|
+
def store(key, value, options = {})
|
83
|
+
self[key] = value
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear
|
87
|
+
FileUtils.rm_rf(@directory)
|
88
|
+
FileUtils.mkdir(@directory)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def path(key)
|
93
|
+
::File.join(@directory, key)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
include Implementation
|
97
|
+
include Expires
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
begin
|
2
|
+
require "memcache"
|
3
|
+
rescue LoadError
|
4
|
+
puts "You need the memcache gem to use the Memcache moneta store"
|
5
|
+
exit
|
6
|
+
end
|
7
|
+
|
8
|
+
module Moneta
|
9
|
+
class Memcache
|
10
|
+
def initialize(options = {})
|
11
|
+
@cache = MemCache.new(options.delete(:server), options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def key?(key)
|
15
|
+
!self["key"].nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
alias has_key? key?
|
19
|
+
|
20
|
+
def [](key)
|
21
|
+
@cache.get(key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(key, value)
|
25
|
+
store(key, value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fetch(key, default)
|
29
|
+
self[key] || default
|
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
|
data/lib/moneta/xattr.rb
ADDED
@@ -0,0 +1,67 @@
|
|
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
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
file = options[:file]
|
14
|
+
@hash = ::Xattr.new(file)
|
15
|
+
FileUtils.mkdir_p(::File.dirname(file))
|
16
|
+
FileUtils.touch(file)
|
17
|
+
unless options[:skip_expires]
|
18
|
+
@expiration = Moneta::Xattr.new(:file => "#{file}_expiration", :skip_expires => true)
|
19
|
+
self.extend(Expires)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Implementation
|
24
|
+
|
25
|
+
def key?(key)
|
26
|
+
@hash.list.include?(key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_key?(key)
|
30
|
+
@hash.list.include?(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
return nil unless key?(key)
|
35
|
+
Marshal.load(@hash.get(key))
|
36
|
+
end
|
37
|
+
|
38
|
+
def []=(key, value)
|
39
|
+
@hash.set(key, Marshal.dump(value))
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch(key, value)
|
43
|
+
self[key] || value
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(key)
|
47
|
+
return nil unless key?(key)
|
48
|
+
value = self[key]
|
49
|
+
@hash.remove(key)
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def store(key, value, options = {})
|
54
|
+
self[key] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def clear
|
58
|
+
@hash.list.each do |item|
|
59
|
+
@hash.remove(item)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
include Implementation
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wycats-moneta
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yehuda Katz
|
8
|
+
autorequire: moneta
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-12 00:00:00 -08: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/datamapper.rb
|
33
|
+
- lib/moneta/file.rb
|
34
|
+
- lib/moneta/memcache.rb
|
35
|
+
- lib/moneta/memory.rb
|
36
|
+
- lib/moneta/xattr.rb
|
37
|
+
- lib/moneta.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://www.yehudakatz.com
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: A unified interface to key/value stores
|
64
|
+
test_files: []
|
65
|
+
|