Revert rss20 to using $ENV{PATH_INFO} in self link.
[blosxom-plugins.git] / general / entries_cache_meta
blob0a112b1e15f4d01b53b35b6b283e64a5ce1be295
1 # vim: ft=perl
2 # Blosxom Plugin: entries_cache_meta
3 # Author(s): Jason Thaxter <sseye@ahab.com>
4 # Version: 0.6
6 package entries_cache_meta;
8 # --- Configurable variables -----
11 # Caching variables
14 # How many minutes delay before entries are re-indexed?
15 # (In minutes)
16 # default: 15
17 # zero means never auto-reindex
18 $cache_period;
20 # reindex password via query string to force re-indexing
21 # default: none (re-index by time only)
22 $reindex_passwd;
24 # if true, this will warn when the cachefile can't be locked
25 # default: off
26 $write_lock_warn;
29 # Meta-related variables
32 # What prefix should I expect prepended to each meta tag?
33 # default: meta-
34 $meta_prefix;
36 # If set, this meta variable will enable you to set modification times
37 # e.g.:
38 #   meta-last_modified: 2003/01/12 10:33:33
39 # see pod docs re: time format
40 # default: mtime
41 $meta_mtime;
43 # enabled
44 # default: 1
45 # set to zero to disable this plugin
46 # good for use w/ config plugin
47 $enabled;
49 # --------------------------------
51 # defaults
52 $cache_period = 15 if not defined $cache_period;
53 $meta_prefix ||= 'meta-';
54 $meta_mtime = 'mtime' unless defined $meta_mtime;
55 $enabled    = 1       unless defined $enabled;
57 # standard perl modules
58 use Fcntl qw(:DEFAULT :flock);
59 use File::stat;
60 use File::Find;
61 use Data::Dumper;
63 # standard for blosxom
64 use CGI qw/:standard/;
66 # we'll use Date::Parse if we have it
67 $have_date_parse;
68 eval { require Date::Parse; };
69 if ($@) {
70     use Time::Local;
72 else {
73     $have_date_parse = 1;
76 %cache;    # the cache
77 %indexes = ();    # nothing - we don't do static
78 $reindex;         # reindex or not
79 *CACHE;           # for the cache file
80 $reindexed;       # flag for "save cache"
82 # the filename where the cache is stored
83 $cachefile = "$blosxom::plugin_state_dir/entries_cache_meta";
84 # and a temporary one while generating the cache
85 $cache_tmp = "$cachefile.TMP";
87 sub start {
89     return 0 if ( not $enabled or param('-all') );
91     # Read cache and reindex if failed or otherwise directed
92     (
93         (
94             not( $reindex_passwd and param('reindex') eq $reindex_passwd )
95               and not( $cache_period
96                 and -f "$cachefile"
97                 and stat($cachefile)->mtime lt( time() - $cache_period * 60 ) )
98         )
99           and ( open( MYCACHE, $cachefile )
100             and $index = join '', <MYCACHE>
101             and $index =~ /\$VAR1 = /
102             and eval($index)
103             and !$@
104             and %cache = %$VAR1
105             and close MYCACHE )
107       )
108       or $reindex = &lokkit( \*CACHE );
110     return 1;
113 sub entries {
115     # If we haven't flagged a need to re-index,
116     # copy from the cache
117     return sub {
118         my %files;
119         foreach ( keys %cache ) {
121             # copy into cache if the file exists
122             $files{$_} = $cache{$_}{mtime} if -f $_;
123         }
124         return ( \%files, \%indexes );
125       }
126       unless $reindex;
128     # otherwise, do a full reindex
129     return sub {
131         # Check to see if previously indexed files exist, and then rescan
132         # the datadir for any new files, while preserving the old times
134         for my $file ( keys %cache ) {
135             -f $file or do { $reindexed++; delete $cache{$file} };
136         }
138         find(
139             sub {
141                 # easier to read.
142                 $ffname = $File::Find::name;
144                 # return if we're out of our depth
145                 my $curr_depth = $File::Find::dir =~ tr[/][];
146                 if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
147                     $cache{$ffname}
148                       and delete $cache{$ffname};
149                     return;
150                 }
152                 # only bother for real entries
153                 return
154                   unless ( $ffname =~
155 m!^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$!
156                     and $2 ne 'index'
157                     and $2 !~ /^\./
158                     and ( -r "$ffname" )
159                     and ++$reindexed );
161                 # process meta tags
162                 if ( -T "$ffname" and open( FF, "<$ffname" ) ) {
163                     $meta_title = <FF>;
164                     chomp($meta_title);
165                     $cache{$ffname}{title} = $meta_title;
166                     while (<FF>) {
168                         # get values
169                         my ( $key, $value ) = m/^$meta_prefix(\w+)\s*:\s*(.+)$/;
170                         last if not $key;
172                         # set values
173                         if ( $key ne "$meta_mtime" ) {
174                             $cache{$ffname}{$key} = $value;
175                         }
177                         # set modification time
178                         # with a double negative (hey)
179                         else {
180                             if ($have_date_parse) {
181                                 $time = Date::Parse::str2time($value);
182                             }
183                             elsif (
184                                 my ( $yr, $mo, $day, $hr, $min, $sec, $tz ) = (
185                                     $value =~
186 m! (\d{4}) [/\:\-] (\d{2}) [/\:\-] (\d{2}) \s+
187                                       (\d{2}) : (\d{2}) : (\d{2})
188                                       \s*((UTC|GMT)*)!x
189                                 )
190                               )
191                             {
192                                 if ($tz) {
193                                     $time =
194                                       timegm( $sec, $min, $hr, $day, --$mo,
195                                         $yr );
196                                 }
197                                 else {
198                                     $time =
199                                       timelocal( $sec, $min, $hr, $day, --$mo,
200                                         $yr );
201                                 }
202                             }
203                             else {
204                                 warn "unparseable time in $ffname: $value";
205                             }
206                             $cache{$ffname}{mtime} = $time;
207                         }
208                     }
209                     close FF;
210                 }
211                 else {
212                     warn "couldn't open entry ($ffname): $!";
213                 }
215                 # If we have no meta mtime and no cached time,
216                 # stat the file and store it.
217                 if ( $cache{$ffname}{mtime} ) {
218                     $files{$ffname} = $cache{$ffname}{mtime};
219                 }
220                 else {    # make sure blosxom behaves normally
221                     $files{$ffname} = stat($ffname)->mtime;
222                     $cache{$ffname}{mtime} = $files{$ffname};
223                 }
225                 # show or not to show future entries
226                 if ( not $blosxom::show_future_entries
227                     and $files{$ffname} > time() )
228                 {
229                     delete $files{$ffname} and return;
230                 }
232             },
233             $blosxom::datadir
234         );
236         return ( \%files, \%indexes );    # indexes is bogus: we don't do static
237       }
240 # put the stuff into $meta:: namespace for story rendering
241 # and trim meta tags out of our story
242 sub story {
243     my ( $pkg, $path, $filename, $story_ref, $title_ref, $body_ref ) = @_;
245     # first, load up our cached meta values
246     foreach my $key ( keys %{ $cache{$filename} } ) {
247         ${"meta::$key"} = $cache{$filename}{$key};
248     }
250     # next, remove any meta tags from the body
251     my ( $body, $in_body );
252     foreach ( split /\n/, $$body_ref ) {
253         if ($in_body) {
254             $body .= "$_\n";
255         }
256         elsif (/^$meta_prefix(\w+)\s*:\s*(.+)$/) {
257             ;
258         }
259         else {
260             $in_body = 1;
261             $body .= "$_\n";
262         }
263     }
265     $$body_ref = $body;
267     return 1;
270 # save cache - as late as possible
271 # this gives other plugins a chance to alter values
272 # or, forbid us from caching them!
273 sub end {
274     &stuffit( \*CACHE, Dumper \%cache ) if ($reindexed);
275     1;
278 #####################################################################
279 # Internal subs
282 # Reserve file for writing
283 sub lokkit {
284     local *FH = shift;
286     # open it
287     my $umask = umask 002;    # group-write is useful
288     sysopen( FH, "$cache_tmp", O_RDWR | O_CREAT )
289       or ( warn "can't open file $cache_tmp: $!" and return );
290     umask $umask;             # restore so it doesn't affect other plugins
292     # lock file
293     flock( FH, LOCK_EX | LOCK_NB )
294       or (  close FH
295         and ( not $write_lock_warn or warn "can't lock file: $!" )
296         and return );
298     return 1;
301 # Write and release file
302 sub stuffit {
303     local *FH       = shift;
304     local $contents = shift;
306     my $err;
307     print FH $contents or $err = "can't write cache $cache_tmp: $!";
308     flock( FH, LOCK_UN ) or $err = "can't unlock cache $cache_tmp: $!";
309     close FH or $err = "can't close cache $cache_tmp: $!";
311     if ( not $err ) {
312         rename "$cache_tmp", $cachefile
313           or warn "Error installing cache $cache_tmp: $!";
314     }
315     else {
316         warn $err;
317         unlink "$cache_tmp";
318     }
323 __END__
325 =head1 NAME - entries_cache_meta
327 Blosxom Plug-in: entries_cache_meta
329 =head1 DESCRIPTION
331 The entries_cache_meta plugin is Yet Another Caching Plugin, with some
332 additional features, notably meta-tagging.
334 By combining the meta-tag functionality with the entries cache, it becomes
335 possible to write or use plugins that access meta-values outside the C<story>
336 hook. For example, you could use this plugin to write another one showing the
337 most recent entry for each author defined in meta tags.
339 Additionally, it makes use of file locking to avoid from re-indexing if another
340 request already has re-indexing in progress. Simultaneous re-indexing could
341 corrupt the cache file, or bog down the server with redundant, expensive tasks;
342 the larger and busier the site, the more likely these problems. For added
343 security, you must supply a password if you want to force re-indexing via query
344 string; this prevents any Blosxom-savvy user from forcing a re-index on your
345 site.
347 =head1 USAGE
349 A properly permissioned $plugin_state_dir is required. Configuration is
350 optional, but there are a few useful configuration variables.
352 =over 4
354 =item $cache_period
356 How often, in minutes, to reindex automatically. If set to zero, re-indexing will never
357 be automatic.
359 =item $reindex_passwd
361 What query string password forces reindex.  You can force a scan by appending a
362 query string, e.g.  C<?reindex=mypassword> to the end of the url.  This will
363 also cause meta tags to be updated. However, for security's sake, you must
364 set the password in the configuration to use this.
366 =item $write_lock_warn
368 Send a warning to the error log when it can't get a write-lock on the cache
369 file. This is mostly useful for verifying that write-locking actually does
370 something.
372 =item $meta_prefix
374 What prefix marks meta variable names. Defaults to C<meta->. An empty string
375 should be permitted, but currently it isn't.
377 =item $meta_mtime
379 The name of the meta-property for file modification times. If this property is
380 present in a given entry, it will override the modification time of the file as
381 the entry's time. The default value is "mtime".
383 The format of the time is obviously important: it can be either
385         meta-mtime: YYYY/MM/DD HH:MM:SS [UTC|GMT]
387 or, if Date::Parse is present, any format parseable by that module. Either way,
388 unparseable dates are simply ignored as if the property was not present. This
389 feature may be disabled by setting it to an empty string.
391 =back
393 =head1 VERSION
395 Version 0.6
397 =head1 AUTHOR
399 Jason Thaxter <sseye@ahab.com>
401 This plugin is now maintained by the Blosxom Sourceforge Team,
402 <blosxom-devel@lists.sourceforge.net>.
404 =head1 SEE ALSO
406 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net
408 Blosxom Plugin Docs: http://blosxom.sourceforge.net/documentation/users/plugins.html
410 =head1 BUGS
412 It doesn't currently work in static mode, in part because the premise of static
413 mode is to be a performance benefit all on its own. Also because I don't use
414 static mode and I'm lazy.
416 Please send bug reports and feedback to the Blosxom development mailing list 
417 <blosxom-devel@lists.sourceforge.net>.
419 =head1 LICENSE
421 entries_cache_meta plugin
422 Copyright 2004, Jason Thaxter
424 Permission is hereby granted, free of charge, to any person obtaining a
425 copy of this software and associated documentation files (the "Software"),
426 to deal in the Software without restriction, including without limitation
427 the rights to use, copy, modify, merge, publish, distribute, sublicense,
428 and/or sell copies of the Software, and to permit persons to whom the
429 Software is furnished to do so, subject to the following conditions:
431 The above copyright notice and this permission notice shall be included
432 in all copies or substantial portions of the Software.
434 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
435 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
436 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
437 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
438 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
439 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
440 OTHER DEALINGS IN THE SOFTWARE.