rufus-cloche 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []