rufus-cloche 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +1 -1
- data/LICENSE.txt +21 -0
- data/TODO.txt +12 -0
- data/lib/rufus-cloche.rb +3 -0
- data/lib/rufus/cloche.rb +246 -0
- metadata +6 -2
data/CHANGELOG.txt
CHANGED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
THE SOFTWARE.
|
21
|
+
|
data/TODO.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
[x] !! test for type change. Not covered. Have to delete + put.
|
3
|
+
[x] use 1! lock for put. It's OK for now
|
4
|
+
[o] fall back on regular json
|
5
|
+
[o] thread-safe
|
6
|
+
[o] readme : get_many example
|
7
|
+
[o] subdir for types (2 last ascii chars argh)
|
8
|
+
|
9
|
+
[ ] LRU ?
|
10
|
+
|
11
|
+
[ ] need for a purge mecha
|
12
|
+
|
data/lib/rufus-cloche.rb
ADDED
data/lib/rufus/cloche.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'thread'
|
26
|
+
require 'fileutils'
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'yajl'
|
30
|
+
rescue LoadError
|
31
|
+
require 'json'
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
module Rufus
|
36
|
+
|
37
|
+
#
|
38
|
+
# A cloche is a local JSON hashes store.
|
39
|
+
#
|
40
|
+
# Warning : cloches are process-safe but not thread-safe.
|
41
|
+
#
|
42
|
+
class Cloche
|
43
|
+
|
44
|
+
if defined?(Yajl)
|
45
|
+
def self.json_decode (s)
|
46
|
+
Yajl::Parser.parse(s)
|
47
|
+
end
|
48
|
+
def self.json_encode (o)
|
49
|
+
Yajl::Encoder.encode(o)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
def self.json_decode (s)
|
53
|
+
::JSON.parse(s)
|
54
|
+
end
|
55
|
+
def self.json_encode (o)
|
56
|
+
o.to_json
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
VERSION = '0.1.1'
|
61
|
+
|
62
|
+
attr_reader :dir
|
63
|
+
|
64
|
+
# Currently, the only known option is :dir
|
65
|
+
#
|
66
|
+
def initialize (opts={})
|
67
|
+
|
68
|
+
@dir = File.expand_path(opts[:dir] || 'cloche')
|
69
|
+
@mutex = Mutex.new
|
70
|
+
end
|
71
|
+
|
72
|
+
# Puts a document (Hash) under the cloche.
|
73
|
+
#
|
74
|
+
# If the document is brand new, it will be given a revision number '_rev'
|
75
|
+
# of 0.
|
76
|
+
#
|
77
|
+
# If the document already exists in the cloche and the version to put
|
78
|
+
# has an older (different) revision number than the one currently stored,
|
79
|
+
# put will fail and return the current version of the doc.
|
80
|
+
#
|
81
|
+
# If the put is successful, nil is returned.
|
82
|
+
#
|
83
|
+
def put (doc)
|
84
|
+
|
85
|
+
type, key = doc['type'], doc['_id']
|
86
|
+
|
87
|
+
raise(
|
88
|
+
ArgumentError.new("missing values for keys 'type' and/or '_id'")
|
89
|
+
) if type.nil? || key.nil?
|
90
|
+
|
91
|
+
d, f = path_for(type, key)
|
92
|
+
fn = File.join(d, f)
|
93
|
+
|
94
|
+
rev = (doc['_rev'] ||= -1)
|
95
|
+
|
96
|
+
raise(
|
97
|
+
ArgumentError.new("values for '_rev' must be positive integers")
|
98
|
+
) if rev.class != Fixnum && rev.class != Bignum
|
99
|
+
|
100
|
+
FileUtils.mkdir_p(d) unless File.exist?(d)
|
101
|
+
FileUtils.touch(fn) unless File.exist?(fn)
|
102
|
+
|
103
|
+
lock(fn) do |f|
|
104
|
+
|
105
|
+
cur = do_get(f)
|
106
|
+
|
107
|
+
return cur if cur && cur['_rev'] != doc['_rev']
|
108
|
+
|
109
|
+
doc['_rev'] = doc['_rev'] + 1
|
110
|
+
|
111
|
+
File.open(f, 'wb') { |io| io.write(Cloche.json_encode(doc)) }
|
112
|
+
end
|
113
|
+
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
# Gets a document (or nil if not found (or corrupted)).
|
118
|
+
#
|
119
|
+
def get (type, key)
|
120
|
+
|
121
|
+
r = lock(type, key) { |f| do_get(f) }
|
122
|
+
r == true ? nil : r
|
123
|
+
end
|
124
|
+
|
125
|
+
# Attempts at deleting a document. You have to pass the current version
|
126
|
+
# or at least the { '_id' => i, 'type' => t, '_rev' => r }.
|
127
|
+
#
|
128
|
+
# Will return nil if the deletion is successful.
|
129
|
+
#
|
130
|
+
# If the deletion failed because the given doc has an older revision number
|
131
|
+
# that the one currently stored, the doc in its freshest version will be
|
132
|
+
# returned.
|
133
|
+
#
|
134
|
+
# Returns true if the deletion failed.
|
135
|
+
#
|
136
|
+
def delete (doc)
|
137
|
+
|
138
|
+
type, key = doc['type'], doc['_id']
|
139
|
+
|
140
|
+
lock(type, key) do |f|
|
141
|
+
|
142
|
+
cur = do_get(f)
|
143
|
+
|
144
|
+
return nil unless cur
|
145
|
+
return cur if cur['_rev'] != doc['_rev']
|
146
|
+
|
147
|
+
begin
|
148
|
+
File.delete(f.path)
|
149
|
+
nil
|
150
|
+
rescue
|
151
|
+
true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Given a type, this method will return an array of all the documents for
|
157
|
+
# that type.
|
158
|
+
#
|
159
|
+
# A optional second parameter may be used to select, based on a regular
|
160
|
+
# expression, which documents to include (match on the key '_id').
|
161
|
+
#
|
162
|
+
# Will return an empty Hash if there is no documents for a given type.
|
163
|
+
#
|
164
|
+
def get_many (type, key_match=nil)
|
165
|
+
|
166
|
+
d = dir_for(type)
|
167
|
+
|
168
|
+
return [] unless File.exist?(d)
|
169
|
+
|
170
|
+
Dir[File.join(d, '**', '*.json')].inject([]) { |a, fn|
|
171
|
+
|
172
|
+
key = File.basename(fn, '.json')
|
173
|
+
|
174
|
+
if (not key_match) || key.match(key_match)
|
175
|
+
|
176
|
+
doc = get(type, key)
|
177
|
+
a << doc if doc
|
178
|
+
end
|
179
|
+
|
180
|
+
a
|
181
|
+
}.sort { |doc0, doc1|
|
182
|
+
doc0['_id'] <=> doc1['_id']
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
protected
|
187
|
+
|
188
|
+
def self.neutralize (s)
|
189
|
+
|
190
|
+
s.to_s.strip.gsub(/[ \/:;\*\\\+\?]/, '_')
|
191
|
+
end
|
192
|
+
|
193
|
+
def do_get (file)
|
194
|
+
|
195
|
+
Cloche.json_decode(file.read) rescue nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def dir_for (type)
|
199
|
+
|
200
|
+
File.join(@dir, Cloche.neutralize(type || 'no_type'))
|
201
|
+
end
|
202
|
+
|
203
|
+
def path_for (type, key)
|
204
|
+
|
205
|
+
nkey = Cloche.neutralize(key)
|
206
|
+
|
207
|
+
[
|
208
|
+
File.join(dir_for(type), nkey[-2, 2] || nkey),
|
209
|
+
"#{nkey}.json"
|
210
|
+
]
|
211
|
+
end
|
212
|
+
|
213
|
+
def file_for (type_or_doc, key)
|
214
|
+
|
215
|
+
fn = if key
|
216
|
+
File.join(*path_for(type_or_doc, key))
|
217
|
+
elsif type_or_doc.is_a?(String)
|
218
|
+
type_or_doc
|
219
|
+
else # it's a doc (Hash)
|
220
|
+
File.join(*path_for(type_or_doc['type'], type_or_doc['_id']))
|
221
|
+
end
|
222
|
+
|
223
|
+
File.exist?(fn) ? (File.new(fn) rescue nil) : nil
|
224
|
+
end
|
225
|
+
|
226
|
+
def lock (type_or_doc, key=nil, &block)
|
227
|
+
|
228
|
+
file = file_for(type_or_doc, key)
|
229
|
+
|
230
|
+
return true if file.nil?
|
231
|
+
|
232
|
+
begin
|
233
|
+
file.flock(File::LOCK_EX)
|
234
|
+
@mutex.synchronize { block.call(file) }
|
235
|
+
ensure
|
236
|
+
begin
|
237
|
+
file.flock(File::LOCK_UN)
|
238
|
+
rescue Exception => e
|
239
|
+
#p [ :lock, @fpath, e ]
|
240
|
+
#e.backtrace.each { |l| puts l }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rufus-cloche
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
@@ -27,9 +27,13 @@ extra_rdoc_files:
|
|
27
27
|
- CHANGELOG.txt
|
28
28
|
- CREDITS.txt
|
29
29
|
files:
|
30
|
-
-
|
30
|
+
- lib/rufus/cloche.rb
|
31
|
+
- lib/rufus-cloche.rb
|
31
32
|
- CHANGELOG.txt
|
32
33
|
- CREDITS.txt
|
34
|
+
- LICENSE.txt
|
35
|
+
- TODO.txt
|
36
|
+
- README.rdoc
|
33
37
|
has_rdoc: true
|
34
38
|
homepage: http://github.com/jmettraux/rufus-cloche/
|
35
39
|
licenses: []
|