arrow 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +1590 -0
- data/LICENSE +28 -0
- data/README +75 -0
- data/Rakefile +366 -0
- data/Rakefile.local +63 -0
- data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
- data/data/arrow/applets/args.rb +50 -0
- data/data/arrow/applets/config.rb +55 -0
- data/data/arrow/applets/error.rb +63 -0
- data/data/arrow/applets/files.rb +46 -0
- data/data/arrow/applets/inspect.rb +46 -0
- data/data/arrow/applets/nosuchapplet.rb +31 -0
- data/data/arrow/applets/status.rb +92 -0
- data/data/arrow/applets/test.rb +133 -0
- data/data/arrow/applets/tutorial/counter.rb +96 -0
- data/data/arrow/applets/tutorial/dingus.rb +67 -0
- data/data/arrow/applets/tutorial/hello.rb +34 -0
- data/data/arrow/applets/tutorial/hello2.rb +73 -0
- data/data/arrow/applets/tutorial/imgtext.rb +90 -0
- data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
- data/data/arrow/applets/tutorial/index.rb +36 -0
- data/data/arrow/applets/tutorial/logo.rb +98 -0
- data/data/arrow/applets/tutorial/memcache.rb +61 -0
- data/data/arrow/applets/tutorial/missing.rb +37 -0
- data/data/arrow/applets/tutorial/protected.rb +100 -0
- data/data/arrow/applets/tutorial/redirector.rb +52 -0
- data/data/arrow/applets/tutorial/rndimages.rb +159 -0
- data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
- data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
- data/data/arrow/applets/tutorial/superhello.rb +72 -0
- data/data/arrow/applets/tutorial/timeclock.rb +78 -0
- data/data/arrow/applets/view-applet.rb +123 -0
- data/data/arrow/applets/view-template.rb +85 -0
- data/data/arrow/applets/wiki.rb +274 -0
- data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
- data/data/arrow/templates/applet-status.tmpl +153 -0
- data/data/arrow/templates/args-display.tmpl +120 -0
- data/data/arrow/templates/config/display-table.tmpl +36 -0
- data/data/arrow/templates/config/display.tmpl +36 -0
- data/data/arrow/templates/counter-deleted.tmpl +33 -0
- data/data/arrow/templates/counter.tmpl +59 -0
- data/data/arrow/templates/dingus.tmpl +55 -0
- data/data/arrow/templates/enumtable.tmpl +8 -0
- data/data/arrow/templates/error-display.tmpl +92 -0
- data/data/arrow/templates/filemap.tmpl +89 -0
- data/data/arrow/templates/hello-world-src.tmpl +34 -0
- data/data/arrow/templates/hello-world.tmpl +60 -0
- data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
- data/data/arrow/templates/imgtext/form.tmpl +70 -0
- data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
- data/data/arrow/templates/imgtext/reload.tmpl +55 -0
- data/data/arrow/templates/inspect/display.tmpl +80 -0
- data/data/arrow/templates/loginform.tmpl +64 -0
- data/data/arrow/templates/logout.tmpl +32 -0
- data/data/arrow/templates/memcache/display.tmpl +41 -0
- data/data/arrow/templates/navbar.incl +27 -0
- data/data/arrow/templates/nosuchapplet.tmpl +32 -0
- data/data/arrow/templates/printsource.tmpl +35 -0
- data/data/arrow/templates/protected.tmpl +36 -0
- data/data/arrow/templates/rndimages.tmpl +38 -0
- data/data/arrow/templates/service-response.tmpl +13 -0
- data/data/arrow/templates/sharenotes/display.tmpl +38 -0
- data/data/arrow/templates/status.tmpl +120 -0
- data/data/arrow/templates/templateviewer.tmpl +43 -0
- data/data/arrow/templates/test/harness.tmpl +57 -0
- data/data/arrow/templates/test/list.tmpl +48 -0
- data/data/arrow/templates/test/problem.tmpl +42 -0
- data/data/arrow/templates/tutorial/index.tmpl +37 -0
- data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
- data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
- data/data/arrow/templates/view-applet.tmpl +40 -0
- data/data/arrow/templates/view-template.tmpl +83 -0
- data/data/arrow/templates/wiki/formerror.tmpl +47 -0
- data/data/arrow/templates/wiki/markup_help.incl +6 -0
- data/data/arrow/templates/wiki/new.tmpl +56 -0
- data/data/arrow/templates/wiki/new_system.tmpl +122 -0
- data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
- data/data/arrow/templates/wiki/show.tmpl +34 -0
- data/docs/manual/layouts/default.page +43 -0
- data/docs/manual/lib/api-filter.rb +81 -0
- data/docs/manual/lib/editorial-filter.rb +64 -0
- data/docs/manual/lib/examples-filter.rb +244 -0
- data/docs/manual/lib/links-filter.rb +117 -0
- data/lib/apache/fakerequest.rb +448 -0
- data/lib/apache/logger.rb +33 -0
- data/lib/arrow.rb +51 -0
- data/lib/arrow/acceptparam.rb +207 -0
- data/lib/arrow/applet.rb +725 -0
- data/lib/arrow/appletmixins.rb +218 -0
- data/lib/arrow/appletregistry.rb +590 -0
- data/lib/arrow/applettestcase.rb +503 -0
- data/lib/arrow/broker.rb +255 -0
- data/lib/arrow/cache.rb +176 -0
- data/lib/arrow/config-loaders/yaml.rb +75 -0
- data/lib/arrow/config.rb +615 -0
- data/lib/arrow/constants.rb +24 -0
- data/lib/arrow/cookie.rb +359 -0
- data/lib/arrow/cookieset.rb +108 -0
- data/lib/arrow/dispatcher.rb +368 -0
- data/lib/arrow/dispatcherloader.rb +50 -0
- data/lib/arrow/exceptions.rb +61 -0
- data/lib/arrow/fallbackhandler.rb +48 -0
- data/lib/arrow/formvalidator.rb +631 -0
- data/lib/arrow/htmltokenizer.rb +343 -0
- data/lib/arrow/logger.rb +488 -0
- data/lib/arrow/logger/apacheoutputter.rb +69 -0
- data/lib/arrow/logger/arrayoutputter.rb +63 -0
- data/lib/arrow/logger/coloroutputter.rb +111 -0
- data/lib/arrow/logger/fileoutputter.rb +96 -0
- data/lib/arrow/logger/htmloutputter.rb +54 -0
- data/lib/arrow/logger/outputter.rb +123 -0
- data/lib/arrow/mixins.rb +425 -0
- data/lib/arrow/monkeypatches.rb +94 -0
- data/lib/arrow/object.rb +117 -0
- data/lib/arrow/path.rb +196 -0
- data/lib/arrow/service.rb +447 -0
- data/lib/arrow/session.rb +289 -0
- data/lib/arrow/session/dbstore.rb +100 -0
- data/lib/arrow/session/filelock.rb +160 -0
- data/lib/arrow/session/filestore.rb +132 -0
- data/lib/arrow/session/id.rb +98 -0
- data/lib/arrow/session/lock.rb +253 -0
- data/lib/arrow/session/md5id.rb +42 -0
- data/lib/arrow/session/nulllock.rb +42 -0
- data/lib/arrow/session/posixlock.rb +166 -0
- data/lib/arrow/session/sha1id.rb +54 -0
- data/lib/arrow/session/store.rb +366 -0
- data/lib/arrow/session/usertrackid.rb +52 -0
- data/lib/arrow/spechelpers.rb +73 -0
- data/lib/arrow/template.rb +713 -0
- data/lib/arrow/template/attr.rb +31 -0
- data/lib/arrow/template/call.rb +31 -0
- data/lib/arrow/template/comment.rb +33 -0
- data/lib/arrow/template/container.rb +118 -0
- data/lib/arrow/template/else.rb +41 -0
- data/lib/arrow/template/elsif.rb +44 -0
- data/lib/arrow/template/escape.rb +53 -0
- data/lib/arrow/template/export.rb +87 -0
- data/lib/arrow/template/for.rb +145 -0
- data/lib/arrow/template/if.rb +78 -0
- data/lib/arrow/template/import.rb +119 -0
- data/lib/arrow/template/include.rb +206 -0
- data/lib/arrow/template/iterator.rb +208 -0
- data/lib/arrow/template/nodes.rb +734 -0
- data/lib/arrow/template/parser.rb +571 -0
- data/lib/arrow/template/prettyprint.rb +53 -0
- data/lib/arrow/template/render.rb +191 -0
- data/lib/arrow/template/selectlist.rb +94 -0
- data/lib/arrow/template/set.rb +87 -0
- data/lib/arrow/template/timedelta.rb +81 -0
- data/lib/arrow/template/unless.rb +78 -0
- data/lib/arrow/template/urlencode.rb +51 -0
- data/lib/arrow/template/yield.rb +139 -0
- data/lib/arrow/templatefactory.rb +125 -0
- data/lib/arrow/testcase.rb +567 -0
- data/lib/arrow/transaction.rb +608 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/documentation.rb +114 -0
- data/rake/helpers.rb +502 -0
- data/rake/hg.rb +282 -0
- data/rake/manual.rb +787 -0
- data/rake/packaging.rb +129 -0
- data/rake/publishing.rb +278 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/spec/arrow/acceptparam_spec.rb +157 -0
- data/spec/arrow/applet_spec.rb +575 -0
- data/spec/arrow/appletmixins_spec.rb +409 -0
- data/spec/arrow/appletregistry_spec.rb +294 -0
- data/spec/arrow/broker_spec.rb +153 -0
- data/spec/arrow/config_spec.rb +224 -0
- data/spec/arrow/cookieset_spec.rb +164 -0
- data/spec/arrow/dispatcher_spec.rb +137 -0
- data/spec/arrow/dispatcherloader_spec.rb +65 -0
- data/spec/arrow/formvalidator_spec.rb +781 -0
- data/spec/arrow/logger_spec.rb +346 -0
- data/spec/arrow/mixins_spec.rb +120 -0
- data/spec/arrow/service_spec.rb +645 -0
- data/spec/arrow/session_spec.rb +121 -0
- data/spec/arrow/template/iterator_spec.rb +222 -0
- data/spec/arrow/templatefactory_spec.rb +185 -0
- data/spec/arrow/transaction_spec.rb +319 -0
- data/spec/arrow_spec.rb +37 -0
- data/spec/lib/appletmatchers.rb +281 -0
- data/spec/lib/constants.rb +77 -0
- data/spec/lib/helpers.rb +41 -0
- data/spec/lib/matchers.rb +44 -0
- data/tests/cookie.tests.rb +310 -0
- data/tests/path.tests.rb +157 -0
- data/tests/session.tests.rb +111 -0
- data/tests/session_id.tests.rb +82 -0
- data/tests/session_lock.tests.rb +191 -0
- data/tests/session_store.tests.rb +53 -0
- data/tests/template.tests.rb +1360 -0
- metadata +339 -0
data/lib/arrow/broker.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'arrow/mixins'
|
4
|
+
require 'arrow/exceptions'
|
5
|
+
require 'arrow/object'
|
6
|
+
require 'arrow/cache'
|
7
|
+
require 'arrow/templatefactory'
|
8
|
+
require 'arrow/appletregistry'
|
9
|
+
|
10
|
+
|
11
|
+
# The broker is the applet manager. It maintains a registry of applets, and delegates
|
12
|
+
# transactions based on the request's URI.
|
13
|
+
#
|
14
|
+
# == Authors
|
15
|
+
#
|
16
|
+
# * Michael Granger <mgranger@RubyCrafters.com>
|
17
|
+
#
|
18
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
19
|
+
#
|
20
|
+
class Arrow::Broker < Arrow::Object
|
21
|
+
|
22
|
+
### Class constants/methods
|
23
|
+
|
24
|
+
# A regular expression that matches the file separator on this system
|
25
|
+
FILE_SEPARATOR = Regexp.new( Regexp.compile(File::SEPARATOR) )
|
26
|
+
|
27
|
+
|
28
|
+
### Create a new Arrow::Broker object from the specified +config+ (an
|
29
|
+
### Arrow::Config object).
|
30
|
+
def initialize( config )
|
31
|
+
@config = config
|
32
|
+
@registry = Arrow::AppletRegistry.new( config )
|
33
|
+
@start_time = Time.now
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
######
|
38
|
+
public
|
39
|
+
######
|
40
|
+
|
41
|
+
# The Hash of RegistryEntry structs keyed by uri
|
42
|
+
attr_accessor :registry
|
43
|
+
|
44
|
+
# The Time when the Broker was started
|
45
|
+
attr_reader :start_time
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
### Dispatch the specified transaction +txn+ to the appropriate handler
|
50
|
+
### based on the request's path_info.
|
51
|
+
def delegate( txn )
|
52
|
+
rval = appletchain = nil
|
53
|
+
self.log.debug "Start of delegation (%s)" % [ txn.unparsed_uri ]
|
54
|
+
|
55
|
+
# Fetch the path and trim the leading '/'
|
56
|
+
path = txn.path
|
57
|
+
path.sub!( %r{^/}, '' )
|
58
|
+
self.log.debug "Request's path is %p" % path
|
59
|
+
|
60
|
+
# Check for updated/deleted/added applets
|
61
|
+
@registry.check_for_updates
|
62
|
+
|
63
|
+
# Get the chain of applets to execute for the request
|
64
|
+
appletchain = @registry.find_applet_chain( path )
|
65
|
+
|
66
|
+
# If the pathinfo doesn't correspond to at least one applet, run
|
67
|
+
# the no-such-applet handler.
|
68
|
+
if appletchain.empty?
|
69
|
+
rval = self.run_missing_applet_handler( txn, path )
|
70
|
+
else
|
71
|
+
rval = self.run_applet_chain( txn, appletchain )
|
72
|
+
end
|
73
|
+
|
74
|
+
# Set the request status to declined if it hasn't been set yet and
|
75
|
+
# the return value is false.
|
76
|
+
if !rval
|
77
|
+
self.log.error "Applet returned false value. " +
|
78
|
+
"Setting status to DECLINED"
|
79
|
+
txn.status = Apache::DECLINED
|
80
|
+
end
|
81
|
+
|
82
|
+
# self.log.debug "Returning %p" % [ rval ]
|
83
|
+
return rval
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
### Run the specified +applet+ with the given +txn+ (an Arrow::Transaction)
|
88
|
+
### and the +rest+ of the path_info split on '/'.
|
89
|
+
def run_applet( applet, txn, rest )
|
90
|
+
self.log.debug "Running '%s' with args: %p" %
|
91
|
+
[ applet.signature.name, rest ]
|
92
|
+
return applet.run( txn, *rest )
|
93
|
+
rescue ::Exception => err
|
94
|
+
self.log.error "[%s]: Error running %s (%s): %s:\n\t%s" % [
|
95
|
+
txn.serial,
|
96
|
+
applet.signature.name,
|
97
|
+
applet.class.filename,
|
98
|
+
err.message,
|
99
|
+
err.backtrace.join("\n\t"),
|
100
|
+
]
|
101
|
+
return self.run_error_handler( applet, txn, err )
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
### Handle requests that target an applet that doesn't exist.
|
106
|
+
def run_missing_applet_handler( txn, uri )
|
107
|
+
rval = appletchain = nil
|
108
|
+
handlerUri = @config.applets.missingApplet
|
109
|
+
args = uri.split( %r{/} )
|
110
|
+
|
111
|
+
# Build an applet chain for user-configured handlers
|
112
|
+
if handlerUri != "(builtin)"
|
113
|
+
appletchain = @registry.find_applet_chain( handlerUri )
|
114
|
+
self.log.error "Configured MissingApplet handler (%s) doesn't exist" %
|
115
|
+
handlerUri if appletchain.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
# If the user-configured handler maps to one or more handlers, run
|
119
|
+
# them. Otherwise, run the build-in handler.
|
120
|
+
unless appletchain.nil? || appletchain.empty?
|
121
|
+
rval = self.run_applet_chain( txn, appletchain )
|
122
|
+
else
|
123
|
+
rval = self.builtin_missing_handler( txn, *args )
|
124
|
+
end
|
125
|
+
|
126
|
+
return rval
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
### Handle the given applet error +err+ for the specified +applet+, using
|
131
|
+
### the given transaction +txn+. This will attempt to run whatever applet
|
132
|
+
### is configured as the error-handler, or run a builtin handler applet
|
133
|
+
### if none is configured or the configured one isn't loaded.
|
134
|
+
def run_error_handler( applet, txn, err )
|
135
|
+
rval = nil
|
136
|
+
handlerName = @config.applets.errorApplet.sub( %r{^/}, '' )
|
137
|
+
|
138
|
+
unless handlerName == "(builtin)" or !@registry.key?( handlerName )
|
139
|
+
handler = @registry[handlerName]
|
140
|
+
self.log.notice "Running error handler applet '%s' (%s)" %
|
141
|
+
[ handler.signature.name, handlerName ]
|
142
|
+
|
143
|
+
begin
|
144
|
+
rval = handler.run( txn, "report_error", applet, err )
|
145
|
+
rescue ::Exception => err2
|
146
|
+
self.log.error "Error while attempting to use custom error "\
|
147
|
+
"handler '%s': %s\n\t%s" % [
|
148
|
+
handler.signature.name,
|
149
|
+
err2.message,
|
150
|
+
err2.backtrace.join("\n\t"),
|
151
|
+
]
|
152
|
+
|
153
|
+
rval = self.builtin_error_handler( applet, txn, err )
|
154
|
+
end
|
155
|
+
else
|
156
|
+
rval = self.builtin_error_handler( applet, txn, err )
|
157
|
+
end
|
158
|
+
|
159
|
+
return rval
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
#########
|
164
|
+
protected
|
165
|
+
#########
|
166
|
+
|
167
|
+
### Given a chain of applets built from a URI, run the +index+th one with
|
168
|
+
### the specified transaction (+txn+). Applets before the last get called
|
169
|
+
### via their #delegate method, while the last one is called via #run.
|
170
|
+
def run_applet_chain( txn, chain )
|
171
|
+
self.log.debug "Running applet chain: #{chain.inspect}"
|
172
|
+
raise Arrow::AppletError, "Malformed applet chain" if
|
173
|
+
chain.empty? || !chain.first.respond_to?( :applet )
|
174
|
+
|
175
|
+
res = nil
|
176
|
+
applet, txn.applet_path, args = self.unwrap_chain_link( chain.first )
|
177
|
+
|
178
|
+
# If there's only one item left, run it
|
179
|
+
if chain.nitems == 1
|
180
|
+
self.log.debug "Running final applet in chain"
|
181
|
+
res = self.run_applet( applet, txn, args )
|
182
|
+
|
183
|
+
# Otherwise, delegate the transaction to the next applet with the
|
184
|
+
# remainder of the chain.
|
185
|
+
else
|
186
|
+
dchain = chain[ 1..-1 ]
|
187
|
+
self.log.debug "Running applet %s in chain of %d; chain = %p" %
|
188
|
+
[ applet.signature.name, chain.nitems, dchain ]
|
189
|
+
|
190
|
+
begin
|
191
|
+
res = applet.delegate( txn, dchain, *args ) do |subchain|
|
192
|
+
subchain = dchain if subchain.nil?
|
193
|
+
self.log.debug "Delegated call to appletchain %p" % [ subchain ]
|
194
|
+
self.run_applet_chain( txn, subchain )
|
195
|
+
end
|
196
|
+
rescue ::Exception => err
|
197
|
+
self.log.error "Error while executing applet chain: %p (/%s): %s:\n\t%s" % [
|
198
|
+
applet,
|
199
|
+
chain.first[1],
|
200
|
+
err.message,
|
201
|
+
err.backtrace.join("\n\t"),
|
202
|
+
]
|
203
|
+
res = self.run_error_handler( applet, txn, err )
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
return res
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
### Check the specified +link+ of an applet chain for sanity and return
|
212
|
+
### its constituent bits for assignment. This is necessary to provide
|
213
|
+
### sensible errors if a delegating app screws up a chain somehow.
|
214
|
+
def unwrap_chain_link( link )
|
215
|
+
applet = link.applet or raise Arrow::AppletChainError, "Null applet"
|
216
|
+
path = link.path or raise Arrow::AppletChainError, "Null path"
|
217
|
+
args = link.args or raise Arrow::AppletChainError, "Null argument list"
|
218
|
+
unless args.is_a?( Array )
|
219
|
+
emsg = "Argument list is a %s: expected an Array" %
|
220
|
+
args.class.name
|
221
|
+
raise Arrow::AppletChainError, emsg
|
222
|
+
end
|
223
|
+
|
224
|
+
return applet, path, args
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
### The builtin missing-applet handler routine. Returns +false+, which
|
229
|
+
### causes the dispatcher to decline the request.
|
230
|
+
def builtin_missing_handler( txn, *args )
|
231
|
+
self.log.notice "Using builtin missing-applet handler."
|
232
|
+
return false
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
### The builtin error handler routine. Outputs a plain-text backtrace
|
237
|
+
### for the given exception +err+ and +applet+ to the given
|
238
|
+
### transaction +txn+.
|
239
|
+
def builtin_error_handler( applet, txn, err )
|
240
|
+
self.log.notice "Using builtin error handler."
|
241
|
+
txn.request.content_type = "text/plain"
|
242
|
+
txn.status = Apache::OK
|
243
|
+
|
244
|
+
return "Arrow Applet Error in '%s': %s\n\t%s" %
|
245
|
+
[ applet.class.signature.name, err.message, err.backtrace.join("\n\t") ]
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
|
250
|
+
end # class Arrow::Broker
|
251
|
+
|
252
|
+
|
253
|
+
__END__
|
254
|
+
|
255
|
+
|
data/lib/arrow/cache.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cache'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
require 'arrow/logger'
|
7
|
+
require 'arrow/mixins'
|
8
|
+
|
9
|
+
|
10
|
+
#
|
11
|
+
# A derivative of the Cache class from the ruby-cache module. It adds a few
|
12
|
+
# convenience and introspection methods to its parent.
|
13
|
+
#
|
14
|
+
# Instances of this class are LRU caches for disk-based objects which keep
|
15
|
+
# track of the cached object's modification time, expiring the cached
|
16
|
+
# version when the disk-based version changes (e.g., for template caching).
|
17
|
+
#
|
18
|
+
# == Authors
|
19
|
+
#
|
20
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
21
|
+
#
|
22
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
23
|
+
#
|
24
|
+
class Arrow::Cache < ::Cache
|
25
|
+
extend Forwardable
|
26
|
+
include Arrow::Loggable
|
27
|
+
|
28
|
+
# Default configuration values
|
29
|
+
DefaultConfig = {
|
30
|
+
:maxNum => 10,
|
31
|
+
:maxObjSize => nil,
|
32
|
+
:maxSize => nil,
|
33
|
+
:expiration => 3600,
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
#############################################################
|
38
|
+
### C L A S S M E T H O D S
|
39
|
+
#############################################################
|
40
|
+
|
41
|
+
@extent = []
|
42
|
+
class << self
|
43
|
+
attr_reader :extent
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
#############################################################
|
48
|
+
### I N S T A N C E M E T H O D S
|
49
|
+
#############################################################
|
50
|
+
|
51
|
+
### Create a new cache. This merges the DefaultConfig with the specified
|
52
|
+
### values and transforms camelCased keys into under_barred ones.
|
53
|
+
def initialize( name, config={}, &cleanup )
|
54
|
+
@name = name
|
55
|
+
|
56
|
+
# Merge defaults and specified values
|
57
|
+
merged = DefaultConfig.merge( config )
|
58
|
+
|
59
|
+
# Transform the config hash into the form the superclass expects
|
60
|
+
merged.each_key do |key|
|
61
|
+
lckey = key.to_s.gsub( /(.)([A-Z])/ ) {|match|
|
62
|
+
match[0,1] + "_" + match[1,1].downcase
|
63
|
+
}.to_sym
|
64
|
+
|
65
|
+
next if key == lckey
|
66
|
+
merged[ lckey ] = merged.delete( key )
|
67
|
+
end
|
68
|
+
|
69
|
+
# Register this instance with the class for introspection (costs
|
70
|
+
# much less than ObjectSpace.each_object).
|
71
|
+
obj = super( merged, &cleanup )
|
72
|
+
self.class.extent << obj
|
73
|
+
|
74
|
+
return obj
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
######
|
79
|
+
public
|
80
|
+
######
|
81
|
+
|
82
|
+
# Delegate some methods to the underlying hash
|
83
|
+
def_delegators :@objs, :length, :keys, :values
|
84
|
+
|
85
|
+
|
86
|
+
# The name of the cache; used in introspection
|
87
|
+
attr_reader :name
|
88
|
+
|
89
|
+
# Total count of cache hits
|
90
|
+
attr_reader :hits
|
91
|
+
|
92
|
+
# Total count of cache misses
|
93
|
+
attr_reader :misses
|
94
|
+
|
95
|
+
# Cache size in bytes
|
96
|
+
attr_reader :size
|
97
|
+
|
98
|
+
# The list of cached objects
|
99
|
+
attr_reader :list
|
100
|
+
|
101
|
+
|
102
|
+
### Overridden to provide logging of invalidated keys.
|
103
|
+
def invalidate( key )
|
104
|
+
self.log.debug "invalidating cache key '%p' for %s" % [key, self.name]
|
105
|
+
super
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
### Overridden for logging.
|
110
|
+
def invalidate_all
|
111
|
+
self.log.debug "invalidating all cached objects for %s" % [self.name]
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
### Overridden to provide logging of expire phase.
|
117
|
+
def expire
|
118
|
+
self.log.debug "looking for expired entries in %s" % [self.name]
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
### Overridden from the superclass to prevent .to_s from being called on
|
124
|
+
### objects to determine their size if the object supports a #memsize
|
125
|
+
### method. This is mostly to stop templates from being rendered every
|
126
|
+
### time they're cached.
|
127
|
+
def []=( key, obj )
|
128
|
+
self.expire
|
129
|
+
|
130
|
+
self.invalidate( key ) if self.cached?( key )
|
131
|
+
|
132
|
+
if obj.respond_to?( :memsize )
|
133
|
+
size = obj.memsize
|
134
|
+
else
|
135
|
+
size = obj.to_s.size
|
136
|
+
end
|
137
|
+
|
138
|
+
# Test against size threshold
|
139
|
+
if @max_obj_size && size > @max_obj_size
|
140
|
+
Arrow::Logger[self.class].debug \
|
141
|
+
"%p not cached: size exceeds maxObjSize: %d" %
|
142
|
+
[ obj, @max_obj_size ]
|
143
|
+
return obj
|
144
|
+
end
|
145
|
+
if @max_obj_size.nil? && @max_size && size > @max_size
|
146
|
+
Arrow::Logger[self.class].debug \
|
147
|
+
"%p not cached: size exceeds maxSize: %d" %
|
148
|
+
[ obj, @max_size ]
|
149
|
+
return obj
|
150
|
+
end
|
151
|
+
|
152
|
+
if @max_num && @list.size >= @max_num
|
153
|
+
Arrow::Logger[self.class].debug \
|
154
|
+
"Dropping %p from the cache: count exceeds maxNum: %d" %
|
155
|
+
[ @list.first, @max_num ]
|
156
|
+
self.invalidate( @list.first )
|
157
|
+
end
|
158
|
+
|
159
|
+
@size += size
|
160
|
+
if @max_size
|
161
|
+
while @size > @max_size
|
162
|
+
Arrow::Logger[self.class].debug \
|
163
|
+
"Dropping %p from the cache: size exceeds maxSize: %d" %
|
164
|
+
[ @list.first, @max_size ]
|
165
|
+
self.invalidate( @list.first )
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
@objs[ key ] = Cache::CACHE_OBJECT.new( obj, size, Time.now.to_i )
|
170
|
+
@list.push( key )
|
171
|
+
|
172
|
+
return obj
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
end # class Arrow::Cache
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'arrow/config'
|
6
|
+
require 'arrow/path'
|
7
|
+
require 'arrow/constants'
|
8
|
+
|
9
|
+
|
10
|
+
# The Arrow::Config::YamlLoader class, a derivative of Arrow::Config::Loader. It
|
11
|
+
# is used to load configuration files written in YAML for the Arrow web
|
12
|
+
# application framework.
|
13
|
+
#
|
14
|
+
# == Authors
|
15
|
+
#
|
16
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
17
|
+
#
|
18
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
19
|
+
#
|
20
|
+
class Arrow::Config::YamlLoader < Arrow::Config::Loader
|
21
|
+
include Arrow::Constants
|
22
|
+
|
23
|
+
|
24
|
+
# Add YAML domain types for Arrow classes
|
25
|
+
|
26
|
+
YAML.add_domain_type( YAML_DOMAIN, "arrowPath" ) do |type, val|
|
27
|
+
obj = nil
|
28
|
+
case val
|
29
|
+
when Array
|
30
|
+
Arrow::Logger.debug "Adding %p to loaded Arrow::Path" % [ val ]
|
31
|
+
obj = Arrow::Path.new( val )
|
32
|
+
else
|
33
|
+
raise "Invalid #{type}: %p" % val
|
34
|
+
end
|
35
|
+
|
36
|
+
obj
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
######
|
42
|
+
public
|
43
|
+
######
|
44
|
+
|
45
|
+
### Load and return configuration values from the YAML +file+
|
46
|
+
### specified.
|
47
|
+
def load( filename )
|
48
|
+
self.log.info "Loading YAML-format configuration from '%s'" % filename
|
49
|
+
return YAML.load_file( filename )
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
### Save configuration values to the YAML +file+ specified.
|
54
|
+
def save( confighash, filename )
|
55
|
+
self.log.info "Saving YAML-format configuration to '%s'" % filename
|
56
|
+
File.open( filename, File::WRONLY|File::CREAT|File::TRUNC ) {|ofh|
|
57
|
+
ofh.print( confighash.to_yaml )
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
### Return +true+ if the specified +file+ is newer than the given
|
63
|
+
### +time+.
|
64
|
+
def is_newer?( file, time )
|
65
|
+
return false unless File.exists?( file )
|
66
|
+
st = File.stat( file )
|
67
|
+
self.log.debug "File mtime is: %s, comparison time is: %s" %
|
68
|
+
[ st.mtime, time ]
|
69
|
+
return st.mtime > time
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end # class Arrow::Config::YamlLoader
|
74
|
+
|
75
|
+
|