Added a configurable variable enabling regeneration of statically rendered files...
[matthijs/upstream/blosxom-plugins.git] / general / entries_cache
1 # Blosxom Plugin: entries_cache -*- perl -*-
2 # Author(s): Fletcher T. Penney <http://fletcher.freeshell.org/> 
3 # Modified by Steve Schwarz <http://agilitynerd.com/>
4 # Version: 0.92
5 # Based on entries_index by Rael Dornfest
6 # Documentation help contributed by Iain Cheyne
7
8 package entries_cache;
9
10 # --- Configurable variables -----
11
12 $delay = 0 unless defined $delay;
13                         # How many minutes delay before entries are re-indexed?
14                         # Set to 0 to force reindexing everytime - this
15                         # will provide the same behavior as Rael's
16                         # entries_index.  Though, I am not sure why one 
17                         # would do this.  At least use a small cache time to
18                         # improve performance....
19                         # SAS - I set this to zero so comments and new files
20                         # are immediately visible.
21 $indexname = "$blosxom::plugin_state_dir/.entries_cache.index" unless defined $indexname;
22 $others_indexname = "$blosxom::plugin_state_dir/.entries_cache.others" unless defined $others_indexname;
23
24
25 $use_date_tags = 1 unless defined $use_date_tags;
26         # Set to 1 to enable parsing meta- keywords
27         # for date tags
28
29 $use_UK_dates = 0 unless defined $use_UK_dates;
30         # Default is mm/dd/yy (US)
31         # Set to 1 to use dd/mm/yy (UK)
32
33 $update_meta_date = 0 unless defined $update_meta_date;
34         # Add a meta_date tag if it doesn't exist
35         # This will require that perl has write access to your story files
36         # and that you are using the meta plugin.
37         
38         # NOTE: As of version 0.91, you do not NEED the meta plugin...
39
40         # Be sure to save your text files with UNIX line endings
41         # (any decent text editor should be able to do this) or use the blok
42         # plugin (http://www.enilnomi.net/download.html#blok).
43         # There is some logic in entries_cache to fix this, but do not rely
44         # on it.
45
46         # Lastly, make sure there is at least one blank line between the
47         # entry title and the body. Again, there is some logic in
48         # entries_cache to fix this, but do not rely on it.
49         
50         # The default meta-keywords are compatible with Eric Sherman's
51         # entries_index_tagged defaults:
52         
53         # http://primitiveworker.org/blo.g/development/blosxom/entries_index_tagged/
54
55 $meta_timestamp = "meta-creation_timestamp:" unless defined $meta_timestamp;
56         # timestamp_tag is the non-human readable date stamp format
57         # used by entries_index, entries_cache, entries_index_tagged,
58         # and blosxom
59
60 $meta_date = "meta-creation_date:" unless defined $meta_date;
61         # date_tag is a human readable version
62
63 $strip_meta_dates = 1 unless defined $strip_meta_dates; 
64         # Strip meta-tags from story so that 
65         # they are not displayed.  Unnecessary if you are running the 
66         # meta plugin.
67
68 $update_static_rendering = 0 unless defined $update_static_rendering;
69         # Recreate statically rendered files.
70         # When set to 0, Blosxom will not update statically rendered files
71         # even when stories are edited afterwards (Fletcher Penney's default).
72         # Set to 1 if you want to be able to fix typos in statically rendered stories.
73
74 $debug = 0 unless defined $debug;
75         # Debugging flag
76
77 # --------------------------------
78
79 use File::stat;
80 use File::Find;
81 use CGI qw/:standard/;
82 use Time::Local;
83
84 my $time = time();
85 my $reindex = 0;
86
87 sub start {
88         # Force a reindex
89         $reindex = 1 if (CGI::param('reindex'));
90         return 1;
91 }
92
93
94 sub entries {
95         
96         return sub {
97                 my(%files, %indexes, %others);
98         
99                 # Read cached index
100                 if ( open CACHE, $indexname) {
101                         while ($line = <CACHE>) {
102                                 # Improved backwards compatibility with entries_index
103                                 if ($line =~ /\s*'?(.*?)'?\s*=>\s*(\d*),?/) {
104                                         $files{$1} = $2;
105                                 }
106                         }
107                         close CACHE;
108                         # See if it's time to reindex
109                         $reindex = 1 if ( stat($indexname)->mtime lt ($time - $delay*60) );
110                 } else {
111                         # No index found, so we need to index
112                         $reindex = 1;
113                 }
114
115                 # Read cached others index
116                 if ( open CACHE, $others_indexname) {
117                         while ($line = <CACHE>) {
118                                 # Improved backwards compatibility with entries_index
119                                 if ($line =~ /\s*'?(.*)'?\s*=>\s*(.*),?/) {
120                                         $others{$1} = $2;
121                                 }
122                         }
123                         close CACHE;
124                         # See if it's time to reindex
125                         $reindex = 1 if ( stat($others_indexname)->mtime lt ($time - $delay*60) );
126                 } else {
127                         # No index found, so we need to index
128                         $reindex = 1;
129                 }
130
131
132                 # Perform reindexing if necessary
133                 # This code was originally copied from entries_index by Rael Dornfest
134                 # Check to see if previously indexed files exist, and then rescan
135                 # the datadir for any new files, while preserving the old times
136
137                 # Static mode requires some of the code in this section, and the
138                 # speed hit is unimportant for static blogs
139                 
140                 if ($blosxom::static_or_dynamic eq "static") {
141                         $reindex = 1;
142                 }
143
144
145                 if ($reindex eq 1) {
146                         
147                         # If any files not available, err on side of caution and reindex
148                         for my $file (keys %files) { -f $file or do { $reindex++; delete $files{$file} }; }
149                         for my $other (keys %others) { -f $other or do { $reindex++; delete $others{$other} }; }
150
151
152                         find(
153                                 sub {
154                                         my $d; 
155                                         my $curr_depth = $File::Find::dir =~ tr[/][]; 
156
157                                         #       if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
158                                         #       # We are beyond depth, so remove files
159                                         #       $files{$File::Find::name} and delete $files{$File::Find::name};
160                                         #       $others{$File::Find::name} and delete $others{$File::Find::name};
161                                         #       return;
162                                         #}
163     
164                                 # Adding support for %others
165                                 if (
166                                         $File::Find::name =~ 
167                                                 m!^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$!
168                                                 and $2 ne 'index' and $2 !~ /^\./ and (-r $File::Find::name)
169                                         ) {
170                                     # SAS modified so if meta-creation_date or file timestamp are in the future
171                                     # and we don't want future entries the file won't be included in the file list
172                                     # the index list
173                                     if (!$blosxom::show_future_entries and 
174                                         ((extract_date($File::Find::name, $files{$File::Find::name}) > time) or
175                                          (stat($File::Find::name)->mtime > time))) {
176                                         $files{$File::Find::name} and delete $files{$File::Find::name};
177                                         $others{$File::Find::name} and delete $others{$File::Find::name};
178                                     } else {
179                                                 ( $files{$File::Find::name} || ++$reindex )
180                                                 and ( $files{$File::Find::name} = 
181                                                         extract_date($File::Find::name,$files{$File::Find::name}) ||
182                                                         $files{$File::Find::name} ||
183                                                         stat($File::Find::name)->mtime )
184                                                  
185                                                 # Static
186                                                 and (
187                                                         param('-all') 
188                                                         or !-f "$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0]
189 # Trying to fix for static mode
190                                                         or ( $update_static_rendering && stat("$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0])->mtime < stat($File::Find::name)->mtime )
191                                                         or stat("$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0])->mtime < $files{$File::Find::name}
192                                                 )
193                                                 and $indexes{$1} = 1
194                                                 and $d = join('/', (blosxom::nice_date($files{$File::Find::name}))[5,2,3])
195                                                 and $indexes{$d} = $d
196                                                 and $blosxom::static_entries and $indexes{ ($1 ? "$1/" : '') . "$2.$blosxom::file_extension" } = 1;
197                                             }
198                                         } else {
199                                                 !-d $File::Find::name and -r $File::Find::name and $others{$File::Find::name} = stat($File::Find::name)->mtime
200                                         }
201                                 }, $blosxom::datadir
202                         );
203
204
205                         if ( $reindex ) {
206                                 # The index was recreated, so we should record the new version
207                                 if ( open ENTRIES, "> $indexname" ) {
208                                         foreach (sort keys %files) {
209                                                 print ENTRIES "$_=>$files{$_}\n";
210                                         }
211                                         close ENTRIES;
212                                 } else {
213                                         warn "couldn't > $indexname: $!\n";
214                                 }
215
216                                 if ( open ENTRIES, "> $others_indexname" ) {
217                                         foreach (sort keys %others) {
218                                                 print ENTRIES "$_=>$others{$_}\n";
219                                         }
220                                         close ENTRIES;
221                                 } else {
222                                         warn "couldn't > $others_indexname: $!\n";
223                                 }
224                         }
225                 }
226         return (\%files, \%indexes, \%others);
227         }
228 }
229  
230  
231 sub extract_date {
232         my ($file, $indexed_date) = @_;
233         my $new_story = "";
234         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
235         
236         warn "Entries_Cache: Checking $file for meta-tag\n\tComparing to $indexed_date\n" if ($debug == 1);
237         
238         # This is an attempt for compatibility with Eric Sherman's entries_index_tagged
239         # But it does not handle as many date formats, as there are too many additional
240         # necessary modules that I am not willing to require
241         
242         if ( $use_date_tags != 0) {
243         
244                 open (FILE, $file);
245                 $line = <FILE>;         # Read first line ( ie the title)
246                 $new_story .= $line;
247                 
248                 # now, parse the story, and try to correct misformatted stories
249                 while ($line = <FILE>) {
250                         if ($line =~ /^$meta_timestamp\s*(\d+)/) {
251                                 # If present, this format is used
252                                 close File;
253                                 $result = $1;
254                                 warn "Entries_Cache: Found meta_timestamp $result for $file\n" if ($debug == 1);
255                                 return $result;
256                         }
257                         
258                         if ($line =~ /^$meta_date\s*(.*)/) {
259                                 close File;
260                                 $result = $1;
261                                 warn "Entries_Cache: Found meta-date $result for $file\n" if ($debug == 1);
262                                 return parsedate($result);
263                         }
264                         
265                         if ( $line !~ /^meta.*?:/i) {
266                                 # line doesn't start with meta...  (ie story was not formatted
267                                 # for meta-tags), or the meta-tags are finished
268                                 
269                                 if ($update_meta_date eq 1) {
270                                         # Don't mess with stories unless using UNIX line endings.
271                                         
272                                         if (($line =~ /\r/) || ($new_story =~ /\r/)) {
273                                                 warn "Entries_Cache: File $file has non-UNIX line endings; cannot update metatags...\n";
274                                                 close FILE;
275                                                 return 0;
276                                         }
277                                         
278                                         warn "Entries_Cache: Updating meta-tag for $file\n" if ($debug == 1);
279                                         if ($indexed_date eq 0 || $indexed_date == "") {
280                                                 $indexed_date = stat($file)->mtime;
281                                                 warn "Entries_Cache: No date for $file, using $indexed_date\n" if ($debug == 1);
282                                         }
283                                         
284                                         ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($indexed_date);
285                                         $year += 1900;
286                                         $mon += 1;
287                                         $hour = sprintf("%02d",$hour);
288                                         $min = sprintf("%02d",$min);
289                                         $sec = sprintf("%02d",$sec);
290
291                                         warn "Entries_Cache: Adding meta-tag to $file, using $meta_date $mday/$mon/$year $hour:$min:$sec\n" if ($debug == 1);
292                                 
293                                         if ($use_UK_dates eq 1) {
294                                                 $new_story .= "$meta_date $mday/$mon/$year $hour:$min:$sec\n\n";
295                                         } else {
296                                                 $new_story .= "$meta_date $mon/$mday/$year $hour:$min:$sec\n\n";
297                                         }
298                                         
299                                         if ( $line !~ /^\s*$/) {
300                                                 # If this line wasn't empty, then add it to story
301                                                 $new_story .= $line;
302                                         }
303                                         
304                                         while ($line = <FILE>) {
305                                                 # read remainder of story
306                                                 $new_story .= $line;
307                                         }
308                                         
309                                         close FILE;
310                                         open (FILE, "> $file") or warn "Unable to update date meta-tag on $file\n";
311                                         print FILE $new_story;
312                                         close FILE;
313                                         return 0;
314                                 } else {
315                                         close FILE;
316                                         return 0;
317                                 }
318                         }
319
320                         $new_story .= $line;
321                 }
322         }
323         return 0;       
324 }
325
326 sub parsedate {
327         my ($datestring) = @_;
328         #warn "Parsing $datestring\n";
329         
330         # Possible formatting
331         # Month can be 3 letter abbreviation or full name (in English)
332         # Time must be hh:mm or hh:mm:ss  in 24 hour format
333         # Year must be yyyy
334         # The remaining 1 or 2 digits are treated as date
335         # ie: May 25 2003 18:40 
336         # order is not important as long as pieces are there
337                 
338         # Convert the datestring to a time() format
339
340         # Find "Shorthand" Date
341         if ( $datestring =~ /\d\d?\/\d\d?\/\d\d\d?\d?/) {
342                 if ( $use_UK_dates eq 0) {
343                         # Use US Formatting
344                         $datestring =~ s/(\d\d?)\/(\d\d?)\/(\d\d\d?\d?)//;
345                         $mon = $1 - 1;
346                         $day = $2;
347                         $year = $3;
348                 } else {
349                         # Use UK Formatting
350                         $datestring =~ s/(\d\d?)\/(\d\d?)\/(\d\d\d?\d?)//;
351                         $mon = $2 - 1;
352                         $day = $1;
353                         $year = $3;
354                 }
355                 
356                 # Now, clean up year if 2 digit
357                 # You may change the 70 to whatever cutoff you like
358                 $year += 2000 if ($year < 70 );
359                 $year += 1900 if ($year < 100);
360         }
361         
362         # Find Month
363         $mon = 0 if ($datestring =~ s/(Jan|January)//i);
364         $mon = 1 if ($datestring =~ s/(Feb|February)//i);
365         $mon = 2 if ($datestring =~ s/(Mar|March)//i);
366         $mon = 3 if ($datestring =~ s/(Apr|April)//i);
367         $mon = 4 if ($datestring =~ s/(May)//i);
368         $mon = 5 if ($datestring =~ s/(Jun|June)//i);
369         $mon = 6 if ($datestring =~ s/(Jul|July)//i);
370         $mon = 7 if ($datestring =~ s/(Aug|August)//i);
371         $mon = 8 if ($datestring =~ s/(Sep|September)//i);
372         $mon = 9 if ($datestring =~ s/(Oct|October)//i);
373         $mon = 10 if ($datestring =~ s/(Nov|November)//i);
374         $mon = 11 if ($datestring =~ s/(Dec|December)//i);
375
376         # Find Time
377         if ($datestring =~ s/(\d\d?):(\d\d)(:\d\d)?//) {
378                 $hour = $1;
379                 $min = $2;
380                 $sec = $3;
381         }
382         
383         if ($datestring =~ s/(\d\d\d\d)//) {
384                 $year = $1;
385         }
386         
387         if ($datestring =~ s/(\d\d?)//) {
388                 $day = $1;
389         }
390         
391         return timelocal($sec,$min,$hour,$day,$mon,$year);
392         
393 }
394
395 sub story {
396         return 1 if (! $strip_meta_dates);
397         # This code based on Rael's meta plugin
398         my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
399         
400         # Strip date-based meta tags from body
401         $$body_ref =~ s/^$meta_timestamp.*//g;
402         $$body_ref =~ s/^$meta_date.*//g;
403         
404         
405         return 1;
406 }
407
408 1;
409
410 __END__
411
412 =head1 NAME
413
414 Blosxom Plug-in: entries_cache
415
416 =head1 SYNOPSIS
417
418 Purpose: This plugin reserves original creation timestamp on weblog 
419 entries, allowing for editing of entries without altering the original 
420 creation time. It maintains an index ($blosxom::plugin_state_dir/
421 .entries_cache.index) of filenames and their creation times. It also adds 
422 new entries to the index the first time Blosxom come across them. In 
423 addition, it is possible to rely on entries_cache inserting date-based meta 
424 tags automatically into entries - this is enabled by the $use_date_tags 
425 setting. This makes the entries more portable. Note that if this approach 
426 is used, the meta plugin is required: http://www.blosxom.com/downloads/
427 plugins/meta.  (NOTE: As of version 0.91 you can use this without the meta
428 plugin.)
429
430 Replaces the default $blosxom::entries subroutine
431
432 The entries_cache plugin is a "souped-up" version of the entries_index 
433 plugin.  It maintains file modification times in the same manner as the 
434 original plugin, but goes one-step further. It uses the modification time 
435 of the index file to determine whether to rescan the datadir.  If $delay 
436 minutes have not passed, it relies on the cached information.
437
438 You can force a manual scan by appending ?reindex=y to the end of your base 
439 url.
440
441 The reason for this change is that the original blosxom and the 
442 entries_index plugin rescan the datadir each time a page is viewed. This 
443 plugin allows you to cache the information to speed up processing times on 
444 most page views.  According to several posts on the blosxom mailing list, 
445 this is one of the big processor hogs. With a $delay setting of 60 minutes, 
446 there will only be one page view each hour that has to wait for the full 
447 directory scan. To be honest, I have not noticed much of a speed boost 
448 during my testing yet, but I imagine it would only appear for sites with a 
449 large number of files to be indexed.
450
451
452 =head1 VERSION
453
454 0.92
455
456 =head1 VERSION HISTORY
457 0.92    Checks if meta-creation_date is in the future and only displays
458         entry if $show_future_entries is set. Otherwise future articles
459         (by meta-creation_date or file modified time) are not displayed.
460         Don't think this breaks static rendering... haven't tested it.
461         
462 0.91    Improved documentation courtesy of Iain Cheyne - THANKS!!!
463                 Now, if you are not running the meta plugin, you will not see any meta tags in your stories
464
465 0.9             Added parser that detects stories that are not properly formatted for meta-tags and reformats them so that they are.
466                 Additionally, it will not update files that have improper line endings ( ie non-UNIX endings).
467  
468 0.8             Fixed typo that caused index to be rebuilt every time...  :)
469
470 0.7             Major revisions - fixed the "Year 1900" bug and an issue with statically generated blogs misbehaving
471
472 0.61    Fixed bug reading old styled cache files
473
474 0.6             Added feature to automatically create meta-tags from indexed
475                                 time/date
476                                 
477 0.52    Fixed a bug where a new index might not be written
478
479 0.51    Added dd/mm/yy(yy) and mm/dd/yy(yy) date formatting
480
481 0.5             Complete rewrite - add support for %others, meta- tags, added backwards compatibility to using the .entries_index.index file from rael's plugin
482
483 0.2             Removed reliance on Data::Dumper, making it suitable for use on Earthlink
484
485 =head1 AUTHOR
486
487 Fletcher T. Penney
488 Modified by Steve Schwarz
489 based on original code by:
490 Rael Dornfest  <rael@oreilly.com>, http://www.raelity.org/
491
492 This plugin is now maintained by the Blosxom Sourceforge Team,
493 <blosxom-devel@lists.sourceforge.net>.
494
495 =head1 SEE ALSO
496
497 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
498
499 Blosxom Plugin Docs: http://blosxom.sourceforge.net/documentation/users/plugins.html
500
501 =head1 BUGS
502
503 None known; please send bug reports and feedback to the Blosxom
504 development mailing list <blosxom-devel@lists.sourceforge.net>.
505
506 =head1 LICENSE
507
508 entries_cache plugin
509 Copyright 2003, Fletcher Penney
510 except for portions copied from entries_index and entries_index_tagged
511
512 Blosxom and original entries_index plugin
513 Copyright 2003, Rael Dornfest 
514
515 Permission is hereby granted, free of charge, to any person obtaining a
516 copy of this software and associated documentation files (the "Software"),
517 to deal in the Software without restriction, including without limitation
518 the rights to use, copy, modify, merge, publish, distribute, sublicense,
519 and/or sell copies of the Software, and to permit persons to whom the
520 Software is furnished to do so, subject to the following conditions:
521
522 The above copyright notice and this permission notice shall be included
523 in all copies or substantial portions of the Software.
524
525 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
526 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
527 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
528 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
529 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
530 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
531 OTHER DEALINGS IN THE SOFTWARE.