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