Filename | /var/www/foswikidev/core/lib/Foswiki/PageCache.pm |
Statements | Executed 18 statements in 2.19ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 1.28ms | 1.36ms | BEGIN@61 | Foswiki::PageCache::
1 | 1 | 1 | 13µs | 27µs | BEGIN@58 | Foswiki::PageCache::
1 | 1 | 1 | 9µs | 13µs | BEGIN@59 | Foswiki::PageCache::
1 | 1 | 1 | 8µs | 37µs | BEGIN@74 | Foswiki::PageCache::
1 | 1 | 1 | 8µs | 109µs | BEGIN@63 | Foswiki::PageCache::
1 | 1 | 1 | 5µs | 5µs | BEGIN@62 | Foswiki::PageCache::
1 | 1 | 1 | 4µs | 4µs | BEGIN@64 | Foswiki::PageCache::
1 | 1 | 1 | 4µs | 4µs | BEGIN@66 | Foswiki::PageCache::
1 | 1 | 1 | 4µs | 4µs | BEGIN@60 | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | __ANON__[:654] | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | __ANON__[:657] | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | _handleDirtyArea | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | addDependency | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | cachePage | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | deleteAll | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | deleteDependencies | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | deletePage | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | finish | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | fireDependency | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | genVariationKey | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | getDependencies | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | getPage | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | getPageVariation | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | getWebDependencies | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | isCacheable | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | new | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | renderDirtyAreas | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | setDependencies | Foswiki::PageCache::
0 | 0 | 0 | 0s | 0s | setPageVariation | Foswiki::PageCache::
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::PageCache | ||||
6 | |||||
7 | This class is a purely virtual base class that implements the | ||||
8 | basic infrastructure required to cache pages as produced by | ||||
9 | the rendering engine. Once a page was computed, it will be | ||||
10 | cached for subsequent calls for the same output. In addition | ||||
11 | a Foswiki::PageCache has to ensure cache correctness, that is | ||||
12 | all content stored in the cache is up-to-date. It must not | ||||
13 | return any content being rendered on the base of data that has already | ||||
14 | changed in the meantine by actions performed by the Foswiki::Store. | ||||
15 | |||||
16 | The Foswiki::Store informs the cache whenever any content has changed | ||||
17 | by calling Foswiki::PageCache::fireDependency($web, $topic). This | ||||
18 | will in turn delete any cache entries that used this $web.$topic as an | ||||
19 | ingredience to render the cached page. That's why there is a dependency | ||||
20 | graph part of the page cache. | ||||
21 | |||||
22 | The dependency graph records all topics that have been touched while | ||||
23 | the current page is being computed. It also records the session and url | ||||
24 | parameters that were in use, part of which is the user name as well. | ||||
25 | |||||
26 | An edge in the dependency graph consists of: | ||||
27 | |||||
28 | * from: the topic being rendered | ||||
29 | * variation: an opaque key encoding the context in which the page was rendered | ||||
30 | * to: the topic that has been used to render the "from" topic | ||||
31 | |||||
32 | For every cached page there's a record of meta data describing it: | ||||
33 | |||||
34 | * topic: the web.topic being cached | ||||
35 | * variation: the context which this page was rendered within | ||||
36 | * md5: fingerprint of the data stored; this is used to get access to the stored | ||||
37 | blob related to this page | ||||
38 | * contenttype: to be used in the http header | ||||
39 | * lastmodified: time when this page was cached in http-date format | ||||
40 | * etag: tag used for browser-side caching | ||||
41 | * status: http response status | ||||
42 | * location: url in case the status is a 302 redirect | ||||
43 | * expire: time when this cache entry is outdated | ||||
44 | * isdirty: boolean flag indicating whether the cached page has got "dirtyareas" | ||||
45 | and thus needs post-processing | ||||
46 | |||||
47 | Whenever the Foswiki::Store informs the cache by firing a dependency for | ||||
48 | a given web.topic, the cache will remove those cache entries that have a dependency | ||||
49 | to the given web.topic. It thereby guarentees that whenever a page has been | ||||
50 | successfully retrieved from the cache, there is no "fresher" content available | ||||
51 | in the Foswiki::Store, and that this cache entry can be used instead without | ||||
52 | rendering the related yet again. | ||||
53 | |||||
54 | =cut | ||||
55 | |||||
56 | package Foswiki::PageCache; | ||||
57 | |||||
58 | 2 | 27µs | 2 | 40µs | # spent 27µs (13+14) within Foswiki::PageCache::BEGIN@58 which was called:
# once (13µs+14µs) by Foswiki::UI::View::BEGIN@25 at line 58 # spent 27µs making 1 call to Foswiki::PageCache::BEGIN@58
# spent 14µs making 1 call to strict::import |
59 | 2 | 29µs | 2 | 17µs | # spent 13µs (9+4) within Foswiki::PageCache::BEGIN@59 which was called:
# once (9µs+4µs) by Foswiki::UI::View::BEGIN@25 at line 59 # spent 13µs making 1 call to Foswiki::PageCache::BEGIN@59
# spent 4µs making 1 call to warnings::import |
60 | 2 | 20µs | 1 | 4µs | # spent 4µs within Foswiki::PageCache::BEGIN@60 which was called:
# once (4µs+0s) by Foswiki::UI::View::BEGIN@25 at line 60 # spent 4µs making 1 call to Foswiki::PageCache::BEGIN@60 |
61 | 2 | 107µs | 1 | 1.36ms | # spent 1.36ms (1.28+74µs) within Foswiki::PageCache::BEGIN@61 which was called:
# once (1.28ms+74µs) by Foswiki::UI::View::BEGIN@25 at line 61 # spent 1.36ms making 1 call to Foswiki::PageCache::BEGIN@61 |
62 | 2 | 24µs | 1 | 5µs | # spent 5µs within Foswiki::PageCache::BEGIN@62 which was called:
# once (5µs+0s) by Foswiki::UI::View::BEGIN@25 at line 62 # spent 5µs making 1 call to Foswiki::PageCache::BEGIN@62 |
63 | 2 | 30µs | 2 | 210µs | # spent 109µs (8+101) within Foswiki::PageCache::BEGIN@63 which was called:
# once (8µs+101µs) by Foswiki::UI::View::BEGIN@25 at line 63 # spent 109µs making 1 call to Foswiki::PageCache::BEGIN@63
# spent 101µs making 1 call to Error::import |
64 | 2 | 41µs | 1 | 4µs | # spent 4µs within Foswiki::PageCache::BEGIN@64 which was called:
# once (4µs+0s) by Foswiki::UI::View::BEGIN@25 at line 64 # spent 4µs making 1 call to Foswiki::PageCache::BEGIN@64 |
65 | |||||
66 | # spent 4µs within Foswiki::PageCache::BEGIN@66 which was called:
# once (4µs+0s) by Foswiki::UI::View::BEGIN@25 at line 71 | ||||
67 | 1 | 4µs | if ( $Foswiki::cfg{UseLocale} ) { | ||
68 | require locale; | ||||
69 | import locale(); | ||||
70 | } | ||||
71 | 1 | 20µs | 1 | 4µs | } # spent 4µs making 1 call to Foswiki::PageCache::BEGIN@66 |
72 | |||||
73 | # Enable output | ||||
74 | 2 | 1.89ms | 2 | 66µs | # spent 37µs (8+29) within Foswiki::PageCache::BEGIN@74 which was called:
# once (8µs+29µs) by Foswiki::UI::View::BEGIN@25 at line 74 # spent 37µs making 1 call to Foswiki::PageCache::BEGIN@74
# spent 29µs making 1 call to constant::import |
75 | |||||
76 | =begin TML | ||||
77 | |||||
78 | ---++ ClassMethod new( ) -> $object | ||||
79 | |||||
80 | Construct a new page cache | ||||
81 | |||||
82 | =cut | ||||
83 | |||||
84 | sub new { | ||||
85 | my ($class) = @_; | ||||
86 | |||||
87 | return bless( {}, $class ); | ||||
88 | } | ||||
89 | |||||
90 | =begin TML | ||||
91 | |||||
92 | ---++ ObjectMethod genVariationKey() -> $key | ||||
93 | |||||
94 | Generate a key for the current webtopic being produced; this reads | ||||
95 | information from the current session and url params, as follows: | ||||
96 | * the server serving the request (HTTP_HOST) | ||||
97 | * the port number of the server serving the request (HTTP_PORT) | ||||
98 | * the action used to render the page (view or rest) | ||||
99 | * the language of the current session, if any | ||||
100 | * all session parameters EXCEPT: | ||||
101 | o Those starting with an underscore | ||||
102 | o VALIDATION | ||||
103 | o REMEMBER | ||||
104 | o FOSWIKISTRIKEONE.* | ||||
105 | o VALID_ACTIONS.* | ||||
106 | o BREADCRUMB_TRAIL | ||||
107 | o DGP_hash | ||||
108 | * all HTTP request parameters EXCEPT: | ||||
109 | o All those starting with an underscore | ||||
110 | o refresh | ||||
111 | o foswiki_redirect_cache | ||||
112 | o logout | ||||
113 | o topic | ||||
114 | o cache_ignore | ||||
115 | o cache_expire | ||||
116 | |||||
117 | =cut | ||||
118 | |||||
119 | sub genVariationKey { | ||||
120 | my $this = shift; | ||||
121 | |||||
122 | my $variationKey = $this->{variationKey}; | ||||
123 | return $variationKey if defined $variationKey; | ||||
124 | |||||
125 | my $session = $Foswiki::Plugins::SESSION; | ||||
126 | my $request = $session->{request}; | ||||
127 | my $action = substr( ( $request->{action} || 'view' ), 0, 4 ); | ||||
128 | my $serverName = $request->server_name || $Foswiki::cfg{DefaultUrlHost}; | ||||
129 | my $serverPort = $request->server_port || 80; | ||||
130 | $variationKey = '::' . $serverName . '::' . $serverPort . '::' . $action; | ||||
131 | |||||
132 | # add a flag to distinguish compressed from uncompressed cache entries | ||||
133 | $variationKey .= '::' | ||||
134 | . ( | ||||
135 | ( | ||||
136 | $Foswiki::cfg{HttpCompress} | ||||
137 | && $Foswiki::engine->isa('Foswiki::Engine::CLI') | ||||
138 | ) | ||||
139 | ? 1 | ||||
140 | : 0 | ||||
141 | ); | ||||
142 | |||||
143 | # add language tag | ||||
144 | if ( $Foswiki::cfg{UserInterfaceInternationalisation} ) { | ||||
145 | my $language = $session->i18n->language(); | ||||
146 | $variationKey .= "::language=$language" if $language; | ||||
147 | } | ||||
148 | |||||
149 | # get information from the session object | ||||
150 | my $sessionValues = $session->getLoginManager()->getSessionValues(); | ||||
151 | foreach my $key ( sort keys %$sessionValues ) { | ||||
152 | |||||
153 | # SMELL: add a setting to make exclusion of session variables configurable | ||||
154 | next | ||||
155 | if $key =~ | ||||
156 | m/^(_.*|VALIDATION|REMEMBER|FOSWIKISTRIKEONE.*|VALID_ACTIONS.*|BREADCRUMB_TRAIL|DGP_hash|release_lock)$/; | ||||
157 | |||||
158 | #writeDebug("adding session key=$key"); | ||||
159 | |||||
160 | my $val = $sessionValues->{$key}; | ||||
161 | next unless defined $val; | ||||
162 | |||||
163 | $variationKey .= '::' . $key . '=' . $val; | ||||
164 | } | ||||
165 | |||||
166 | # get cache_ignore pattern | ||||
167 | my @ignoreParams = $request->multi_param("cache_ignore"); | ||||
168 | push @ignoreParams, | ||||
169 | ( | ||||
170 | "cache_expire", "cache_ignore", | ||||
171 | "_.*", "refresh", | ||||
172 | "foswiki_redirect_cache", "logout", | ||||
173 | "topic" | ||||
174 | ); | ||||
175 | my $ignoreParams = join( "|", @ignoreParams ); | ||||
176 | |||||
177 | foreach my $key ( sort $request->multi_param() ) { | ||||
178 | |||||
179 | # filter out some params that are not relevant | ||||
180 | next if $key =~ m/^($ignoreParams)$/; | ||||
181 | my @vals = $request->multi_param($key); | ||||
182 | foreach my $val (@vals) { | ||||
183 | next unless defined $val; # wtf? | ||||
184 | $variationKey .= '::' . $key . '=' . $val; | ||||
185 | Foswiki::Func::writeDebug("adding urlparam key=$key val=$val") | ||||
186 | if TRACE; | ||||
187 | } | ||||
188 | } | ||||
189 | |||||
190 | $variationKey =~ s/'/\\'/g; | ||||
191 | |||||
192 | Foswiki::Func::writeDebug("variation key = '$variationKey'") if TRACE; | ||||
193 | |||||
194 | # cache it | ||||
195 | $this->{variationKey} = $variationKey; | ||||
196 | return $variationKey; | ||||
197 | } | ||||
198 | |||||
199 | =begin TML | ||||
200 | |||||
201 | ---++ ObjectMethod cachePage($contentType, $data) -> $boolean | ||||
202 | |||||
203 | Cache a page. Every page is stored in a page bucket that contains all | ||||
204 | variations (stored for other users or other session parameters) of this page, | ||||
205 | as well as dependency and expiration information | ||||
206 | |||||
207 | =cut | ||||
208 | |||||
209 | sub cachePage { | ||||
210 | my ( $this, $contentType, $data ) = @_; | ||||
211 | |||||
212 | my $session = $Foswiki::Plugins::SESSION; | ||||
213 | my $request = $session->{request}; | ||||
214 | my $web = $session->{webName}; | ||||
215 | my $topic = $session->{topicName}; | ||||
216 | $web =~ s/\//./g; | ||||
217 | |||||
218 | Foswiki::Func::writeDebug("called cachePage($web, $topic)") if TRACE; | ||||
219 | return undef unless $this->isCacheable( $web, $topic ); | ||||
220 | |||||
221 | # delete page and all variations if we ask for a refresh copy | ||||
222 | my $refresh = $request->param('refresh') || ''; | ||||
223 | my $variationKey = $this->genVariationKey(); | ||||
224 | |||||
225 | # remove old entries | ||||
226 | if ( $refresh =~ m/^(on|cache|all)$/ ) { | ||||
227 | $this->deletePage( $web, $topic ); # removes all variations | ||||
228 | } | ||||
229 | else { | ||||
230 | $this->deletePage( $web, $topic, $variationKey ); | ||||
231 | } | ||||
232 | |||||
233 | # prepare page variation | ||||
234 | my $isDirty = | ||||
235 | ( $data =~ m/<dirtyarea[^>]*?>/ ) | ||||
236 | ? 1 | ||||
237 | : 0; # SMELL: only for textual content type | ||||
238 | |||||
239 | Foswiki::Func::writeDebug("isDirty=$isDirty") if TRACE; | ||||
240 | |||||
241 | my $etag = ''; | ||||
242 | my $lastModified = ''; | ||||
243 | my $time = time(); | ||||
244 | |||||
245 | unless ($isDirty) { | ||||
246 | $data =~ s/([\t ]?)[ \t]*<\/?(nop|noautolink)\/?>/$1/gis; | ||||
247 | |||||
248 | # clean pages are stored utf8-encoded, whether plaintext or zip | ||||
249 | $data = Foswiki::encode_utf8($data); | ||||
250 | if ( $Foswiki::cfg{HttpCompress} ) { | ||||
251 | |||||
252 | # Cache compressed page | ||||
253 | require Compress::Zlib; | ||||
254 | $data = Compress::Zlib::memGzip($data); | ||||
255 | } | ||||
256 | $etag = $time; | ||||
257 | $lastModified = Foswiki::Time::formatTime( $time, '$http', 'gmtime' ); | ||||
258 | } | ||||
259 | |||||
260 | my $headers = $session->{response}->headers(); | ||||
261 | my $status = $headers->{Status} || 200; | ||||
262 | my $variation = { | ||||
263 | contenttype => $contentType, | ||||
264 | lastmodified => $lastModified, | ||||
265 | data => $data, | ||||
266 | etag => $etag, | ||||
267 | isdirty => $isDirty, | ||||
268 | status => $status, | ||||
269 | }; | ||||
270 | $variation->{location} = $headers->{Location} if $status == 302; | ||||
271 | |||||
272 | # get cache-expiry preferences and add it to the bucket if available | ||||
273 | my $expire = $request->param("cache_expire"); | ||||
274 | $expire = $session->{prefs}->getPreference('CACHEEXPIRE') | ||||
275 | unless defined $expire; | ||||
276 | $variation->{expire} = CGI::Util::expire_calc($expire) | ||||
277 | if defined $expire; | ||||
278 | |||||
279 | if ( defined $variation->{expire} && $variation->{expire} !~ /^\d+$/ ) { | ||||
280 | print STDERR | ||||
281 | "WARNING: expire value '$variation->{expire}' is not recognized as a proper cache expiration value\n"; | ||||
282 | $variation->{expire} = undef; | ||||
283 | } | ||||
284 | |||||
285 | # store page variation | ||||
286 | Foswiki::Func::writeDebug("PageCache: Stored data") if TRACE; | ||||
287 | return undef | ||||
288 | unless $this->setPageVariation( $web, $topic, $variationKey, $variation ); | ||||
289 | |||||
290 | # assert newly autotetected dependencies | ||||
291 | $this->setDependencies( $web, $topic, $variationKey ); | ||||
292 | |||||
293 | return $variation; | ||||
294 | } | ||||
295 | |||||
296 | =begin TML | ||||
297 | |||||
298 | ---++ ObjectMethod getPage($web, $topic) | ||||
299 | |||||
300 | Retrieve a cached page for the given web.topic, using a variation | ||||
301 | key based on the current session. | ||||
302 | |||||
303 | =cut | ||||
304 | |||||
305 | sub getPage { | ||||
306 | my ( $this, $web, $topic ) = @_; | ||||
307 | |||||
308 | $web =~ s/\//./g; | ||||
309 | |||||
310 | Foswiki::Func::writeDebug("getPage($web.$topic)") if TRACE; | ||||
311 | |||||
312 | # check url param | ||||
313 | my $session = $Foswiki::Plugins::SESSION; | ||||
314 | my $refresh = $session->{request}->param('refresh') || ''; | ||||
315 | if ( $refresh eq 'all' ) { | ||||
316 | |||||
317 | # SMELL: restrict this to admins; put this somewhere else | ||||
318 | $this->deleteAll(); | ||||
319 | } | ||||
320 | |||||
321 | if ( $refresh eq 'fire' ) { # simulates a "save" of the current topic | ||||
322 | $this->fireDependency( $web, $topic ); | ||||
323 | } | ||||
324 | return undef if $refresh =~ m/^(on|cache|all|fire)$/; | ||||
325 | |||||
326 | # check cacheability | ||||
327 | return undef unless $this->isCacheable( $web, $topic ); | ||||
328 | |||||
329 | # check availability | ||||
330 | my $variationKey = $this->genVariationKey(); | ||||
331 | |||||
332 | my $variation = $this->getPageVariation( $web, $topic, $variationKey ); | ||||
333 | |||||
334 | # check expiry date of this entry; return undef if it did expire, not | ||||
335 | # deleted from cache as it will be recomputed during a normal view | ||||
336 | # cycle | ||||
337 | return undef | ||||
338 | if defined($variation) | ||||
339 | && defined( $variation->{expire} ) | ||||
340 | && $variation->{expire} < time(); | ||||
341 | |||||
342 | return $variation; | ||||
343 | } | ||||
344 | |||||
345 | =begin TML | ||||
346 | |||||
347 | ---++ ObjectMethod setPageVariation($web, $topici, $variationKey, $variation) -> $bool | ||||
348 | |||||
349 | stores a rendered page | ||||
350 | |||||
351 | =cut | ||||
352 | |||||
353 | sub setPageVariation { | ||||
354 | my ( $this, $web, $topic, $variationKey, $variation ) = @_; | ||||
355 | |||||
356 | die("virtual method"); | ||||
357 | } | ||||
358 | |||||
359 | =begin TML | ||||
360 | |||||
361 | ---++ ObjectMethod getPageVariation($web, $topic, $variationKey) | ||||
362 | |||||
363 | retrievs a cache entry; returns undef if there is none. | ||||
364 | |||||
365 | =cut | ||||
366 | |||||
367 | sub getPageVariation { | ||||
368 | die("virtual method"); | ||||
369 | } | ||||
370 | |||||
371 | =begin TML | ||||
372 | |||||
373 | Checks whether the current page is cacheable. It first | ||||
374 | checks the "refresh" url parameter and then looks out | ||||
375 | for the "CACHEABLE" preference variable. | ||||
376 | |||||
377 | =cut | ||||
378 | |||||
379 | sub isCacheable { | ||||
380 | my ( $this, $web, $topic ) = @_; | ||||
381 | |||||
382 | my $webTopic = $web . '.' . $topic; | ||||
383 | |||||
384 | my $isCacheable = $this->{isCacheable}{$webTopic}; | ||||
385 | return $isCacheable if defined $isCacheable; | ||||
386 | |||||
387 | #Foswiki::Func::writeDebug("... checking") if TRACE; | ||||
388 | |||||
389 | # by default we try to cache as much as possible | ||||
390 | $isCacheable = 1; | ||||
391 | |||||
392 | my $session = $Foswiki::Plugins::SESSION; | ||||
393 | |||||
394 | # POSTs and HEADs aren't cacheable | ||||
395 | my $method = $session->{request}->method; | ||||
396 | $isCacheable = 0 if $method && $method =~ m/^(?:POST|HEAD)$/; | ||||
397 | |||||
398 | if ($isCacheable) { | ||||
399 | |||||
400 | # check prefs value | ||||
401 | my $flag = $session->{prefs}->getPreference('CACHEABLE'); | ||||
402 | $isCacheable = 0 if defined $flag && !Foswiki::isTrue($flag); | ||||
403 | } | ||||
404 | |||||
405 | # don't cache 401 Not authorized responses | ||||
406 | my $headers = $session->{response}->headers(); | ||||
407 | my $status = $headers->{Status}; | ||||
408 | $isCacheable = 0 if $status && $status eq 401; | ||||
409 | |||||
410 | # TODO: give plugins a chance - create a callback to intercept cacheability | ||||
411 | |||||
412 | #Foswiki::Func::writeDebug("isCacheable=$isCacheable") if TRACE; | ||||
413 | $this->{isCacheable}{$webTopic} = $isCacheable; | ||||
414 | return $isCacheable; | ||||
415 | } | ||||
416 | |||||
417 | =begin TML | ||||
418 | |||||
419 | ---++ ObjectMethod addDependency($web, $topic) | ||||
420 | |||||
421 | Add a web.topic to the dependencies of the current page | ||||
422 | |||||
423 | =cut | ||||
424 | |||||
425 | sub addDependency { | ||||
426 | my ( $this, $depWeb, $depTopic ) = @_; | ||||
427 | |||||
428 | # exclude invalid topic names | ||||
429 | return unless $depTopic =~ m/^[[:upper:]]/; | ||||
430 | |||||
431 | # omit dependencies triggered from inside a dirtyarea | ||||
432 | my $session = $Foswiki::Plugins::SESSION; | ||||
433 | return if $session->inContext('dirtyarea'); | ||||
434 | |||||
435 | $depWeb =~ s/\//\./g; | ||||
436 | my $depWebTopic = $depWeb . '.' . $depTopic; | ||||
437 | |||||
438 | # exclude unwanted dependencies | ||||
439 | if ( $depWebTopic =~ m/^($Foswiki::cfg{Cache}{DependencyFilter})$/ ) { | ||||
440 | |||||
441 | #Foswiki::Func::writeDebug( "dependency on $depWebTopic ignored by filter $Foswiki::cfg{Cache}{DependencyFilter}") if TRACE; | ||||
442 | return; | ||||
443 | } | ||||
444 | else { | ||||
445 | |||||
446 | #Foswiki::Func::writeDebug("addDependency($depWeb.$depTopic)") if TRACE; | ||||
447 | } | ||||
448 | |||||
449 | # collect them; defer writing them to the database til we cache this page | ||||
450 | $this->{deps}{$depWebTopic} = 1; | ||||
451 | } | ||||
452 | |||||
453 | =begin TML | ||||
454 | |||||
455 | ---++ ObjectMethod getDependencies($web, $topic, $variationKey) -> \@deps | ||||
456 | |||||
457 | Return dependencies for a given web.topic. if $variationKey is specified, only | ||||
458 | dependencies of this page variation will be returned. | ||||
459 | |||||
460 | =cut | ||||
461 | |||||
462 | sub getDependencies { | ||||
463 | my ( $this, $web, $topic, $variationKey ) = @_; | ||||
464 | |||||
465 | die("virtual method"); | ||||
466 | |||||
467 | } | ||||
468 | |||||
469 | =begin TML | ||||
470 | |||||
471 | ---++ ObjectMethod getWebDependencies($web) -> \@deps | ||||
472 | |||||
473 | Returns dependencies that hold for all topics in a web. | ||||
474 | |||||
475 | =cut | ||||
476 | |||||
477 | sub getWebDependencies { | ||||
478 | my ( $this, $web ) = @_; | ||||
479 | |||||
480 | unless ( defined $this->{webDeps} ) { | ||||
481 | my $session = $Foswiki::Plugins::SESSION; | ||||
482 | my $webDeps = | ||||
483 | $session->{prefs}->getPreference( 'WEBDEPENDENCIES', $web ) | ||||
484 | || $Foswiki::cfg{Cache}{WebDependencies} | ||||
485 | || ''; | ||||
486 | |||||
487 | $this->{webDeps} = (); | ||||
488 | |||||
489 | # normalize topics | ||||
490 | foreach my $dep ( split( /\s*,\s*/, $webDeps ) ) { | ||||
491 | my ( $depWeb, $depTopic ) = | ||||
492 | $session->normalizeWebTopicName( $web, $dep ); | ||||
493 | |||||
494 | Foswiki::Func::writeDebug("found webdep $depWeb.$depTopic") | ||||
495 | if TRACE; | ||||
496 | $this->{webDeps}{ $depWeb . '.' . $depTopic } = 1; | ||||
497 | } | ||||
498 | } | ||||
499 | my @result = keys %{ $this->{webDeps} }; | ||||
500 | return \@result; | ||||
501 | } | ||||
502 | |||||
503 | =begin TML | ||||
504 | |||||
505 | ---++ ObjectMethod setDependencies($web, $topic, $variation, @topics) | ||||
506 | |||||
507 | Stores the dependencies for the given web.topic topic. Setting the dependencies | ||||
508 | happens at the very end of a rendering process of a page while it is about | ||||
509 | to be cached. | ||||
510 | |||||
511 | When the optional @topics parameter isn't provided, then all dependencies | ||||
512 | collected in the Foswiki::PageCache object will be used. These dependencies | ||||
513 | are collected during the rendering process. | ||||
514 | |||||
515 | =cut | ||||
516 | |||||
517 | sub setDependencies { | ||||
518 | my ( $this, $web, $topic, $variationKey, @topicDeps ) = @_; | ||||
519 | |||||
520 | @topicDeps = keys %{ $this->{deps} } unless @topicDeps; | ||||
521 | |||||
522 | die("virtual method"); | ||||
523 | } | ||||
524 | |||||
525 | =begin TML | ||||
526 | |||||
527 | ---++ ObjectMethod deleteDependencies($web, $topic, $variation, $force) | ||||
528 | |||||
529 | Remove a dependency from the graph. This operation is normally performed | ||||
530 | as part of a call to Foswiki::PageCache::deletePage(). | ||||
531 | |||||
532 | =cut | ||||
533 | |||||
534 | sub deleteDependencies { | ||||
535 | die("virtual method"); | ||||
536 | } | ||||
537 | |||||
538 | =begin TML | ||||
539 | |||||
540 | ---++ ObjectMethod deletePage($web, $topic, $variation, $force) | ||||
541 | |||||
542 | Remove a page from the cache; this removes all of the information | ||||
543 | that we have about this page, including any dependencies that have | ||||
544 | been established while this page was created. | ||||
545 | |||||
546 | If $variation is specified, only this variation of $web.$topic will | ||||
547 | be removed. When $variation is not specified, all page variations of $web.$topic | ||||
548 | will be removed. | ||||
549 | |||||
550 | When $force is true, the deletion will take place immediately. Otherwise all | ||||
551 | delete requests might be delayed and committed as part of | ||||
552 | Foswiki::PageCache::finish(). | ||||
553 | |||||
554 | =cut | ||||
555 | |||||
556 | sub deletePage { | ||||
557 | die("virtual method"); | ||||
558 | } | ||||
559 | |||||
560 | =begin TML | ||||
561 | |||||
562 | ---++ ObjectMethod deleteAll() | ||||
563 | |||||
564 | purges all of the cache | ||||
565 | |||||
566 | =cut | ||||
567 | |||||
568 | sub deleteAll { | ||||
569 | die("virtual method"); | ||||
570 | } | ||||
571 | |||||
572 | =begin TML | ||||
573 | |||||
574 | ---++ ObjectMethod fireDependency($web, $topic) | ||||
575 | |||||
576 | This method is called to remove all other cache entries that | ||||
577 | used the given $web.$topic as an ingredience to produce the page. | ||||
578 | |||||
579 | A dependency is a directed edge starting from a page variation being rendered | ||||
580 | towards a depending page that has been used to produce it. | ||||
581 | |||||
582 | While dependency edges are stored as they are collected during the rendering | ||||
583 | process, these edges are traversed in reverse order when a dependency is | ||||
584 | fired. | ||||
585 | |||||
586 | In addition all manually asserted dependencies of topics in a web are deleted, | ||||
587 | as well as the given topic itself. | ||||
588 | |||||
589 | =cut | ||||
590 | |||||
591 | sub fireDependency { | ||||
592 | die("virtual method"); | ||||
593 | } | ||||
594 | |||||
595 | =begin TML | ||||
596 | |||||
597 | ---++ ObjectMethod renderDirtyAreas($text) | ||||
598 | |||||
599 | Extract dirty areas and render them; this happens after storing a | ||||
600 | page including the un-rendered dirty areas into the cache and after | ||||
601 | retrieving it again. | ||||
602 | |||||
603 | =cut | ||||
604 | |||||
605 | sub renderDirtyAreas { | ||||
606 | my ( $this, $text ) = @_; | ||||
607 | |||||
608 | Foswiki::Func::writeDebug("called renderDirtyAreas") if TRACE; | ||||
609 | |||||
610 | my $session = $Foswiki::Plugins::SESSION; | ||||
611 | $session->enterContext('dirtyarea'); | ||||
612 | |||||
613 | # remember the current page length to recompute the content length below | ||||
614 | my $found = 0; | ||||
615 | my $topicObj = | ||||
616 | new Foswiki::Meta( $session, $session->{webName}, $session->{topicName} ); | ||||
617 | |||||
618 | # expand dirt | ||||
619 | while ( $$text =~ | ||||
620 | s/<dirtyarea([^>]*?)>(?!.*<dirtyarea)(.*?)<\/dirtyarea>/$this->_handleDirtyArea($1, $2, $topicObj)/ges | ||||
621 | ) | ||||
622 | { | ||||
623 | $found = 1; | ||||
624 | } | ||||
625 | |||||
626 | $$text =~ s/([\t ]?)[ \t]*<\/?(nop|noautolink)\/?>/$1/gis if $found; | ||||
627 | |||||
628 | # remove any dirtyarea leftovers | ||||
629 | $$text =~ s/<\/?dirtyarea>//g; | ||||
630 | |||||
631 | $session->leaveContext('dirtyarea'); | ||||
632 | } | ||||
633 | |||||
634 | # called by renderDirtyAreas() to process each dirty area in isolation | ||||
635 | sub _handleDirtyArea { | ||||
636 | my ( $this, $args, $text, $topicObj ) = @_; | ||||
637 | |||||
638 | Foswiki::Func::writeDebug("called _handleDirtyArea($args)") | ||||
639 | if TRACE; | ||||
640 | |||||
641 | #Foswiki::Func::writeDebug("in text=$text") if TRACE; | ||||
642 | |||||
643 | # add dirtyarea params | ||||
644 | my $params = new Foswiki::Attrs($args); | ||||
645 | my $session = $Foswiki::Plugins::SESSION; | ||||
646 | my $prefs = $session->{prefs}; | ||||
647 | |||||
648 | $prefs->pushTopicContext( $topicObj->web, $topicObj->topic ); | ||||
649 | $params->remove('_RAW'); | ||||
650 | $prefs->setSessionPreferences(%$params); | ||||
651 | try { | ||||
652 | $text = $topicObj->expandMacros($text); | ||||
653 | $text = $topicObj->renderTML($text); | ||||
654 | }; | ||||
655 | finally { | ||||
656 | $prefs->popTopicContext(); | ||||
657 | }; | ||||
658 | |||||
659 | #Foswiki::Func::writeDebug("out text='$text'") if TRACE; | ||||
660 | return $text; | ||||
661 | } | ||||
662 | |||||
663 | =begin TML | ||||
664 | |||||
665 | ---++ ObjectMethod finish() | ||||
666 | |||||
667 | clean up finally | ||||
668 | |||||
669 | =cut | ||||
670 | |||||
671 | sub finish { | ||||
672 | } | ||||
673 | |||||
674 | 1 | 2µs | 1; | ||
675 | __END__ |