Revert rss20 to using $ENV{PATH_INFO} in self link.
[matthijs/upstream/blosxom-plugins.git] / gavinc / entries_timestamp
1 # Blosxom Plugin: entries_timestamp
2 # Author(s): Gavin Carr <gavin@openfusion.com.au>
3 # Version: 0.002000
4 # Documentation: See the bottom of this file or type: perldoc entries_timestamp
5
6 package entries_timestamp;
7
8 use strict;
9 use File::stat;
10 use File::Find;
11 use Data::Dumper;
12 use Time::Local;
13 use CGI ();
14
15 #use Blosxom::Debug debug_level => 2;
16
17 # --- Configurable variables -----
18
19 # Where should I store the entries_timestamp index file?
20 # IMO timestamps are metadata rather than state, but you may well not care.
21 my $meta_dir = "$blosxom::datadir/../meta";
22 #my $meta_dir = $blosxom::plugin_state_dir;
23
24 # What name should my entries_timestamp index file be called?
25 # If you want to migrate from entries_index, you can just use the original
26 # entries_index .entries_index.index file, or or just copy/rename it.
27 my $entries_index = 'entries_timestamp.index';
28 #my $entries_index = '.entries_index.index';
29
30 # Reindexing password. If entries_timestamp finds a '?reindex=$reindex_password'
31 # parameter it will check and resync machine timestamps to the human versions
32 my $reindex_password = 'abracad';    # CHANGEME!
33
34 # --------------------------------
35 # __END_CONFIG__
36
37 my $q = CGI->new;
38
39 use vars qw($TS_MACHINE $TS_HUMAN $SYMLINKS $VAR1 $VAR2 $VAR3);
40
41 sub start { 1 }
42
43 sub entries {
44   return sub {
45     my(%indexes, %files_ts, %files_ts_str, %files_symlinks);
46
47     # Read $entries_index
48     if ( open ENTRIES, "$meta_dir/$entries_index" ) {
49       my $index = join '', <ENTRIES>;
50       close ENTRIES;
51       if ( $index =~ m/\$(TS_\w+|VAR1) = \{/ ) {
52         eval $index;
53         if ( $@ ) {
54           warn "(entries_timestamp) eval of $entries_index failed: $@";
55           return;
56         }
57         else {
58           if ($TS_MACHINE && keys %$TS_MACHINE) {
59             %files_ts = %$TS_MACHINE;
60           } elsif ($VAR1 && keys %$VAR1) {
61             %files_ts = %$VAR1;
62           }
63           if ($TS_HUMAN && keys %$TS_HUMAN) {
64             %files_ts_str = %$TS_HUMAN;
65           } elsif ($VAR2 && keys %$VAR2) {
66             %files_ts_str = %$VAR2;
67           }
68           if ($SYMLINKS && keys %$SYMLINKS) {
69             %files_symlinks = %$SYMLINKS;
70           } elsif ($VAR3 && keys %$VAR3) {
71             %files_symlinks = %$VAR3;
72           }
73         }
74       } 
75     }
76     %files_ts_str = () unless defined %files_ts_str;
77     %files_symlinks = () unless defined %files_symlinks;
78
79     my $index_mods = 0;
80
81     # Check for deleted files
82     for my $file (keys %files_ts) { 
83       if ( ! -f $file || ( -l $file && ! -f readlink($file)) ) {
84         $index_mods++; 
85         delete $files_ts{$file};
86         delete $files_ts_str{$file};
87         delete $files_symlinks{$file};
88         # debug(2, "deleting removed file '$file' from indexes");
89       } 
90     }
91
92     # Check for new files
93     find(
94       sub {
95         my $d; 
96         my $curr_depth = $File::Find::dir =~ tr[/][]; 
97         if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
98           delete $files_ts{$File::Find::name};
99           delete $files_ts_str{$File::Find::name};
100           delete $files_symlinks{$File::Find::name};
101           return;
102         }
103      
104         # Return unless a match
105         return unless $File::Find::name =~ 
106           m! ^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$ !x;
107         my $path = $1;
108         my $filename = $2;
109         # Return if an index, a dotfile, or unreadable
110         if ( $filename eq 'index' or $filename =~ /^\./ or ! -r $File::Find::name ) {
111           # debug(1, "(entries_timetamp) '$path/$filename.$blosxom::file_extension' is an index, a dotfile, or is unreadable - skipping\n");
112           return;
113         }
114
115         # Get modification time
116         my $mtime = stat($File::Find::name)->mtime or return;
117
118         # Ignore if future unless $show_future_entries is set
119         return unless $blosxom::show_future_entries or $mtime <= time;
120
121         my @nice_date = blosxom::nice_date( $mtime );
122
123         # If a new symlink, add to %files_symlinks
124         if ( -l $File::Find::name ) {
125           if ( ! exists $files_symlinks{ $File::Find::name } ) {
126             $files_symlinks{$File::Find::name} = 1;
127             $index_mods++;
128             # Backwards compatibility deletes
129             delete $files_ts{$File::Find::name};
130             delete $files_ts_str{$File::Find::name};
131             # debug(2, "new file_symlinks entry $File::Find::name, index_mods now $index_mods");
132           }
133         }
134
135         # If a new file, add to %files_ts and %files_ts_str
136         else {
137           if ( ! exists $files_ts{$File::Find::name} ) {
138             $files_ts{$File::Find::name} = $mtime;
139             $index_mods++;
140             # debug(2, "new file entry $File::Find::name, index_mods now $index_mods");
141           }
142           if ( ! exists $files_ts_str{$File::Find::name} ) {
143             my $date = join('-', @nice_date[5,2,3]);
144             my $time = sprintf '%s:%02d', $nice_date[4], (localtime($mtime))[0];
145             $files_ts_str{$File::Find::name} = join(' ', $date, $time, $nice_date[6]);
146             $index_mods++;
147             # debug(2, "new file_ts_str entry $File::Find::name, index_mods now $index_mods");
148           }
149          
150           # If asked to reindex, check and sync machine timestamps to the human ones
151           if ( my $reindex = $q->param('reindex') ) {
152             if ( $reindex eq $reindex_password ) {
153               if ( my $reindex_ts = parse_ts( $files_ts_str{$File::Find::name} )) {
154                 if ($reindex_ts != $files_ts{$File::Find::name}) {
155                   # debug(1, "reindex: updating timestamp on '$File::Find::name'\n");
156                   # debug(2, "reindex_ts $reindex_ts, files_ts $files_ts{$File::Find::name}");
157                   $files_ts{$File::Find::name} = $reindex_ts;
158                   $index_mods++;
159                 }
160               }
161               else {
162                 warn "(entries_timestamp) Warning: bad timestamp '$files_ts_str{$File::Find::name}' on file '$File::Find::name' - failed to parse (not %Y-%m-%d %T %z?)\n";
163               }
164             }
165             else {
166               warn "(entries_timestamp) Warning: reindex requested with incorrect password\n";
167             }
168           }
169         }
170
171         # Static rendering
172         if ($blosxom::static_entries) {
173           my $static_file = "$blosxom::static_dir/$path/index.$blosxom::static_flavours[0]";
174           if ( $q->param('-all') 
175                or ! -f $static_file
176                or stat($static_file)->mtime < $mtime ) {
177             # debug(3, "static_file: $static_file");
178             $indexes{$path} = 1;
179             $d = join('/', @nice_date[5,2,3]);
180             $indexes{$d} = $d;
181             $path = $path ? "$path/" : '';
182             $indexes{ "$path$filename.$blosxom::file_extension" } = 1;
183           }
184         }
185       }, $blosxom::datadir
186     );
187
188     # If updates, save back to index
189     if ( $index_mods ) {
190       # debug(1, "index_mods $index_mods, saving \%files to $meta_dir/$entries_index");
191       if ( open ENTRIES, "> $meta_dir/$entries_index" ) {
192         print ENTRIES Data::Dumper->Dump([ \%files_ts_str, \%files_ts, \%files_symlinks ],
193           [ qw(TS_HUMAN TS_MACHINE SYMLINKS) ] );
194         close ENTRIES;
195       } 
196       else {
197         warn "(entries_timestamp) couldn't open $meta_dir/$entries_index for writing: $!\n";
198       }
199     }
200
201     # Generate blosxom %files from %files_ts and %files_symlinks
202     my %files = %files_ts;
203     for (keys %files_symlinks) {
204       # Add to %files with mtime of referenced file
205       my $target = readlink $_;
206       # Note that we only support symlinks pointing to other posts
207       $files{ $_ } = $files{ $target } if exists $files{ $target };
208     }
209
210     return (\%files, \%indexes);
211   };
212 }
213
214 # Helper function to parse human-friendly %Y-%m-%d %T %z timestamps
215 sub parse_ts {
216   my ($ts_str) = @_;
217
218   if ($ts_str =~ m/^(\d{4})-(\d{2})-(\d{2})           # %Y-%m-%d
219                     \s+ (\d{2}):(\d{2})(?::(\d{2}))?  # %H-%M-%S
220                     (?:\s+ [+-]?(\d{2})(\d{2})?)?     # %z
221                   /x) {
222     my ($yy, $mm, $dd, $hh, $mi, $ss, $zh, $zm) = ($1, $2, $3, $4, $5, $6, $7, $8);
223     $mm--;
224     # FIXME: just use localtime for now
225     if (my $mtime = timelocal($ss, $mi, $hh, $dd, $mm, $yy)) {
226       return $mtime;
227     }
228   }
229
230   return 0;
231 }
232
233 1;
234
235 __END__
236
237 =head1 NAME
238
239 entries_timestamp: blosxom plugin to capture and preserve the original
240 creation timestamp on blosxom posts
241
242 =head1 SYNOPSIS
243
244 entries_timestamp is a blosxom plugin for capturing and preserving the
245 original creation timestamp on blosxom posts. It is based on Rael
246 Dornfest's original L<entries_index> plugin, and works in the same way:
247 it maintains an index file (configurable, but 'entries_timestamp.index',
248 by default) maintaining creation timestamps for all blosxom posts, and
249 replaces the default $blosxom::entries subrouting with one returning a 
250 file hash using that index.
251
252 It differs from Rael's L<entries_index> as follows:
253
254 =over 4
255
256 =item User-friendly timestamps
257
258 The index file contains two timestamps for every file - the 
259 machine-friendly system L<time/2> version, for use by blosxom, and a
260 human-friendly stringified timestamp, to allow timestamps to be reviewed
261 and or modified easily.
262
263 entries_timestamp ordinarily just assumes those timestamps are in sync,
264 and ignores the string version. If you update the string version and want
265 that to override the system time, you should pass a 
266 ?reindex=<reindex_password> argument to blosxom to force the system
267 timestamps to be checked and updated.
268
269 =item Separate symlink handling
270
271 entries_timestamp uses separate indexes for posts that are files and
272 posts that are symlinks, and doesn't bother to cache timestamps for
273 the latter at all, deriving them instead from the post they point to.
274 (Note that this means entries_timestamp currently doesn't support 
275 symlinks to non-post files at all - they're just ignored).
276
277 =item Configurable index file name and location
278
279 I consider post timestamps to be metadata rather than state, so I 
280 tend to use a separate C<meta> directory alongside by posts for this,
281 rather than the traditional $plugin_state_dir. You may note care. ;-)
282
283 =item A complete rewrite
284
285 Completely rewritten code, since the original used evil evil and-chains 
286 and was pretty difficult to understand (imho).
287
288 =back
289
290 =head1 SEE ALSO
291
292 L<entries_index>, L<entries_cache>, L<entries_cache_meta>
293
294 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
295
296 =head1 ACKNOWLEDGEMENTS
297
298 This plugin is largely based on Rael Dornfest's original 
299 L<entries_index> plugin.
300
301 =head1 BUGS AND LIMITATIONS
302
303 entries_timestamp currently only supports symlinks to local post files,
304 not symlinks to arbitrary files outside your $datadir.
305
306 entries_timestamp doesn't currently do any kind caching, so it's not
307 directly equivalent to L<entries_cache> or L<entries_cache_meta>.
308
309 Please report bugs either directly to the author or to the blosxom 
310 development mailing list: <blosxom-devel@lists.sourceforge.net>.
311
312 =head1 AUTHOR
313
314 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
315
316 =head1 LICENSE
317
318 Copyright 2007, Gavin Carr.
319
320 This plugin is licensed under the same terms as blosxom itself i.e.
321
322 Permission is hereby granted, free of charge, to any person obtaining a
323 copy of this software and associated documentation files (the "Software"),
324 to deal in the Software without restriction, including without limitation
325 the rights to use, copy, modify, merge, publish, distribute, sublicense,
326 and/or sell copies of the Software, and to permit persons to whom the
327 Software is furnished to do so, subject to the following conditions:
328
329 The above copyright notice and this permission notice shall be included
330 in all copies or substantial portions of the Software.
331
332 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
333 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
334 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
335 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
336 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
337 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
338 OTHER DEALINGS IN THE SOFTWARE.
339
340 =cut
341
342 # vim:ft=perl