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 CHANGED
@@ -2,5 +2,5 @@
2
2
  = rufus-cloche CHANGELOG.txt
3
3
 
4
4
 
5
- == rufus-cloche - 0.1.0 released 2009/11/16
5
+ == rufus-cloche - 0.1.1 released 2009/11/16
6
6
 
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
+
@@ -0,0 +1,3 @@
1
+
2
+ require 'rufus/cloche'
3
+
@@ -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.0
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
- - README.rdoc
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: []