# Blosxom Plugin: entries_timestamp # Author(s): Gavin Carr # Version: 0.002000 # 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 (); # Uncomment next line to enable debug output (don't uncomment debug() lines) #use Blosxom::Debug debug_level => 2; # --- Configurable variables ----- my %config = (); # Where should I store the entries_timestamp index file? # IMO timestamps are metadata rather than state, but you may well not care. $config{meta_dir} = "$blosxom::datadir/../meta"; #$config{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. $config{entries_index} = 'entries_timestamp.index'; #$config{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 $config{reindex_password} = 'abracad'; # CHANGEME! # -------------------------------- # __END_CONFIG__ my $q = CGI->new; use vars qw($TS_MACHINE $TS_HUMAN $SYMLINKS $VAR1 $VAR2 $VAR3); sub start { 1 } sub entries { return sub { my(%indexes, %files_ts, %files_ts_str, %files_symlinks); # Read $config{entries_index} if ( open ENTRIES, "$config{meta_dir}/$config{entries_index}" ) { my $index = join '', ; close ENTRIES; if ( $index =~ m/\$(TS_\w+|VAR1) = \{/ ) { eval $index; if ( $@ ) { warn "(entries_timestamp) eval of $config{entries_index} failed: $@"; return; } else { if ($TS_MACHINE && keys %$TS_MACHINE) { %files_ts = %$TS_MACHINE; } elsif ($VAR1 && keys %$VAR1) { %files_ts = %$VAR1; } if ($TS_HUMAN && keys %$TS_HUMAN) { %files_ts_str = %$TS_HUMAN; } elsif ($VAR2 && keys %$VAR2) { %files_ts_str = %$VAR2; } if ($SYMLINKS && keys %$SYMLINKS) { %files_symlinks = %$SYMLINKS; } elsif ($VAR3 && keys %$VAR3) { %files_symlinks = %$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 $config{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 $config{meta_dir}/$config{entries_index}"); if ( open ENTRIES, "> $config{meta_dir}/$config{entries_index}" ) { print ENTRIES Data::Dumper->Dump([ \%files_ts_str, \%files_ts, \%files_symlinks ], [ qw(TS_HUMAN TS_MACHINE SYMLINKS) ] ); close ENTRIES; } else { warn "(entries_timestamp) couldn't open $config{meta_dir}/$config{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