internal API: get and head methods require env
[metropolis.git] / lib / metropolis / tc / hdb.rb
blob09d64392ffe5369a9e7160800f60710b59cd74d6
1 # -*- encoding: binary -*-
3 # this module is NOT thread-safe, all performance is dependent on the
4 # local machine so there is never anything that needs yielding to threads.
5 module Metropolis::TC::HDB
6   autoload :RO, 'metropolis/tc/hdb/ro'
7   autoload :EX, 'metropolis/tc/hdb/ex'
9   TCHDB = TokyoCabinet::HDB # :nodoc
10   include Metropolis::Common
12   def setup(opts)
13     super
14     path_pattern = opts[:path_pattern]
15     path_pattern.scan(/%\d*x/).size == 1 or
16       raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}"
18     @rd_flags = TCHDB::OREADER
19     @wr_flags = TCHDB::OWRITER
21     @optimize = nil
22     if query = opts[:query]
23       case query['rdlock']
24       when 'true', nil
25       when 'false'
26         @rd_flags |= TCHDB::ONOLCK
27       else
28         raise ArgumentError, "'rdlock' must be 'true' or 'false'"
29       end
31       case query['wrlock']
32       when 'true', nil
33       when 'false'
34         @wr_flags |= TCHDB::ONOLCK
35       else
36         raise ArgumentError, "'wrlock' must be 'true' or 'false'"
37       end
39       flags = 0
40       @optimize = %w(bnum apow fpow).map do |x|
41         v = query[x]
42         v ? v.to_i : nil
43       end
45       case large = query['large']
46       when 'false', nil
47       when 'true'
48         flags |= TCHDB::TLARGE
49       else
50         raise ArgumentError, "invalid 'large' value: #{large}"
51       end
53       case compress = query['compress']
54       when nil
55       when 'deflate', 'bzip', 'tcbs'
56         flags |= TCHDB.const_get("T#{compress.upcase}")
57       else
58         raise ArgumentError, "invalid 'compress' value: #{compress}"
59       end
60       @optimize << flags
61     end
62     @dbv = (0...@nr_slots).to_a.map do |slot|
63       path = sprintf(path_pattern, slot)
64       hdb = TCHDB.new
65       unless @readonly
66         hdb.open(path, TCHDB::OWRITER | TCHDB::OCREAT) or ex!(:open, hdb)
67         if @optimize
68           hdb.optimize(*@optimize) or ex!(:optimize, hdb)
69         end
70         hdb.close or ex!(:close, hdb)
71       end
72       [ hdb, path ]
73     end
74     extend(RO) if @readonly
75     extend(EX) if @exclusive
76   end
78   def ex!(msg, hdb)
79     raise "#{msg}: #{hdb.errmsg(hdb.ecode)}"
80   end
82   def writer(key, &block)
83     hdb, path = @dbv[key.hash % @nr_slots]
84     hdb.open(path, @wr_flags) or ex!(:open, hdb)
85     yield hdb
86     ensure
87       hdb.close or ex!(:close, hdb)
88   end
90   def reader(key)
91     hdb, path = @dbv[key.hash % @nr_slots]
92     hdb.open(path, @rd_flags) or ex!(:open, hdb)
93     yield hdb
94     ensure
95       hdb.close or ex!(:close, hdb)
96   end
98   def put(key, env)
99     value = env["rack.input"].read
100     writer(key) do |hdb|
101       case env['HTTP_X_TT_PDMODE']
102       when '1'
103         unless hdb.putkeep(key, value)
104           TCHDB::EKEEP == hdb.ecode and return r(409)
105           ex!(:putkeep, hdb)
106         end
107       when '2'
108         hdb.putcat(key, value) or ex!(:putcat, hdb)
109       else
110         # ttserver does not care for other PDMODE values, so we don't, either
111         hdb.put(key, value) or ex!(:put, hdb)
112       end
113     end
114     r(201)
115   end
117   def delete(key)
118     writer(key) do |hdb|
119       unless hdb.delete(key)
120         TCHDB::ENOREC == hdb.ecode and return r(404)
121         ex!(:delete, hdb)
122       end
123     end
124     r(200)
125   end
127   def head(key, env)
128     size = reader(key) { |hdb| hdb.vsiz(key) or ex!(:vsiz, hdb) }
129     0 > size and return r(404, "")
130     [ 200, { 'Content-Length' => size.to_s }.merge!(@headers), [] ]
131   end
133   def get(key, env)
134     value = nil
135     reader(key) do |hdb|
136       unless value = hdb.get(key)
137         TCHDB::ENOREC == hdb.ecode and return r(404)
138         ex!(:get, hdb)
139       end
140     end
141     [ 200, { 'Content-Length' => value.size.to_s }.merge!(@headers), [ value ] ]
142   end
144   def close!
145     @dbv.each { |(hdb,_)| hdb.close }
146   end