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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
|
|
5
|
+
require 'arrow/session/id'
|
|
6
|
+
|
|
7
|
+
# The Arrow::Session::MD5Id class, a derivative of Arrow::Session::Id. Instances
|
|
8
|
+
# of this class are session IDs created by MD5-hashing some semi-random data.
|
|
9
|
+
#
|
|
10
|
+
# == Authors
|
|
11
|
+
#
|
|
12
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
13
|
+
#
|
|
14
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
15
|
+
#
|
|
16
|
+
class Arrow::Session::MD5Id < Arrow::Session::Id
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#############################################################
|
|
20
|
+
### C L A S S M E T H O D S
|
|
21
|
+
#############################################################
|
|
22
|
+
|
|
23
|
+
### Generate a new id
|
|
24
|
+
def self::generate( uri, request )
|
|
25
|
+
seed = [
|
|
26
|
+
Time.new.to_s,
|
|
27
|
+
Object.new.inspect,
|
|
28
|
+
rand(),
|
|
29
|
+
Process.pid,
|
|
30
|
+
].join
|
|
31
|
+
return Digest::MD5.hexdigest( Digest::MD5.hexdigest(seed) )
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
### Returns the validated id if the given id is in the expected form for
|
|
35
|
+
### this type, or +nil+ if it is not.
|
|
36
|
+
def self::validate( uri, idstr )
|
|
37
|
+
rval = idstr[/^([a-f0-9]{32})$/]
|
|
38
|
+
rval.untaint
|
|
39
|
+
return rval
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end # class Arrow::Session::MD5Id
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/session/lock'
|
|
4
|
+
|
|
5
|
+
# The Arrow::Session::NullLock class, a derivative of
|
|
6
|
+
# Arrow::Session::Lock. This is a null lock, in that it does not lock.
|
|
7
|
+
# This is to be used with an ActiveRecord session store that uses
|
|
8
|
+
# Optomistic Concurrency Control.
|
|
9
|
+
#
|
|
10
|
+
# == VCS Id
|
|
11
|
+
#
|
|
12
|
+
# $Id$
|
|
13
|
+
#
|
|
14
|
+
# == Authors
|
|
15
|
+
#
|
|
16
|
+
# * Jeremiah Jordan <phaedrus@FaerieMUD.org>
|
|
17
|
+
#
|
|
18
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
19
|
+
#
|
|
20
|
+
class Arrow::Session::NullLock < Arrow::Session::Lock
|
|
21
|
+
|
|
22
|
+
def initialize( uri, id )
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def acquire_read_lock(blocking)
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def acquire_write_lock(blocking)
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def release_read_lock
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def release_write_lock
|
|
39
|
+
true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'posixlock'
|
|
4
|
+
require 'ftools'
|
|
5
|
+
|
|
6
|
+
require 'arrow/session/lock'
|
|
7
|
+
|
|
8
|
+
# The Arrow::Session::PosixLock class, a derivative of
|
|
9
|
+
# Arrow::Session::Lock. This lock type uses the 'posixlock' library
|
|
10
|
+
# (http://raa.ruby-lang.org/project/posixlock/).
|
|
11
|
+
#
|
|
12
|
+
# == VCS Id
|
|
13
|
+
#
|
|
14
|
+
# $Id$
|
|
15
|
+
#
|
|
16
|
+
# == Authors
|
|
17
|
+
#
|
|
18
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
19
|
+
#
|
|
20
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
21
|
+
#
|
|
22
|
+
class Arrow::Session::PosixLock < Arrow::Session::Lock
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# The path to the default lockdir
|
|
26
|
+
DefaultLockDir = '/tmp'
|
|
27
|
+
|
|
28
|
+
# The format string that will be used for the name of the lock file. The
|
|
29
|
+
# first '%s' will be replaced with a sanitized version of the session
|
|
30
|
+
# id.
|
|
31
|
+
LockfileFormat = "arrow-session-%s.plock"
|
|
32
|
+
|
|
33
|
+
# The mode to open the lockfile in
|
|
34
|
+
FileMode = File::CREAT|File::RDWR
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#############################################################
|
|
38
|
+
### C L A S S M E T H O D S
|
|
39
|
+
#############################################################
|
|
40
|
+
|
|
41
|
+
### Clean the specified +directory+ of lock files older than +threshold+
|
|
42
|
+
### seconds.
|
|
43
|
+
def self::clean( directory=DefaultLockDir, threshold=3600 )
|
|
44
|
+
pat = File.join( directory, LockfileFormat.gsub(/%s/, '*') )
|
|
45
|
+
threshold = Time.now - threshold
|
|
46
|
+
Dir[ pat ].each do |file|
|
|
47
|
+
if File.mtime( file ) < threshold
|
|
48
|
+
Arrow::Logger[self].info \
|
|
49
|
+
"Removing stale lockfile '%s'" % file
|
|
50
|
+
begin
|
|
51
|
+
fh = File.open( file, FileMode )
|
|
52
|
+
fh.posixlock( File::LOCK_EX|File::LOCK_NB )
|
|
53
|
+
File.delete( file )
|
|
54
|
+
fh.posixlock( File::LOCK_UN )
|
|
55
|
+
fh.close
|
|
56
|
+
rescue => err
|
|
57
|
+
Arrow::Logger[self].warning \
|
|
58
|
+
"Could not clean up '%s': %s" %
|
|
59
|
+
[ file, err.message ]
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
#############################################################
|
|
69
|
+
### I N S T A N C E M E T H O D S
|
|
70
|
+
#############################################################
|
|
71
|
+
|
|
72
|
+
### Create a new Arrow::Session::FileLock object.
|
|
73
|
+
def initialize( uri, id )
|
|
74
|
+
@lockDir = uri.path || DefaultLockDir
|
|
75
|
+
super
|
|
76
|
+
|
|
77
|
+
File.mkpath( @lockDir )
|
|
78
|
+
@filename = File.join( @lockDir, LockfileFormat % id.to_s.gsub(/\W/, '_') ).untaint
|
|
79
|
+
self.log.debug "Filename is: #@filename"
|
|
80
|
+
@lockfile = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
######
|
|
85
|
+
public
|
|
86
|
+
######
|
|
87
|
+
|
|
88
|
+
# The path to the directory where session lockfiles are kept.
|
|
89
|
+
attr_accessor :lockDir
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
### Indicate to the lock that the caller will no longer be using it, and it
|
|
93
|
+
### may free any resources it had been using.
|
|
94
|
+
def finish
|
|
95
|
+
super
|
|
96
|
+
self.close_lock_file
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
#########
|
|
102
|
+
protected
|
|
103
|
+
#########
|
|
104
|
+
|
|
105
|
+
### Get the File object for the lockfile belonging to this lock,
|
|
106
|
+
### creating it if necessary.
|
|
107
|
+
def lockfile
|
|
108
|
+
@lockfile ||= File.open( @filename, FileMode )
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Close the lockfile and destroy the File object belonging to this
|
|
113
|
+
### lock.
|
|
114
|
+
def close_lock_file
|
|
115
|
+
if @lockfile
|
|
116
|
+
path = @lockfile.path
|
|
117
|
+
@lockfile.close
|
|
118
|
+
@lockfile = nil
|
|
119
|
+
if File.exist?( path.untaint )
|
|
120
|
+
File.delete( path.untaint )
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
### Acquire a read (shared) lock on the lockfile.
|
|
127
|
+
def acquire_read_lock( blocking )
|
|
128
|
+
flags = File::LOCK_SH
|
|
129
|
+
flags |= File::LOCK_NB if !blocking
|
|
130
|
+
|
|
131
|
+
self.lockfile.posixlock( flags )
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
### Acquire a write (exclusive) lock on the lockfile.
|
|
136
|
+
def acquire_write_lock( blocking )
|
|
137
|
+
flags = File::LOCK_EX
|
|
138
|
+
flags |= File::LOCK_NB if !blocking
|
|
139
|
+
|
|
140
|
+
self.lockfile.posixlock( flags )
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
### Release a previously-acquired read lock.
|
|
145
|
+
def release_read_lock
|
|
146
|
+
if !self.write_locked?
|
|
147
|
+
self.lockfile.posixlock( File::LOCK_UN )
|
|
148
|
+
self.close_lock_file
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
### Release a previously-acquired write lock.
|
|
154
|
+
def release_write_lock
|
|
155
|
+
if self.read_locked?
|
|
156
|
+
self.lockfile.posixlock( File::LOCK_SH )
|
|
157
|
+
else
|
|
158
|
+
self.lockfile.posixlock( File::LOCK_UN )
|
|
159
|
+
self.close_lock_file
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end # module Arrow::Session::PosixLock
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'digest/sha1'
|
|
4
|
+
|
|
5
|
+
require 'arrow/session/id'
|
|
6
|
+
|
|
7
|
+
# The Arrow::Session::SHA1Id class, a derivative of
|
|
8
|
+
# Arrow::Session::Id. Instances of this class are session IDs created by
|
|
9
|
+
# SHA1-hashing some semi-random data.
|
|
10
|
+
#
|
|
11
|
+
# == Synopsis
|
|
12
|
+
#
|
|
13
|
+
# # In arrow.conf:
|
|
14
|
+
# session:
|
|
15
|
+
# idType: sha1:.
|
|
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::SHA1Id < Arrow::Session::Id
|
|
24
|
+
|
|
25
|
+
# Default salt characters
|
|
26
|
+
DEFAULT_SALT = 'sadblkw456jbhgsdfi7283hnehonaseegop26m'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#############################################################
|
|
30
|
+
### C L A S S M E T H O D S
|
|
31
|
+
#############################################################
|
|
32
|
+
|
|
33
|
+
### Generate a new id
|
|
34
|
+
def self::generate( uri, request )
|
|
35
|
+
salt = uri.opaque || DEFAULT_SALT
|
|
36
|
+
seed = [
|
|
37
|
+
salt,
|
|
38
|
+
Time.new.to_s,
|
|
39
|
+
Object.new.inspect,
|
|
40
|
+
rand(),
|
|
41
|
+
Process.pid,
|
|
42
|
+
].join
|
|
43
|
+
return Digest::SHA1.hexdigest( Digest::SHA1.hexdigest(seed) )
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
### Returns the validated id if the given id is in the expected form for
|
|
47
|
+
### this type, or +nil+ if it is not.
|
|
48
|
+
def self::validate( uri, idstr )
|
|
49
|
+
rval = idstr[/^([a-f0-9]{40})$/]
|
|
50
|
+
rval.untaint
|
|
51
|
+
return rval
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end # class Arrow::Session::MD5Id
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'pluginfactory'
|
|
5
|
+
|
|
6
|
+
require 'arrow/mixins'
|
|
7
|
+
require 'arrow/exceptions'
|
|
8
|
+
require 'arrow/session'
|
|
9
|
+
require 'arrow/session/lock'
|
|
10
|
+
|
|
11
|
+
# The Arrow::Session::Store class, a derivative of Arrow::Object. Instances
|
|
12
|
+
# of concrete deriviatives of this class provide serialization and semi-permanent
|
|
13
|
+
# storage of session data for Arrow::Session objects.
|
|
14
|
+
#
|
|
15
|
+
# === Derivative Interface ===
|
|
16
|
+
#
|
|
17
|
+
# In order to create your own session store classes, you need to provide four
|
|
18
|
+
# methods: #insert, #update, #retrieve, #remove. All but one of the methods
|
|
19
|
+
# provides serialization and marking records as dirty in the base class, so
|
|
20
|
+
# unless you want to manage these tasks yourself, you should +super()+ to the
|
|
21
|
+
# parent's implementation with a block. Examples are provided for each method.
|
|
22
|
+
#
|
|
23
|
+
# #insert::
|
|
24
|
+
# Insert a new session into the backing store. Example:
|
|
25
|
+
# def insert
|
|
26
|
+
# super {|data| @io.print(data) }
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# #update::
|
|
30
|
+
# Update an existing session's data in the backing store. Example:
|
|
31
|
+
# def update
|
|
32
|
+
# super {|data| @io.rewind; @io.truncate(0); @io.print(data) }
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# #retrieve::
|
|
36
|
+
# Retrieve the serialized session data from the backing store. Example:
|
|
37
|
+
# def retrieve
|
|
38
|
+
# super { @io.rewind; @io.read }
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# #delete::
|
|
42
|
+
# Delete the session from the backing store. Example:
|
|
43
|
+
# def delete
|
|
44
|
+
# super {|data| @io.close; File.delete(@session_file) }
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# === Optional Derivative Interface ===
|
|
48
|
+
# ==== Serialization ====
|
|
49
|
+
#
|
|
50
|
+
# If you want to use something other than Marshal for object serialization,
|
|
51
|
+
# you can override the protected methods #serialized_data and #serialized_data=
|
|
52
|
+
# to provide your own serialization.
|
|
53
|
+
#
|
|
54
|
+
# #serialized_data::
|
|
55
|
+
# Serialize the data in the instance variable +@data+ and return it.
|
|
56
|
+
# #serialized_data=( serialized )::
|
|
57
|
+
# Deserialize the given +serialized+ data and assign it to @data.
|
|
58
|
+
#
|
|
59
|
+
# Example (serializing to YAML instead of binary):
|
|
60
|
+
# require 'yaml'
|
|
61
|
+
#
|
|
62
|
+
# def serialized_data
|
|
63
|
+
# @data.to_yaml
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# def serialized_data=( data )
|
|
67
|
+
# @data = YAML.load( data )
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# ==== Lock Recommendation ====
|
|
71
|
+
#
|
|
72
|
+
# If arrow is configured to use the 'recommended' session lock, your session
|
|
73
|
+
# store can recommend one it knows will work (e.g., if your session store is
|
|
74
|
+
# a database, you can recommend a lock that uses database locking). The simple
|
|
75
|
+
# way to do that is to define a RecommendedLocker constant in your class which
|
|
76
|
+
# contains the URI of the locker you wish to use. If you need more control
|
|
77
|
+
# than the URI can provide, you can also override the #create_recommended_lock
|
|
78
|
+
# method, which should return an instance of the locker that should be used.
|
|
79
|
+
#
|
|
80
|
+
# The method will be given the instantiated Arrow::Session::Id object that
|
|
81
|
+
# identifies the session so that you can derive a filename, primary key, etc.
|
|
82
|
+
#
|
|
83
|
+
# Example:
|
|
84
|
+
#
|
|
85
|
+
# def create_recommended_lock( idobj )
|
|
86
|
+
# return DBITransactionLock.new( idobj.to_s )
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# == Authors
|
|
90
|
+
#
|
|
91
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
92
|
+
#
|
|
93
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
94
|
+
#
|
|
95
|
+
class Arrow::Session::Store < Arrow::Object
|
|
96
|
+
include PluginFactory
|
|
97
|
+
extend Forwardable
|
|
98
|
+
|
|
99
|
+
# The URI of the lock class recommended for use with this Store.
|
|
100
|
+
RecommendedLocker = URI.parse( 'file:.' )
|
|
101
|
+
|
|
102
|
+
# The methods which are delegate directly to the data hash.
|
|
103
|
+
DelegatedMethods = [
|
|
104
|
+
:[], :default, :default=, :each, :each_key, :each_pair, :each_value,
|
|
105
|
+
:empty?, :fetch, :has_key?, :has_value?, :include?, :index, :invert,
|
|
106
|
+
:keys, :length, :member?, :merge, :rehash, :reject, :select, :size,
|
|
107
|
+
:sort, :to_a, :value?, :values
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
#############################################################
|
|
112
|
+
### C L A S S M E T H O D S
|
|
113
|
+
#############################################################
|
|
114
|
+
|
|
115
|
+
### Returns the Array of directories to search for derivatives; part of
|
|
116
|
+
### the PluginFactory interface.
|
|
117
|
+
def self::derivativeDirs
|
|
118
|
+
[ 'arrow/session', 'arrow/session/store' ]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### Overridden factory method: handle a URI object or a name
|
|
123
|
+
def self::create( uri, idobj )
|
|
124
|
+
uri = Arrow::Session.parse_uri( uri ) if uri.is_a?( String )
|
|
125
|
+
super( uri.scheme.dup, uri, idobj )
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
#############################################################
|
|
131
|
+
### I N S T A N C E M E T H O D S
|
|
132
|
+
#############################################################
|
|
133
|
+
|
|
134
|
+
### Create a new Arrow::Session::Store object.
|
|
135
|
+
def initialize( uri, idobj )
|
|
136
|
+
@data = {}
|
|
137
|
+
@id = idobj
|
|
138
|
+
@new = true
|
|
139
|
+
@modified = false
|
|
140
|
+
|
|
141
|
+
unless idobj.new?
|
|
142
|
+
self.retrieve
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
super()
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
######
|
|
150
|
+
public
|
|
151
|
+
######
|
|
152
|
+
|
|
153
|
+
# Delegate some methods to the data hash directly.
|
|
154
|
+
def_delegators :@data, *DelegatedMethods
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# The raw session data hash
|
|
158
|
+
attr_reader :data
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### Set the +value+ for the specified +key+.
|
|
162
|
+
def []=( key, value )
|
|
163
|
+
@data[ key ] = value
|
|
164
|
+
@modified = true
|
|
165
|
+
end
|
|
166
|
+
alias_method :store, :[]=
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
### Deletes and returns a key-value pair from the receiver whose key is
|
|
170
|
+
### equal to +key+. If the +key+ is not found, returns the default
|
|
171
|
+
### value. If the optional code-block is given and the key is not found,
|
|
172
|
+
### the block is called with the key, and the return value is used as
|
|
173
|
+
### the result of the method.
|
|
174
|
+
def delete( key, &block )
|
|
175
|
+
rval = @data.delete( key, &block )
|
|
176
|
+
return rval
|
|
177
|
+
ensure
|
|
178
|
+
@modified = true if rval != @data.default
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
### Clear all key/value pairs from the store for this session.
|
|
183
|
+
def clear
|
|
184
|
+
@data.clear
|
|
185
|
+
@modified = true
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
### Adds the contents of the +other+ hash to the session data,
|
|
190
|
+
### overwriting entries in the session data with values from the +other+
|
|
191
|
+
### hash where there are duplicates. If a +block+ is given, it is called
|
|
192
|
+
### for each duplicate key, and the return value is the value set in the
|
|
193
|
+
### hash.
|
|
194
|
+
def merge!( other, &block ) # :yields: key, sessionValue, otherValue
|
|
195
|
+
@data.merge!( other, &block )
|
|
196
|
+
ensure
|
|
197
|
+
@modified = true
|
|
198
|
+
end
|
|
199
|
+
alias_method :update, :merge!
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
### Replace the contents of the session hash with those of the given
|
|
203
|
+
### +other+ hash.
|
|
204
|
+
def replace( other )
|
|
205
|
+
@data.replace( other )
|
|
206
|
+
ensure
|
|
207
|
+
@modified = true
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
### Deletes every key-value pair from the session data for which the
|
|
212
|
+
### +block+ evaluates to true.
|
|
213
|
+
def reject!( &block ) # :yields: key, value
|
|
214
|
+
rval = @data.reject!( &block )
|
|
215
|
+
return rval
|
|
216
|
+
ensure
|
|
217
|
+
@modified = true if rval
|
|
218
|
+
end
|
|
219
|
+
alias_method :delete_if, :reject!
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
### Returns +true+ if the receiver's data is out of sync with the
|
|
223
|
+
### data in the backing store.
|
|
224
|
+
def modified?
|
|
225
|
+
@modified
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
### Returns +true+ if the data in the receiver has not yet been saved to
|
|
230
|
+
### the backing store, or if the entry in the backing store has been deleted
|
|
231
|
+
### since it was last saved.
|
|
232
|
+
def new?
|
|
233
|
+
@new
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
### Save the session data to the backing store
|
|
238
|
+
def save
|
|
239
|
+
return false unless self.modified? || self.new?
|
|
240
|
+
if self.new?
|
|
241
|
+
self.insert
|
|
242
|
+
else
|
|
243
|
+
self.update
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
### Insert the current +data+ hash into whatever permanent storage the
|
|
249
|
+
### Store object is acting as an interface to. Concrete implementations
|
|
250
|
+
### should provide an overriding implementation of this method that
|
|
251
|
+
### calls #super with a block which will be called with the serialized
|
|
252
|
+
### data that should be stored.
|
|
253
|
+
def insert
|
|
254
|
+
self.log.debug "Inserting session data for key %s" % @id
|
|
255
|
+
yield( self.serialized_data )
|
|
256
|
+
@new = @modified = false
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
### Update the current data hash stored in permanent storage with the
|
|
261
|
+
### values contained in the store's data. Concrete implementations
|
|
262
|
+
### should provide an overriding implementation of this method that
|
|
263
|
+
### calls #super with a block which will be called with the serialized
|
|
264
|
+
### data that should be stored.
|
|
265
|
+
def update
|
|
266
|
+
self.log.debug "Updating session data for key %s" % @id
|
|
267
|
+
yield( self.serialized_data )
|
|
268
|
+
@modified = false
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
### Retrieve the data hash stored in permanent storage associated with
|
|
273
|
+
### the id the object was created with. Concrete implementations
|
|
274
|
+
### should provide an overriding implementation of this method that calls
|
|
275
|
+
### #super with a block that returns the serialized data to be restored.
|
|
276
|
+
def retrieve
|
|
277
|
+
self.log.debug "Retrieving session data for key %s" % @id
|
|
278
|
+
self.serialized_data = yield
|
|
279
|
+
@new = @modified = false
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
### Permanently remove the data hash associated with the id used in the
|
|
284
|
+
### receiver's creation from permanent storage.
|
|
285
|
+
def remove
|
|
286
|
+
self.log.debug "Removing session data for key %s" % @id
|
|
287
|
+
@new = true
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
### Returns an instance of the recommended lock object for the receiving
|
|
292
|
+
### store. If no recommended locking strategy is known, this method
|
|
293
|
+
### raises a SessionError.
|
|
294
|
+
def create_recommended_lock( idobj )
|
|
295
|
+
self.log.debug "Searching for recommended lock for %s" %
|
|
296
|
+
self.class.name
|
|
297
|
+
|
|
298
|
+
# Traverse the class hierarchy to find a class which defines a
|
|
299
|
+
# RecommendedLocker constant
|
|
300
|
+
adviceClass = self.class.ancestors.find {|klass|
|
|
301
|
+
klass.const_defined?( :RecommendedLocker )
|
|
302
|
+
} or raise SessionError, "No recommended locker for %p" %
|
|
303
|
+
self.class.ancestors
|
|
304
|
+
|
|
305
|
+
uri = adviceClass.const_get( :RecommendedLocker ) or
|
|
306
|
+
raise SessionError, "Could not fetch RecommendedLocker constant"
|
|
307
|
+
|
|
308
|
+
self.log.debug "Creating recommended lock %s" % uri
|
|
309
|
+
uri = Arrow::Session.parse_uri( uri ) if
|
|
310
|
+
uri.is_a?( String )
|
|
311
|
+
|
|
312
|
+
lock = Arrow::Session::Lock.create( uri, idobj )
|
|
313
|
+
self.log.debug "Created recommended lock object: %p" % lock
|
|
314
|
+
|
|
315
|
+
return lock
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
#########
|
|
320
|
+
protected
|
|
321
|
+
#########
|
|
322
|
+
|
|
323
|
+
### Returns the data in the session store as a serialized object.
|
|
324
|
+
def serialized_data
|
|
325
|
+
data = strip_hash( @data )
|
|
326
|
+
return Marshal.dump( data )
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
### Sets the session's data by deserializing the object
|
|
331
|
+
### contained in the given +string+.
|
|
332
|
+
def serialized_data=( string )
|
|
333
|
+
if string.empty?
|
|
334
|
+
self.log.error "No session data: retaining default hash"
|
|
335
|
+
else
|
|
336
|
+
@data = Marshal.restore( string )
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
#######
|
|
343
|
+
private
|
|
344
|
+
#######
|
|
345
|
+
|
|
346
|
+
### Return a copy of the given +hash+ with all non-serializable
|
|
347
|
+
### objects stripped out of it.
|
|
348
|
+
def strip_hash( hash, cloned=true )
|
|
349
|
+
newhash = cloned ? hash.dup : hash
|
|
350
|
+
newhash.default = nil if newhash.default_proc
|
|
351
|
+
newhash.each_key {|key|
|
|
352
|
+
case newhash[ key ]
|
|
353
|
+
when Hash
|
|
354
|
+
newhash[ key ] = strip_hash( newhash[key], false )
|
|
355
|
+
|
|
356
|
+
when Proc, Method, UnboundMethod, IO
|
|
357
|
+
self.log.warning "Stripping unserializable object from session " \
|
|
358
|
+
"hash: %p" % newhash[ key ]
|
|
359
|
+
newhash[ key ] = "[Can't serialize a %s]" % newhash[ key ].class
|
|
360
|
+
end
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return newhash
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
end # class Arrow::Session::Store
|