Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 6096 Bytes
1 | require 'digest/sha1' |
2 | |
3 | module Rack::Cache |
4 | |
5 | # Entity stores are used to cache response bodies across requests. All |
6 | # Implementations are required to calculate a SHA checksum of the data written |
7 | # which becomes the response body's key. |
8 | class EntityStore |
9 | |
10 | # Read body calculating the SHA1 checksum and size while |
11 | # yielding each chunk to the block. If the body responds to close, |
12 | # call it after iteration is complete. Return a two-tuple of the form: |
13 | # [ hexdigest, size ]. |
14 | def slurp(body) |
15 | digest, size = Digest::SHA1.new, 0 |
16 | body.each do |part| |
17 | size += bytesize(part) |
18 | digest << part |
19 | yield part |
20 | end |
21 | body.close if body.respond_to? :close |
22 | [ digest.hexdigest, size ] |
23 | end |
24 | |
25 | if ''.respond_to?(:bytesize) |
26 | def bytesize(string); string.bytesize; end |
27 | else |
28 | def bytesize(string); string.size; end |
29 | end |
30 | |
31 | private :slurp, :bytesize |
32 | |
33 | |
34 | # Stores entity bodies on the heap using a Hash object. |
35 | class Heap < EntityStore |
36 | |
37 | # Create the store with the specified backing Hash. |
38 | def initialize(hash={}) |
39 | @hash = hash |
40 | end |
41 | |
42 | # Determine whether the response body with the specified key (SHA1) |
43 | # exists in the store. |
44 | def exist?(key) |
45 | @hash.include?(key) |
46 | end |
47 | |
48 | # Return an object suitable for use as a Rack response body for the |
49 | # specified key. |
50 | def open(key) |
51 | (body = @hash[key]) && body.dup |
52 | end |
53 | |
54 | # Read all data associated with the given key and return as a single |
55 | # String. |
56 | def read(key) |
57 | (body = @hash[key]) && body.join |
58 | end |
59 | |
60 | # Write the Rack response body immediately and return the SHA1 key. |
61 | def write(body) |
62 | buf = [] |
63 | key, size = slurp(body) { |part| buf << part } |
64 | @hash[key] = buf |
65 | [key, size] |
66 | end |
67 | |
68 | # Remove the body corresponding to key; return nil. |
69 | def purge(key) |
70 | @hash.delete(key) |
71 | nil |
72 | end |
73 | |
74 | def self.resolve(uri) |
75 | new |
76 | end |
77 | end |
78 | |
79 | HEAP = Heap |
80 | MEM = Heap |
81 | |
82 | # Stores entity bodies on disk at the specified path. |
83 | class Disk < EntityStore |
84 | |
85 | # Path where entities should be stored. This directory is |
86 | # created the first time the store is instansiated if it does not |
87 | # already exist. |
88 | attr_reader :root |
89 | |
90 | def initialize(root) |
91 | @root = root |
92 | FileUtils.mkdir_p root, :mode => 0755 |
93 | end |
94 | |
95 | def exist?(key) |
96 | File.exist?(body_path(key)) |
97 | end |
98 | |
99 | def read(key) |
100 | File.open(body_path(key), 'rb') { |f| f.read } |
101 | rescue Errno::ENOENT |
102 | nil |
103 | end |
104 | |
105 | class Body < ::File #:nodoc: |
106 | def each |
107 | while part = read(8192) |
108 | yield part |
109 | end |
110 | end |
111 | alias_method :to_path, :path |
112 | end |
113 | |
114 | # Open the entity body and return an IO object. The IO object's |
115 | # each method is overridden to read 8K chunks instead of lines. |
116 | def open(key) |
117 | Body.open(body_path(key), 'rb') |
118 | rescue Errno::ENOENT |
119 | nil |
120 | end |
121 | |
122 | def write(body) |
123 | filename = ['buf', $$, Thread.current.object_id].join('-') |
124 | temp_file = storage_path(filename) |
125 | key, size = |
126 | File.open(temp_file, 'wb') { |dest| |
127 | slurp(body) { |part| dest.write(part) } |
128 | } |
129 | |
130 | path = body_path(key) |
131 | if File.exist?(path) |
132 | File.unlink temp_file |
133 | else |
134 | FileUtils.mkdir_p File.dirname(path), :mode => 0755 |
135 | FileUtils.mv temp_file, path |
136 | end |
137 | [key, size] |
138 | end |
139 | |
140 | def purge(key) |
141 | File.unlink body_path(key) |
142 | nil |
143 | rescue Errno::ENOENT |
144 | nil |
145 | end |
146 | |
147 | protected |
148 | def storage_path(stem) |
149 | File.join root, stem |
150 | end |
151 | |
152 | def spread(key) |
153 | key = key.dup |
154 | key[2,0] = '/' |
155 | key |
156 | end |
157 | |
158 | def body_path(key) |
159 | storage_path spread(key) |
160 | end |
161 | |
162 | def self.resolve(uri) |
163 | path = File.expand_path(uri.opaque || uri.path) |
164 | new path |
165 | end |
166 | end |
167 | |
168 | DISK = Disk |
169 | FILE = Disk |
170 | |
171 | # Stores entity bodies in memcached. |
172 | class MemCache < EntityStore |
173 | |
174 | # The underlying Memcached instance used to communicate with the |
175 | # memcahced daemon. |
176 | attr_reader :cache |
177 | |
178 | def initialize(server="localhost:11211", options={}) |
179 | @cache = |
180 | if server.respond_to?(:stats) |
181 | server |
182 | else |
183 | require 'memcached' |
184 | Memcached.new(server, options) |
185 | end |
186 | end |
187 | |
188 | def exist?(key) |
189 | cache.append(key, '') |
190 | true |
191 | rescue Memcached::NotStored |
192 | false |
193 | end |
194 | |
195 | def read(key) |
196 | cache.get(key, false) |
197 | rescue Memcached::NotFound |
198 | nil |
199 | end |
200 | |
201 | def open(key) |
202 | if data = read(key) |
203 | [data] |
204 | else |
205 | nil |
206 | end |
207 | end |
208 | |
209 | def write(body) |
210 | buf = StringIO.new |
211 | key, size = slurp(body){|part| buf.write(part) } |
212 | cache.set(key, buf.string, 0, false) |
213 | [key, size] |
214 | end |
215 | |
216 | def purge(key) |
217 | cache.delete(key) |
218 | nil |
219 | rescue Memcached::NotFound |
220 | nil |
221 | end |
222 | |
223 | extend Rack::Utils |
224 | |
225 | # Create MemCache store for the given URI. The URI must specify |
226 | # a host and may specify a port, namespace, and options: |
227 | # |
228 | # memcached://example.com:11211/namespace?opt1=val1&opt2=val2 |
229 | # |
230 | # Query parameter names and values are documented with the memcached |
231 | # library: http://tinyurl.com/4upqnd |
232 | def self.resolve(uri) |
233 | server = "#{uri.host}:#{uri.port || '11211'}" |
234 | options = parse_query(uri.query) |
235 | options.keys.each do |key| |
236 | value = |
237 | case value = options.delete(key) |
238 | when 'true' ; true |
239 | when 'false' ; false |
240 | else value.to_sym |
241 | end |
242 | options[k.to_sym] = value |
243 | end |
244 | options[:namespace] = uri.path.sub(/^\//, '') |
245 | new server, options |
246 | end |
247 | end |
248 | |
249 | MEMCACHE = MemCache |
250 | MEMCACHED = MemCache |
251 | end |
252 | |
253 | end |