Filename | /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store.pm |
Statements | Executed 755 statements in 5.31ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
122 | 5 | 4 | 1.82ms | 2.46ms | cleanUpRevID | Foswiki::Store::
70 | 1 | 1 | 1.18ms | 1.18ms | askListeners | Foswiki::Store::
121 | 1 | 1 | 640µs | 640µs | CORE:match (opcode) | Foswiki::Store::
2 | 1 | 1 | 34µs | 34µs | askListenersRevisionHistory | Foswiki::Store::
1 | 1 | 1 | 33µs | 73µs | new | Foswiki::Store::
1 | 1 | 1 | 30µs | 40µs | _LoadAndRegisterListeners | Foswiki::Store::
1 | 1 | 1 | 25µs | 45µs | BEGIN@53 | Foswiki::Store::
1 | 1 | 1 | 24µs | 31µs | BEGIN@52 | Foswiki::Store::
1 | 1 | 1 | 18µs | 56µs | BEGIN@56 | Foswiki::Store::
1 | 1 | 1 | 16µs | 382µs | BEGIN@55 | Foswiki::Store::
1 | 1 | 1 | 10µs | 10µs | CORE:sort (opcode) | Foswiki::Store::
1 | 1 | 1 | 9µs | 9µs | BEGIN@60 | Foswiki::Store::
1 | 1 | 1 | 9µs | 9µs | BEGIN@58 | Foswiki::Store::
1 | 1 | 1 | 8µs | 8µs | BEGIN@59 | Foswiki::Store::
1 | 1 | 1 | 8µs | 8µs | BEGIN@61 | Foswiki::Store::
1 | 1 | 1 | 7µs | 7µs | finish | Foswiki::Store::
0 | 0 | 0 | 0s | 0s | askListenersVersionInfo | Foswiki::Store::
0 | 0 | 0 | 0s | 0s | getWorkArea | Foswiki::Store::
0 | 0 | 0 | 0s | 0s | setListenerPriority | Foswiki::Store::
0 | 0 | 0 | 0s | 0s | tellListeners | Foswiki::Store::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | |||||
3 | =begin TML | ||||
4 | |||||
5 | ---+ package Foswiki::Store | ||||
6 | |||||
7 | This class is a pure virtual base class that specifies the interface | ||||
8 | between the actual store implementation and the rest of the Foswiki | ||||
9 | system. | ||||
10 | |||||
11 | Subclasses of this class (known as "store implementations") are | ||||
12 | responsible for checking for topic existance, access permissions, and | ||||
13 | all the other general admin tasks required of a store. | ||||
14 | |||||
15 | This class knows *nothing* about how the data is actually _stored_ - | ||||
16 | that knowledge is entirely encapsulated in the implementation. | ||||
17 | |||||
18 | The general contract for methods in the class requires that errors | ||||
19 | are signalled using exceptions. Foswiki::AccessControlException is | ||||
20 | used for access control exceptions, and Error::Simple for all other | ||||
21 | types of error. | ||||
22 | |||||
23 | Reference implementations of this base class are =Foswiki::Store::RcsWrap= | ||||
24 | and =Foswiki::Store::RcsLite= (these are both implemented in terms of | ||||
25 | VC::Store, which is an abstract implementation of a store based on a | ||||
26 | version control system). | ||||
27 | |||||
28 | Methods of this class and all subclasses should *only* be called from | ||||
29 | =Foswiki= and =Foswiki::Meta=. All other system components must delegate | ||||
30 | store interactions via =Foswiki::Meta=. | ||||
31 | |||||
32 | For readers who are familiar with Foswiki version 1.0.0, this class | ||||
33 | _describes_ the interface to the old =Foswiki::Store= without actually | ||||
34 | _implementing_ it. | ||||
35 | |||||
36 | Note that most methods are passed a Foswiki::Meta object. This pattern is | ||||
37 | employed to reinforce the encapsulation of a "path" in a meta object, and | ||||
38 | also to allow the store to modify META fields in the object, something it | ||||
39 | would be unable to do if passed $web, $topic. | ||||
40 | |||||
41 | Version numbers are required to be positive, non-zero integers. When | ||||
42 | passing in version numbers to the methods of a store implementation, 0, | ||||
43 | undef and '' are treated as referring to the *latest* (most recent) | ||||
44 | revision of the object. Version numbers are required to increase (later | ||||
45 | version numbers are greater than earlier) but are *not* required to be | ||||
46 | sequential. | ||||
47 | |||||
48 | =cut | ||||
49 | |||||
50 | package Foswiki::Store; | ||||
51 | |||||
52 | 2 | 45µs | 2 | 38µs | # spent 31µs (24+7) within Foswiki::Store::BEGIN@52 which was called:
# once (24µs+7µs) by Foswiki::BEGIN@612 at line 52 # spent 31µs making 1 call to Foswiki::Store::BEGIN@52
# spent 7µs making 1 call to strict::import |
53 | 2 | 49µs | 2 | 64µs | # spent 45µs (25+20) within Foswiki::Store::BEGIN@53 which was called:
# once (25µs+20µs) by Foswiki::BEGIN@612 at line 53 # spent 45µs making 1 call to Foswiki::Store::BEGIN@53
# spent 20µs making 1 call to warnings::import |
54 | |||||
55 | 2 | 47µs | 2 | 749µs | # spent 382µs (16+367) within Foswiki::Store::BEGIN@55 which was called:
# once (16µs+367µs) by Foswiki::BEGIN@612 at line 55 # spent 382µs making 1 call to Foswiki::Store::BEGIN@55
# spent 367µs making 1 call to Error::import |
56 | 2 | 44µs | 2 | 94µs | # spent 56µs (18+38) within Foswiki::Store::BEGIN@56 which was called:
# once (18µs+38µs) by Foswiki::BEGIN@612 at line 56 # spent 56µs making 1 call to Foswiki::Store::BEGIN@56
# spent 38µs making 1 call to Assert::import |
57 | |||||
58 | 2 | 36µs | 1 | 9µs | # spent 9µs within Foswiki::Store::BEGIN@58 which was called:
# once (9µs+0s) by Foswiki::BEGIN@612 at line 58 # spent 9µs making 1 call to Foswiki::Store::BEGIN@58 |
59 | 2 | 36µs | 1 | 8µs | # spent 8µs within Foswiki::Store::BEGIN@59 which was called:
# once (8µs+0s) by Foswiki::BEGIN@612 at line 59 # spent 8µs making 1 call to Foswiki::Store::BEGIN@59 |
60 | 2 | 36µs | 1 | 9µs | # spent 9µs within Foswiki::Store::BEGIN@60 which was called:
# once (9µs+0s) by Foswiki::BEGIN@612 at line 60 # spent 9µs making 1 call to Foswiki::Store::BEGIN@60 |
61 | 2 | 1.17ms | 1 | 8µs | # spent 8µs within Foswiki::Store::BEGIN@61 which was called:
# once (8µs+0s) by Foswiki::BEGIN@612 at line 61 # spent 8µs making 1 call to Foswiki::Store::BEGIN@61 |
62 | |||||
63 | 1 | 2µs | our $STORE_FORMAT_VERSION = '1.1'; | ||
64 | |||||
65 | =begin TML | ||||
66 | |||||
67 | ---++ ClassMethod new() | ||||
68 | |||||
69 | Construct a Store module. | ||||
70 | |||||
71 | =cut | ||||
72 | |||||
73 | # spent 73µs (33+40) within Foswiki::Store::new which was called:
# once (33µs+40µs) by Foswiki::new at line 1697 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm | ||||
74 | 4 | 35µs | my $class = shift; | ||
75 | |||||
76 | my $this = bless( {}, $class ); | ||||
77 | 1 | 40µs | $this->_LoadAndRegisterListeners(); # spent 40µs making 1 call to Foswiki::Store::_LoadAndRegisterListeners | ||
78 | |||||
79 | return $this; | ||||
80 | } | ||||
81 | |||||
82 | #extracted so we can re-jig the list while running (for eg, non-optionally load a listener from code. | ||||
83 | # spent 40µs (30+10) within Foswiki::Store::_LoadAndRegisterListeners which was called:
# once (30µs+10µs) by Foswiki::Store::new at line 77 | ||||
84 | 4 | 39µs | my $this = shift; | ||
85 | |||||
86 | # Create and register store listeners. Store listeners are subclasses | ||||
87 | # of Foswiki::Store::Interfaces::Listener | ||||
88 | |||||
89 | my @evl; | ||||
90 | 1 | 3µs | 1 | 10µs | foreach my $key ( # spent 10µs making 1 call to Foswiki::Store::CORE:sort |
91 | sort { | ||||
92 | $Foswiki::cfg{Store}{Listeners}{$a} <=> $Foswiki::cfg{Store} | ||||
93 | {Listeners}{$b} | ||||
94 | } keys( %{ $Foswiki::cfg{Store}{Listeners} } ) | ||||
95 | ) | ||||
96 | { | ||||
97 | if ( $Foswiki::cfg{Store}{Listeners}{$key} ) { | ||||
98 | |||||
99 | #print STDERR "loading $key\n"; | ||||
100 | eval "require $key"; | ||||
101 | die "Failed to load $key: $@" if $@; | ||||
102 | push( @evl, $key->new() ); | ||||
103 | } | ||||
104 | else { | ||||
105 | |||||
106 | #don't try (and thus potentially crash on disabled listener | ||||
107 | delete $Foswiki::cfg{Store}{Listeners}{$key}; | ||||
108 | } | ||||
109 | } | ||||
110 | |||||
111 | $this->{event_listeners} = \@evl; | ||||
112 | } | ||||
113 | |||||
114 | =begin TML | ||||
115 | |||||
116 | ---++ ObjectMethod setListenerPriority() | ||||
117 | allows you to enable/disable (set priority to 0) a new Listener, or to change its priority | ||||
118 | |||||
119 | =cut | ||||
120 | |||||
121 | sub setListenerPriority { | ||||
122 | my $this = shift; | ||||
123 | my $classname = shift; | ||||
124 | my $priority = shift; | ||||
125 | |||||
126 | $Foswiki::cfg{Store}{Listeners}{$classname} = $priority; | ||||
127 | $this->_LoadAndRegisterListeners(); | ||||
128 | } | ||||
129 | |||||
130 | =begin TML | ||||
131 | |||||
132 | ---++ ObjectMethod finish() | ||||
133 | Break circular references. | ||||
134 | |||||
135 | =cut | ||||
136 | |||||
137 | # Note to developers; please undef *all* fields in the object explicitly, | ||||
138 | # whether they are references or not. That way this method is "golden | ||||
139 | # documentation" of the live fields in the object. | ||||
140 | # spent 7µs within Foswiki::Store::finish which was called:
# once (7µs+0s) by Foswiki::Store::VC::Store::finish at line 60 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm | ||||
141 | 2 | 13µs | my $this = shift; | ||
142 | undef $this->{event_listeners}; | ||||
143 | } | ||||
144 | |||||
145 | =begin TML | ||||
146 | |||||
147 | ---++ ObjectMethod tellListeners( $event, ... ) | ||||
148 | Invoke listeners that have registered an interest in events on this store. | ||||
149 | The $event method on the listener is invoked, passing the ... parameters | ||||
150 | to the listener. | ||||
151 | |||||
152 | =cut | ||||
153 | |||||
154 | sub tellListeners { | ||||
155 | my $this = shift; | ||||
156 | my %arg = @_; | ||||
157 | my $event = $arg{verb}; | ||||
158 | |||||
159 | foreach my $el ( @{ $this->{event_listeners} } ) { | ||||
160 | next unless $el->can($event); | ||||
161 | $el->$event(%arg); | ||||
162 | } | ||||
163 | } | ||||
164 | |||||
165 | =begin TML | ||||
166 | |||||
167 | ---++ ObjectMethod askListeners( $topicObject, $version ) | ||||
168 | Ask listeners if they would like to provide the object specified in the | ||||
169 | $topicObject, at the given version. The first listener to respond with a | ||||
170 | non-zero revision will be assumed to have loaded the topic object. | ||||
171 | |||||
172 | Listeners are expected to implement =loadTopic=. If they do not, they | ||||
173 | will not be asked. | ||||
174 | |||||
175 | =cut | ||||
176 | |||||
177 | # spent 1.18ms within Foswiki::Store::askListeners which was called 70 times, avg 17µs/call:
# 70 times (1.18ms+0s) by Foswiki::Store::VC::Store::readTopic at line 82 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 17µs/call | ||||
178 | 280 | 1.05ms | my ( $this, $meta, $version ) = @_; | ||
179 | my ( $gotRev, $isLatest ); | ||||
180 | |||||
181 | 70 | 182µs | foreach my $el ( @{ $this->{event_listeners} } ) { | ||
182 | next unless $el->can('loadTopic'); | ||||
183 | ( $gotRev, $isLatest ) = $el->loadTopic( $meta, $version ); | ||||
184 | return ( $gotRev, $isLatest ) if $gotRev; | ||||
185 | } | ||||
186 | return ( undef, undef ); | ||||
187 | } | ||||
188 | |||||
189 | =begin TML | ||||
190 | |||||
191 | ---++ ObjectMethod askListenersRevisionHistory( $topicObject, $attachment ) | ||||
192 | Ask listeners if they would like to provide the object specified in the | ||||
193 | $topicObject, at the given version. The first listener to respond with a | ||||
194 | non-zero revision will be assumed to have loaded the topic object. | ||||
195 | |||||
196 | Listeners are expected to implement =loadTopic=. If they do not, they | ||||
197 | will not be asked. | ||||
198 | |||||
199 | =cut | ||||
200 | |||||
201 | # spent 34µs within Foswiki::Store::askListenersRevisionHistory which was called 2 times, avg 17µs/call:
# 2 times (34µs+0s) by Foswiki::Store::VC::Store::getRevisionHistory at line 282 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 17µs/call | ||||
202 | 8 | 40µs | my ( $this, $meta, $attachment ) = @_; | ||
203 | my ($itr); | ||||
204 | |||||
205 | 2 | 4µs | foreach my $el ( @{ $this->{event_listeners} } ) { | ||
206 | next unless $el->can('getRevisionHistory'); | ||||
207 | return $itr = $el->getRevisionHistory( $meta, $attachment ); | ||||
208 | } | ||||
209 | return undef; | ||||
210 | } | ||||
211 | |||||
212 | # my $itr = $this->askListenersRevisionHistory($topicObject, $attachment); | ||||
213 | |||||
214 | =begin TML | ||||
215 | |||||
216 | ---++ ObjectMethod askListenersVersionInfo( $topicObject, $version ) | ||||
217 | Ask listeners if they would like to answer Foswiki::Store->getVersionInfo() | ||||
218 | - a hashref containing date, user, version, comment keys | ||||
219 | |||||
220 | =cut | ||||
221 | |||||
222 | sub askListenersVersionInfo { | ||||
223 | my ( $this, $meta, $version, $attachment ) = @_; | ||||
224 | |||||
225 | foreach my $el ( @{ $this->{event_listeners} } ) { | ||||
226 | next unless $el->can('getVersionInfo'); | ||||
227 | return $el->getVersionInfo( $meta, $version, $attachment ); | ||||
228 | } | ||||
229 | return undef; | ||||
230 | } | ||||
231 | |||||
232 | =begin TML | ||||
233 | |||||
234 | ---++ StaticMethod cleanUpRevID( $rev ) -> $integer | ||||
235 | |||||
236 | Cleans up (maps) a user-supplied revision ID and converts it to an integer | ||||
237 | number that can be incremented to create a new revision number. | ||||
238 | |||||
239 | This method should be used to sanitise user-provided revision IDs. | ||||
240 | |||||
241 | Returns 0 if it was unable to determine a valid rev number from the | ||||
242 | string passed. | ||||
243 | |||||
244 | =cut | ||||
245 | |||||
246 | # spent 2.46ms (1.82+640µs) within Foswiki::Store::cleanUpRevID which was called 122 times, avg 20µs/call:
# 67 times (956µs+385µs) by Foswiki::Meta::setEmbeddedStoreForm at line 3537 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Meta.pm, avg 20µs/call
# 35 times (412µs+127µs) by Foswiki::Meta::setEmbeddedStoreForm at line 3539 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Meta.pm, avg 15µs/call
# 13 times (352µs+116µs) by Foswiki::Store::VC::Handler::_getTOPICINFO at line 274 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Handler.pm, avg 36µs/call
# 6 times (85µs+11µs) by Foswiki::REVINFO at line 18 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 16µs/call
# once (13µs+0s) by Foswiki::UI::View::view at line 101 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm | ||||
247 | 251 | 1.62ms | my $rev = shift; | ||
248 | |||||
249 | # RCS format: 1.2, or plain integer: 2 | ||||
250 | 115 | 847µs | 121 | 640µs | if ( defined $rev && $rev =~ /^(?:\d+\.)?(\d+)$/ ) { # spent 640µs making 121 calls to Foswiki::Store::CORE:match, avg 5µs/call |
251 | return $1; | ||||
252 | } | ||||
253 | |||||
254 | return 0; | ||||
255 | } | ||||
256 | |||||
257 | =begin TML | ||||
258 | |||||
259 | ---+++ ObjectMethod getWorkArea( $key ) -> $directorypath | ||||
260 | |||||
261 | Gets a private directory uniquely identified by $key. The directory is | ||||
262 | intended as a work area for plugins. | ||||
263 | |||||
264 | The standard is a directory named the same as "key" under | ||||
265 | $Foswiki::cfg{WorkingDir}/work_areas | ||||
266 | |||||
267 | =cut | ||||
268 | |||||
269 | sub getWorkArea { | ||||
270 | my ( $this, $key ) = @_; | ||||
271 | |||||
272 | # untaint and detect nasties. The rules are the same as for | ||||
273 | # attachment names. | ||||
274 | $key = Foswiki::Sandbox::untaint( $key, | ||||
275 | \&Foswiki::Sandbox::validateAttachmentName ); | ||||
276 | throw Error::Simple("Bad work area name $key") unless ($key); | ||||
277 | |||||
278 | my $dir = "$Foswiki::cfg{WorkingDir}/work_areas/$key"; | ||||
279 | |||||
280 | unless ( -d $dir ) { | ||||
281 | mkdir($dir) || throw Error::Simple(<<ERROR); | ||||
282 | Failed to create $key work area. Check your setting of {WorkingDir} | ||||
283 | in =configure=. | ||||
284 | ERROR | ||||
285 | } | ||||
286 | return $dir; | ||||
287 | } | ||||
288 | |||||
289 | 1 | 5µs | 1; | ||
290 | __END__ | ||||
# spent 640µs within Foswiki::Store::CORE:match which was called 121 times, avg 5µs/call:
# 121 times (640µs+0s) by Foswiki::Store::cleanUpRevID at line 250, avg 5µs/call | |||||
# spent 10µs within Foswiki::Store::CORE:sort which was called:
# once (10µs+0s) by Foswiki::Store::_LoadAndRegisterListeners at line 90 |