From b43d3408229e74112b85eac8ec1bf256daaf32fc Mon Sep 17 00:00:00 2001 From: Gavin Carr Date: Mon, 29 Oct 2007 09:37:22 +0000 Subject: [PATCH] Add entries_timestamp. --- gavinc/entries_timestamp | 328 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 gavinc/entries_timestamp diff --git a/gavinc/entries_timestamp b/gavinc/entries_timestamp new file mode 100644 index 0000000..e32f0c5 --- /dev/null +++ b/gavinc/entries_timestamp @@ -0,0 +1,328 @@ +# Blosxom Plugin: entries_timestamp +# Author(s): Gavin Carr +# Version: 0.001000 +# Documentation: See the bottom of this file or type: perldoc entries_timestamp + +package entries_timestamp; + +use strict; +use File::stat; +use File::Find; +use Data::Dumper; +use Time::Local; +use CGI (); + +#use Blosxom::Debug debug_level => 1; + +# --- Configurable variables ----- + +# Where should I store the entries_timestamp index file? +# IMO timestamps are metadata rather than state, but you may well not care. +my $meta_dir = "$blosxom::datadir/../meta"; +#my $meta_dir = $blosxom::plugin_state_dir; + +# What name should my entries_timestamp index file be called? +# If you want to migrate from entries_index, you can just use the original +# entries_index .entries_index.index file, or or just copy/rename it. +my $entries_index = 'entries_timestamp.index'; +#my $entries_index = '.entries_index.index'; + +# Reindexing password. If entries_timestamp finds a '?reindex=$reindex_password' +# parameter it will check and resync machine timestamps to the human versions +my $reindex_password = 'abracad'; # CHANGEME! + +# -------------------------------- + +my $q = CGI->new; + +use vars qw($VAR1 $VAR2 $VAR3); + +sub start { 1 } + +sub entries { + return sub { + my(%indexes, %files_ts, %files_ts_str, %files_symlinks); + + # Read $entries_index + if ( open ENTRIES, "$meta_dir/$entries_index" ) { + my $index = join '', ; + close ENTRIES; + if ( $index =~ m/\$VAR1 = \{/ ) { + eval $index; + if ( $@ ) { + warn "(entries_timestamp) eval of $entries_index failed: $@"; + return; + } + else { + %files_ts = %$VAR1; + %files_ts_str = %$VAR2 if keys %$VAR2; + %files_symlinks = %$VAR3 if keys %$VAR3; + } + } + } + %files_ts_str = () unless defined %files_ts_str; + %files_symlinks = () unless defined %files_symlinks; + + my $index_mods = 0; + + # Check for deleted files + for my $file (keys %files_ts) { + if ( ! -f $file || ( -l $file && ! -f readlink($file)) ) { + $index_mods++; + delete $files_ts{$file}; + delete $files_ts_str{$file}; + delete $files_symlinks{$file}; + # debug(2, "deleting removed file '$file' from indexes"); + } + } + + # Check for new files + find( + sub { + my $d; + my $curr_depth = $File::Find::dir =~ tr[/][]; + if ( $blosxom::depth and $curr_depth > $blosxom::depth ) { + delete $files_ts{$File::Find::name}; + delete $files_ts_str{$File::Find::name}; + delete $files_symlinks{$File::Find::name}; + return; + } + + # Return unless a match + return unless $File::Find::name =~ + m! ^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$ !x; + my $path = $1; + my $filename = $2; + # Return if an index, a dotfile, or unreadable + if ( $filename eq 'index' or $filename =~ /^\./ or ! -r $File::Find::name ) { + # debug(1, "(entries_timetamp) '$path/$filename.$blosxom::file_extension' is an index, a dotfile, or is unreadable - skipping\n"); + return; + } + + # Get modification time + my $mtime = stat($File::Find::name)->mtime or return; + + # Ignore if future unless $show_future_entries is set + return unless $blosxom::show_future_entries or $mtime <= time; + + my @nice_date = blosxom::nice_date( $mtime ); + + # If a new symlink, add to %files_symlinks + if ( -l $File::Find::name ) { + if ( ! exists $files_symlinks{ $File::Find::name } ) { + $files_symlinks{$File::Find::name} = 1; + $index_mods++; + # Backwards compatibility deletes + delete $files_ts{$File::Find::name}; + delete $files_ts_str{$File::Find::name}; + # debug(2, "new file_symlinks entry $File::Find::name, index_mods now $index_mods"); + } + } + + # If a new file, add to %files_ts and %files_ts_str + else { + if ( ! exists $files_ts{$File::Find::name} ) { + $files_ts{$File::Find::name} = $mtime; + $index_mods++; + # debug(2, "new file entry $File::Find::name, index_mods now $index_mods"); + } + if ( ! exists $files_ts_str{$File::Find::name} ) { + my $date = join('-', @nice_date[5,2,3]); + my $time = sprintf '%s:%02d', $nice_date[4], (localtime($mtime))[0]; + $files_ts_str{$File::Find::name} = join(' ', $date, $time, $nice_date[6]); + $index_mods++; + # debug(2, "new file_ts_str entry $File::Find::name, index_mods now $index_mods"); + } + + # If asked to reindex, check and sync machine timestamps to the human ones + if ( my $reindex = $q->param('reindex') ) { + if ( $reindex eq $reindex_password ) { + if ( my $reindex_ts = parse_ts( $files_ts_str{$File::Find::name} )) { + if ($reindex_ts != $files_ts{$File::Find::name}) { + # debug(1, "reindex: updating timestamp on '$File::Find::name'\n"); + # debug(2, "reindex_ts $reindex_ts, files_ts $files_ts{$File::Find::name}"); + $files_ts{$File::Find::name} = $reindex_ts; + $index_mods++; + } + } + else { + 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"; + } + } + else { + warn "(entries_timestamp) Warning: reindex requested with incorrect password\n"; + } + } + } + + # Static rendering + if ($blosxom::static_entries) { + my $static_file = "$blosxom::static_dir/$path/index.$blosxom::static_flavours[0]"; + if ( $q->param('-all') + or ! -f $static_file + or stat($static_file)->mtime < $mtime ) { + # debug(3, "static_file: $static_file"); + $indexes{$path} = 1; + $d = join('/', @nice_date[5,2,3]); + $indexes{$d} = $d; + $path = $path ? "$path/" : ''; + $indexes{ "$path$filename.$blosxom::file_extension" } = 1; + } + } + }, $blosxom::datadir + ); + + # If updates, save back to index + if ( $index_mods ) { + # debug(1, "index_mods $index_mods, saving \%files to $meta_dir/$entries_index"); + if ( open ENTRIES, "> $meta_dir/$entries_index" ) { + print ENTRIES Dumper \%files_ts, \%files_ts_str, \%files_symlinks; + close ENTRIES; + } + else { + warn "(entries_timestamp) couldn't open $meta_dir/$entries_index for writing: $!\n"; + } + } + + # Generate blosxom %files from %files_ts and %files_symlinks + my %files = %files_ts; + for (keys %files_symlinks) { + # Add to %files with mtime of referenced file + my $target = readlink $_; + # Note that we only support symlinks pointing to other posts + $files{ $_ } = $files{ $target } if exists $files{ $target }; + } + + return (\%files, \%indexes); + }; +} + +# Helper function to parse human-friendly %Y-%m-%d %T %z timestamps +sub parse_ts { + my ($ts_str) = @_; + + if ($ts_str =~ m/^(\d{4})-(\d{2})-(\d{2}) # %Y-%m-%d + \s+ (\d{2}):(\d{2})(?::(\d{2}))? # %H-%M-%S + (?:\s+ [+-]?(\d{2})(\d{2})?)? # %z + /x) { + my ($yy, $mm, $dd, $hh, $mi, $ss, $zh, $zm) = ($1, $2, $3, $4, $5, $6, $7, $8); + $mm--; + # FIXME: just use localtime for now + if (my $mtime = timelocal($ss, $mi, $hh, $dd, $mm, $yy)) { + return $mtime; + } + } + + return 0; +} + +1; + +__END__ + +=head1 NAME + +entries_timestamp: blosxom plugin to capture and preserve the original +creation timestamp on blosxom posts + +=head1 SYNOPSIS + +entries_timestamp is a blosxom plugin for capturing and preserving the +original creation timestamp on blosxom posts. It is based on Rael +Dornfest's original L plugin, and works in the same way: +it maintains an index file (configurable, but 'entries_timestamp.index', +by default) maintaining creation timestamps for all blosxom posts, and +replaces the default $blosxom::entries subrouting with one returning a +file hash using that index. + +It differs from Rael's L as follows: + +=over 4 + +=item User-friendly timestamps + +The index file contains two timestamps for every file - the +machine-friendly system L