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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
| | # -*- encoding: binary -*-
# this module is NOT thread-safe, all performance is dependent on the
# local machine so there is never anything that needs yielding to threads.
module Metropolis::TC::HDB
autoload :RO, 'metropolis/tc/hdb/ro'
autoload :EX, 'metropolis/tc/hdb/ex'
TCHDB = TokyoCabinet::HDB # :nodoc
include Metropolis::Common
def setup(opts)
super
@rd_flags = TCHDB::OREADER
@wr_flags = TCHDB::OWRITER
@optimize = nil
if @query
case @query['rdlock']
when 'true', nil
when 'false'
@rd_flags |= TCHDB::ONOLCK
else
raise ArgumentError, "'rdlock' must be 'true' or 'false'"
end
case @query['wrlock']
when 'true', nil
when 'false'
@wr_flags |= TCHDB::ONOLCK
else
raise ArgumentError, "'wrlock' must be 'true' or 'false'"
end
flags = 0
@optimize = %w(bnum apow fpow).map do |x|
v = @query[x]
v ? v.to_i : nil
end
case large = @query['large']
when 'false', nil
when 'true'
flags |= TCHDB::TLARGE
else
raise ArgumentError, "invalid 'large' value: #{large}"
end
case compress = @query['compress']
when nil
when 'deflate', 'bzip', 'tcbs'
flags |= TCHDB.const_get("T#{compress.upcase}")
else
raise ArgumentError, "invalid 'compress' value: #{compress}"
end
@optimize << flags
end
@nr_slots = 1 unless @path_pattern
@dbv = (0...@nr_slots).to_a.map do |slot|
path = @path_pattern ? sprintf(@path_pattern, slot) : @uri.path
hdb = TCHDB.new
unless @readonly
hdb.open(path, TCHDB::OWRITER | TCHDB::OCREAT) or ex!(:open, hdb)
if @optimize
hdb.optimize(*@optimize) or ex!(:optimize, hdb)
end
hdb.close or ex!(:close, hdb)
end
[ hdb, path ]
end
@multi_hash ||= :digest_sha1
extend Metropolis::MultiHash
extend(RO) if @readonly
extend(EX) if @exclusive
end
def ex!(msg, hdb)
raise "#{msg}: #{hdb.errmsg(hdb.ecode)}"
end
def writer(key, &block)
hdb, path = @dbv[multi_hash(key) % @nr_slots]
hdb.open(path, @wr_flags) or ex!(:open, hdb)
yield hdb
ensure
hdb.close or ex!(:close, hdb)
end
def reader(key)
hdb, path = @dbv[multi_hash(key) % @nr_slots]
hdb.open(path, @rd_flags) or ex!(:open, hdb)
yield hdb
ensure
hdb.close or ex!(:close, hdb)
end
def put(key, env)
value = env[Rack_Input].read
writer(key) do |hdb|
case env[HTTP_X_TT_PDMODE]
when "1"
unless hdb.putkeep(key, value)
TCHDB::EKEEP == hdb.ecode and return r(409)
ex!(:putkeep, hdb)
end
when "2"
hdb.putcat(key, value) or ex!(:putcat, hdb)
else
# ttserver does not care for other PDMODE values, so we don't, either
hdb.put(key, value) or ex!(:put, hdb)
end
end
r(201)
end
def delete(key)
writer(key) do |hdb|
unless hdb.delete(key)
TCHDB::ENOREC == hdb.ecode and return r(404)
ex!(:delete, hdb)
end
end
r(200)
end
def get(key, env)
value = nil
reader(key) do |hdb|
unless value = hdb.get(key)
TCHDB::ENOREC == hdb.ecode and return r(404)
ex!(:get, hdb)
end
end
[ 200, { Content_Length => value.size.to_s }.merge!(@headers), [ value ] ]
end
def close!
@dbv.each { |(hdb,_)| hdb.close }
end
end
|