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.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. metadata +339 -0
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'uri'
4
+ require 'pluginfactory'
5
+ require 'configurability'
6
+
7
+ require 'arrow/object'
8
+ require 'arrow/exceptions'
9
+ require 'arrow/mixins'
10
+ require 'arrow/logger'
11
+ require 'arrow/config'
12
+
13
+
14
+ # The Arrow::Session class, a container for maintaining state across multiple
15
+ # transactions.
16
+ #
17
+ # == Authors
18
+ #
19
+ # * Michael Granger <ged@FaerieMUD.org>
20
+ #
21
+ # Please see the file LICENSE in the top-level directory for licensing details.
22
+ #
23
+ class Arrow::Session < Arrow::Object
24
+ include PluginFactory,
25
+ Enumerable
26
+ extend Configurability
27
+
28
+ config_key :session
29
+
30
+ require 'arrow/session/store'
31
+ require 'arrow/session/lock'
32
+ require 'arrow/session/id'
33
+
34
+
35
+
36
+ #############################################################
37
+ ### C L A S S M E T H O D S
38
+ #############################################################
39
+
40
+ require 'arrow/config'
41
+ @config = Arrow::Config.new.session
42
+ class << self
43
+ attr_reader :config
44
+ end
45
+
46
+ ### Parse the given string into a URI object, appending the path part if
47
+ ### it doesn't exist.
48
+ def self::parse_uri( str )
49
+ return str if str.is_a?( URI::Generic )
50
+ str += ":." if /^\w+$/ =~ str
51
+ URI.parse( str )
52
+ end
53
+
54
+
55
+ ### Configure the session class's factory with the given Arrow::Config
56
+ ### object.
57
+ def self::configure( config )
58
+ @config = config.dup
59
+ Arrow::Logger[self].debug "Done. Session config is: %p" % @config
60
+ end
61
+
62
+
63
+ ### Create a new session for the specified +request+.
64
+ def self::create( txn, configHash={} )
65
+ # Merge the incoming config with the factory's
66
+ sconfig = @config.merge( configHash )
67
+ Arrow::Logger[self].debug "Merged config is: %p" % sconfig
68
+
69
+ # Create a new id and backing store object
70
+ idobj = self.create_id( sconfig, txn )
71
+ store = self.create_store( sconfig, idobj )
72
+ lock = self.create_lock( sconfig, store, idobj )
73
+
74
+ # Create the session cookie
75
+ scookie = self.create_session_cookie( txn, sconfig, idobj, store, lock )
76
+
77
+ return new( idobj, lock, store, txn, scookie )
78
+ end
79
+
80
+
81
+ ### Set the session cookie if we're really running under Apache.
82
+ def self::create_session_cookie( txn, config, id, store, lock )
83
+ scookie = Arrow::Cookie.new(
84
+ config.idName,
85
+ id.to_s,
86
+ :expires => config.expires,
87
+ :path => '/'
88
+ )
89
+
90
+ Arrow::Logger[self].debug "Created cookie: %p" % scookie.to_s
91
+ return scookie
92
+ end
93
+
94
+
95
+ ### Create an Arrow::Session::Id object for the given +txn+, with the
96
+ ### particulars dictated by the specified +config+.
97
+ def self::create_id( config, txn )
98
+ cookie_name = config.idName
99
+
100
+ # Fetch the id from the request, either from the session cookie or
101
+ # as a parameter if the cookie doesn't exist.
102
+ if txn.request_cookies.include?( cookie_name )
103
+ Arrow::Logger[self].debug "Found an existing session cookie (%s)" %
104
+ [ cookie_name ]
105
+ idstring = txn.request_cookies[ cookie_name ].value
106
+ else
107
+ Arrow::Logger[self].debug \
108
+ "No existing session cookie (%s); looking for one in a request parameter" %
109
+ [ cookie_name]
110
+ idstring = txn.param( cookie_name )
111
+ end
112
+
113
+ Arrow::Logger[self].debug "Creating a session id object: %p" % config.idType
114
+ return Arrow::Session::Id.create( config.idType, txn.request, idstring )
115
+ end
116
+
117
+
118
+ ### Create an Arrow::Session::Store object with the given +id+. The type
119
+ ### and configuration of the store will be dictated by the specified
120
+ ### +config+ object.
121
+ def self::create_store( config, id )
122
+ Arrow::Logger[self].debug "Creating a session store: %p" % config.storeType
123
+ return Arrow::Session::Store.create( config.storeType, id )
124
+ end
125
+
126
+
127
+ ### Create an Arrow::Session::Lock object for the specified +store+ and +id+.
128
+ def self::create_lock( config, store, id )
129
+
130
+ lockuri = Arrow::Session.parse_uri( config.lockType )
131
+ lock = nil
132
+
133
+ # If the configuration says to use the recommended locker, ask the
134
+ # backing store for a lock object.
135
+ if lockuri.scheme == 'recommended'
136
+ Arrow::Logger[self].debug "Creating recommended lock"
137
+ lock = store.create_recommended_lock( id ) or
138
+ raise Arrow::SessionError, "No recommended locker for %s" %
139
+ store.class.name
140
+ else
141
+ Arrow::Logger[self].debug "Creating a session lock: %p" % lockuri
142
+ lock = Arrow::Session::Lock.create( lockuri, id )
143
+ end
144
+
145
+ return lock
146
+ end
147
+
148
+
149
+ ### Return the configured name of the session cookie.
150
+ def self::session_cookie_name
151
+ return @config.idName
152
+ end
153
+
154
+
155
+
156
+ #########
157
+ protected
158
+ #########
159
+
160
+ ### Create delegators that readlock the session store before accessing
161
+ ### it.
162
+ def self::def_delegated_readers( *syms )
163
+ syms.each do |sym|
164
+ define_method( sym ) do |*args|
165
+ @lock.read_lock
166
+ @store.send( sym, *args )
167
+ end
168
+ end
169
+ end
170
+
171
+
172
+ ### Create delegators that writelock the session store before accessing
173
+ ### it.
174
+ def self::def_delegated_writers( *syms )
175
+ syms.each do |sym|
176
+ define_method( sym ) do |*args|
177
+ @lock.write_lock
178
+ @store.send( sym, *args )
179
+ end
180
+ end
181
+ end
182
+
183
+
184
+
185
+ #############################################################
186
+ ### I N S T A N C E M E T H O D S
187
+ #############################################################
188
+
189
+ ### Create a new Arrow::Session object for the given +idobj+ (an
190
+ ### Arrow::Session::Id object), using the given +lock+ (an
191
+ ### Arrow::Session::Locker object) for serializing access.
192
+ def initialize( idobj, lock, store, txn, cookie=nil )
193
+ raise ArgumentError, "No id object" unless idobj
194
+ raise ArgumentError, "No lock object" unless lock
195
+ raise ArgumentError, "No store object" unless store
196
+
197
+ self.log.debug "Initializing session with id: %p, lock: %p, store: %p" %
198
+ [ idobj, lock, store ]
199
+
200
+ @id = idobj
201
+ @lock = lock
202
+ @store = store
203
+ @txn = txn
204
+ @cookie = cookie
205
+
206
+ @store[ :_session_id ] = id.to_s
207
+ end
208
+
209
+
210
+ ######
211
+ public
212
+ ######
213
+
214
+ # The session's unique identifier, an Apache::Session::Id object.
215
+ attr_reader :id
216
+
217
+ # The session's backing store; an Apache::Session::Store object.
218
+ attr_reader :store
219
+
220
+ # The session's lock object; an Apache::Session::Lock.
221
+ attr_reader :lock
222
+
223
+ # The Apache::Cookie object used to manipulate the session cookie.
224
+ attr_reader :cookie
225
+
226
+
227
+ ### Delete the session
228
+ def remove
229
+ @lock.with_write_lock do
230
+ @store.remove
231
+ end
232
+ @lock.release_all_locks
233
+ @cookie.expires = Time.at(0)
234
+ end
235
+
236
+
237
+ ### Clear all data from the session object.
238
+ def clear
239
+ @lock.with_write_lock do
240
+ @store.clear
241
+ @store[ :_session_id ] = @id.to_s
242
+ end
243
+ end
244
+
245
+
246
+ ### Enumerable iterface: iterate over the session's key/value pairs,
247
+ ### calling the given block once for each pair.
248
+ def each( &block )
249
+ raise LocalJumpError, "no block given" unless block
250
+ @lock.read_lock
251
+ @store.each( &block )
252
+ end
253
+
254
+
255
+ ### Save the session to fixed storage and set the session cookie in the
256
+ ### creating transaction's outgoing headers.
257
+ def save
258
+ begin
259
+ self.log.debug "Saving session data"
260
+ @store.save
261
+ self.log.debug "Writing session cookie (%p)" % [ @cookie ]
262
+ @txn.cookies[ self.class.session_cookie_name ] = @cookie
263
+ ensure
264
+ self.log.debug "Releasing all locks"
265
+ @lock.release_all_locks
266
+ end
267
+ end
268
+
269
+
270
+ ### Tell the session that it will not be used again in the current
271
+ ### session.
272
+ def finish
273
+ @lock.release_all_locks
274
+ end
275
+
276
+
277
+ ### Delegate the common hash methods to the session store, which
278
+ ### re-delegates them to its data.
279
+ def_delegated_readers :[], :default, :default=, :each, :each_key,
280
+ :each_pair, :each_value, :empty?, :fetch, :has_key?, :has_value?,
281
+ :include?, :index, :invert, :key?, :keys, :length, :member?, :merge,
282
+ :rehash, :reject, :select, :size, :sort, :to_a, :value?, :values
283
+
284
+ def_delegated_writers :[]=, :delete, :clear, :merge!, :replace,
285
+ :delete_if, :reject!
286
+
287
+ end # class Arrow::Session
288
+
289
+
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sequel'
4
+
5
+ require 'arrow/exceptions'
6
+ require 'arrow/session/store'
7
+
8
+ # The Arrow::Session::DbStore class, a derivative of Arrow::Session::Store.
9
+ # Instances of this class store a session object in a database.
10
+ #
11
+ # == Authors
12
+ #
13
+ # * Michael Granger <ged@FaerieMUD.org>
14
+ #
15
+ # :include: LICENSE
16
+ #
17
+ #--
18
+ #
19
+ # Please see the file LICENSE in the BASE directory for licensing details.
20
+ #
21
+ class Arrow::Session::DbStore < Arrow::Session::Store
22
+
23
+
24
+ #################################################################
25
+ ### I N S T A N C E M E T H O D S
26
+ #################################################################
27
+
28
+ ### Create a new Arrow::Session::DbStore object.
29
+ def initialize( uri, idobj )
30
+
31
+ db = Sequel.connect( uri )
32
+ dataset = db[]
33
+ @id = idobj
34
+ super
35
+ end
36
+
37
+
38
+ ######
39
+ public
40
+ ######
41
+
42
+ # The database handle
43
+ attr_reader :db
44
+
45
+ # The session ID this store was created for
46
+ attr_reader :id
47
+
48
+
49
+ ### Insert the specified +data+ hash into whatever permanent storage the
50
+ ### Store object is acting as an interface to.
51
+ def insert
52
+ super {|data|
53
+ self.log.debug "Inserting data into session table for session %s" % [ self.id ]
54
+ self.
55
+ }
56
+ end
57
+
58
+
59
+ ### Update the current data hash stored in permanent storage with the
60
+ ### values contained in +data+.
61
+ def update
62
+ super {|data|
63
+ self.log.debug "Updating data in session file"
64
+ ofh = self.open
65
+ ofh.seek( 0, File::SEEK_SET )
66
+ ofh.print( data )
67
+ }
68
+ end
69
+
70
+
71
+ ### Retrieve the data hash stored in permanent storage associated with
72
+ ### the id the object was created with.
73
+ def retrieve
74
+ super {
75
+ self.log.debug "Reading data in session file"
76
+ ofh = self.open( File::RDWR )
77
+ ofh.seek( 0, File::SEEK_SET )
78
+ ofh.read
79
+ }
80
+ end
81
+
82
+
83
+ ### Permanently remove the data hash associated with the id used in the
84
+ ### receiver's creation from permanent storage.
85
+ def remove
86
+ super
87
+ self.close
88
+ file = self.session_file
89
+ if File.exists?( file )
90
+ File.delete( file )
91
+ else
92
+ raise Arrow::SessionError,
93
+ "Session file #{file} does not exist in the data store"
94
+ end
95
+ end
96
+
97
+
98
+ end # class Arrow::Session::DbStore
99
+
100
+
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ftools'
4
+
5
+ require 'arrow/session/lock'
6
+
7
+ # The Arrow::Session::FileLock class, a derivative of Arrow::Session::Lock.
8
+ # Instances of this class provide file-based locking for Arrow sessions
9
+ # using the flock(2) system call. It (obviously) won't work on platforms
10
+ # which don't support flock(2), or on filesystems which don't provide
11
+ # flock-based locking semantics (e.g., NFS).
12
+ #
13
+ # == Authors
14
+ #
15
+ # * Michael Granger <ged@FaerieMUD.org>
16
+ #
17
+ # Please see the file LICENSE in the top-level directory for licensing details.
18
+ #
19
+ class Arrow::Session::FileLock < Arrow::Session::Lock
20
+
21
+ # The path to the default lockdir
22
+ DefaultLockDir = '/tmp'
23
+
24
+ # The format string that will be used for the name of the lock file. The
25
+ # first '%s' will be replaced with a sanitized version of the session
26
+ # id.
27
+ LockfileFormat = "arrow-session-%s.lock"
28
+
29
+ # The mode to open the lockfile in
30
+ FileMode = File::RDWR|File::CREAT
31
+
32
+
33
+ #############################################################
34
+ ### C L A S S M E T H O D S
35
+ #############################################################
36
+
37
+ ### Clean the specified +directory+ of lock files older than +threshold+
38
+ ### seconds.
39
+ def self::clean( directory=DefaultLockDir, threshold=3600 )
40
+ pat = File.join( directory, LockfileFormat.gsub(/%s/, '*') )
41
+ threshold = Time.now - threshold
42
+ Dir[ pat ].each do |file|
43
+ if File.mtime( file ) < threshold
44
+ Arrow::Logger[self].info \
45
+ "Removing stale lockfile '%s'" % file
46
+ begin
47
+ fh = File.open( file, FileMode )
48
+ fh.flock( File::LOCK_EX|File::LOCK_NB )
49
+ File.delete( file )
50
+ fh.flock( File::LOCK_UN )
51
+ fh.close
52
+ rescue => err
53
+ Arrow::Logger[self].warning \
54
+ "Could not clean up '%s': %s" %
55
+ [ file, err.message ]
56
+ next
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+
64
+ #############################################################
65
+ ### I N S T A N C E M E T H O D S
66
+ #############################################################
67
+
68
+ ### Create a new Arrow::Session::FileLock object.
69
+ def initialize( uri, id )
70
+ @lockDir = uri.path || DefaultLockDir
71
+ super
72
+
73
+ # 'foo de barg blag 0x1f2eca'.gsub( /\W/, '_' )
74
+ # => foo_de_barg_blag_0x1f2eca
75
+ lockfilename = LockfileFormat % id.to_s.gsub( /\W/, '_' )
76
+ File.mkpath( @lockDir )
77
+ @filename = File.join( @lockDir, lockfilename ).untaint
78
+ @lockfile = nil
79
+ end
80
+
81
+
82
+ ######
83
+ public
84
+ ######
85
+
86
+ # The path to the directory where session lockfiles are kept.
87
+ attr_accessor :lockDir
88
+
89
+
90
+ ### Indicate to the lock that the caller will no longer be using it, and
91
+ ### it may free any resources it had been using.
92
+ def finish
93
+ super
94
+ self.close_lock_file
95
+ end
96
+
97
+
98
+ #########
99
+ protected
100
+ #########
101
+
102
+ ### Get the File object for the lockfile belonging to this lock,
103
+ ### creating it if necessary.
104
+ def lockfile
105
+ @lockfile ||= File.open( @filename, FileMode )
106
+ end
107
+
108
+
109
+ ### Close the lockfile and destroy the File object belonging to this
110
+ ### lock.
111
+ def close_lock_file
112
+ if @lockfile
113
+ path = @lockfile.path
114
+ @lockfile.close
115
+ @lockfile = nil
116
+ File.delete( path.untaint ) if File.exist?( path.untaint )
117
+ end
118
+ end
119
+
120
+
121
+ ### Acquire a read (shared) lock on the lockfile.
122
+ def acquire_read_lock( blocking )
123
+ flags = File::LOCK_SH
124
+ flags |= File::LOCK_NB if !blocking
125
+
126
+ self.lockfile.flock( flags )
127
+ end
128
+
129
+
130
+ ### Acquire a write (exclusive) lock on the lockfile.
131
+ def acquire_write_lock( blocking )
132
+ flags = File::LOCK_EX
133
+ flags |= File::LOCK_NB if !blocking
134
+
135
+ self.lockfile.flock( flags )
136
+ end
137
+
138
+
139
+ ### Release a previously-acquired read lock.
140
+ def release_read_lock
141
+ if !self.write_locked?
142
+ self.lockfile.flock( File::LOCK_UN )
143
+ self.close_lock_file
144
+ end
145
+ end
146
+
147
+
148
+ ### Release a previously-acquired write lock.
149
+ def release_write_lock
150
+ if self.read_locked?
151
+ self.lockfile.flock( File::LOCK_SH )
152
+ else
153
+ self.lockfile.flock( File::LOCK_UN )
154
+ self.close_lock_file
155
+ end
156
+ end
157
+
158
+ end # class Arrow::Session::FileLock
159
+
160
+