1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| | # -*- encoding: binary -*-
require 'tempfile'
# use a Ruby hash as a plain data store
# It can unmarshal a hash from disk
module Metropolis::Hash
include Metropolis::Common
def setup(opts)
super
if @path
begin
@db = Marshal.load(File.open(@path, "rb") { |fp| fp.read })
Hash === @db or raise ArgumentError, "#@path is not a marshaled Hash"
rescue Errno::ENOENT
@db = {}
end
else
@db = {}
end
if @readonly
extend Metropolis::Common::RO
elsif @path
args = [ @db, @path, !!opts[:fsync] ]
@clean_proc = Metropolis::Hash.finalizer_callback(args)
ObjectSpace.define_finalizer(self, @clean_proc)
end
end
def close!
if ! @readonly && ! @path.nil?
@clean_proc.call
ObjectSpace.undefine_finalizer(self)
end
@db = @path = nil
end
def get(key, env)
value = @db[key] or return r(404)
[ 200, { Content_Length => value.size.to_s }.merge!(@headers), [ value ] ]
end
def put(key, env)
value = env[Rack_Input].read
case env[HTTP_X_TT_PDMODE]
when "1"
@db.exists?(key) and r(409)
@db[key] = value
when "2"
(tmp = @db[key] ||= "") << value
else
@db[key] = value
end
r(201)
end
def delete(key)
r(@db.delete(key) ? 200 : 404)
end
def self.finalizer_callback(data)
lambda {
db, path, fsync = data
dir = File.dirname(path)
tmp = Tempfile.new('hash_save', dir)
tmp.binmode
tmp.sync = true
tmp.write(Marshal.dump(db))
tmp.fsync if fsync
File.rename(tmp.path, path)
File.open(dir) { |d| d.fsync } if fsync
tmp.close!
}
end
end
|