GNUmakefile: no news yet...
[metropolis.git] / lib / metropolis / tc / hdb.rb
blobe63b015f91993dac3bc67db1c65f934478da21d2
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     @rd_flags = TCHDB::OREADER
15     @wr_flags = TCHDB::OWRITER
17     @optimize = nil
18     if @query
19       case @query['rdlock']
20       when 'true', nil
21       when 'false'
22         @rd_flags |= TCHDB::ONOLCK
23       else
24         raise ArgumentError, "'rdlock' must be 'true' or 'false'"
25       end
27       case @query['wrlock']
28       when 'true', nil
29       when 'false'
30         @wr_flags |= TCHDB::ONOLCK
31       else
32         raise ArgumentError, "'wrlock' must be 'true' or 'false'"
33       end
35       flags = 0
36       @optimize = %w(bnum apow fpow).map do |x|
37         v = @query[x]
38         v ? v.to_i : nil
39       end
41       case large = @query['large']
42       when 'false', nil
43       when 'true'
44         flags |= TCHDB::TLARGE
45       else
46         raise ArgumentError, "invalid 'large' value: #{large}"
47       end
49       case compress = @query['compress']
50       when nil
51       when 'deflate', 'bzip', 'tcbs'
52         flags |= TCHDB.const_get("T#{compress.upcase}")
53       else
54         raise ArgumentError, "invalid 'compress' value: #{compress}"
55       end
56       @optimize << flags
57     end
58     @nr_slots = 1 unless @path_pattern
59     @dbv = (0...@nr_slots).to_a.map do |slot|
60       path = @path_pattern ? sprintf(@path_pattern, slot) : @uri.path
61       hdb = TCHDB.new
62       unless @readonly
63         hdb.open(path, TCHDB::OWRITER | TCHDB::OCREAT) or ex!(:open, hdb)
64         if @optimize
65           hdb.optimize(*@optimize) or ex!(:optimize, hdb)
66         end
67         hdb.close or ex!(:close, hdb)
68       end
69       [ hdb, path ]
70     end
71     @multi_hash ||= :digest_sha1
72     extend Metropolis::MultiHash
73     extend(RO) if @readonly
74     extend(EX) if @exclusive
75   end
77   def ex!(msg, hdb)
78     raise "#{msg}: #{hdb.errmsg(hdb.ecode)}"
79   end
81   def writer(key, &block)
82     hdb, path = @dbv[multi_hash(key) % @nr_slots]
83     hdb.open(path, @wr_flags) or ex!(:open, hdb)
84     yield hdb
85     ensure
86       hdb.close or ex!(:close, hdb)
87   end
89   def reader(key)
90     hdb, path = @dbv[multi_hash(key) % @nr_slots]
91     hdb.open(path, @rd_flags) or ex!(:open, hdb)
92     yield hdb
93     ensure
94       hdb.close or ex!(:close, hdb)
95   end
97   def put(key, env)
98     value = env["rack.input"].read
99     writer(key) do |hdb|
100       case env['HTTP_X_TT_PDMODE']
101       when '1'
102         unless hdb.putkeep(key, value)
103           TCHDB::EKEEP == hdb.ecode and return r(409)
104           ex!(:putkeep, hdb)
105         end
106       when '2'
107         hdb.putcat(key, value) or ex!(:putcat, hdb)
108       else
109         # ttserver does not care for other PDMODE values, so we don't, either
110         hdb.put(key, value) or ex!(:put, hdb)
111       end
112     end
113     r(201)
114   end
116   def delete(key)
117     writer(key) do |hdb|
118       unless hdb.delete(key)
119         TCHDB::ENOREC == hdb.ecode and return r(404)
120         ex!(:delete, hdb)
121       end
122     end
123     r(200)
124   end
126   def get(key, env)
127     value = nil
128     reader(key) do |hdb|
129       unless value = hdb.get(key)
130         TCHDB::ENOREC == hdb.ecode and return r(404)
131         ex!(:get, hdb)
132       end
133     end
134     [ 200, { 'Content-Length' => value.size.to_s }.merge!(@headers), [ value ] ]
135   end
137   def close!
138     @dbv.each { |(hdb,_)| hdb.close }
139   end