1 # Blosxom Plugin: entries_timestamp
2 # Author(s): Gavin Carr <gavin@openfusion.com.au>
4 # Documentation: See the bottom of this file or type: perldoc entries_timestamp
6 package entries_timestamp;
15 #use Blosxom::Debug debug_level => 2;
17 # --- Configurable variables -----
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;
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';
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!
34 # --------------------------------
38 use vars qw($TS_MACHINE $TS_HUMAN $SYMLINKS $VAR1 $VAR2 $VAR3);
44 my(%indexes, %files_ts, %files_ts_str, %files_symlinks);
47 if ( open ENTRIES, "$meta_dir/$entries_index" ) {
48 my $index = join '', <ENTRIES>;
50 if ( $index =~ m/\$(TS_\w+|VAR1) = \{/ ) {
53 warn "(entries_timestamp) eval of $entries_index failed: $@";
57 if ($TS_MACHINE && keys %$TS_MACHINE) {
58 %files_ts = %$TS_MACHINE;
59 } elsif ($VAR1 && keys %$VAR1) {
62 if ($TS_HUMAN && keys %$TS_HUMAN) {
63 %files_ts_str = %$TS_HUMAN;
64 } elsif ($VAR2 && keys %$VAR2) {
65 %files_ts_str = %$VAR2;
67 if ($SYMLINKS && keys %$SYMLINKS) {
68 %files_symlinks = %$SYMLINKS;
69 } elsif ($VAR3 && keys %$VAR3) {
70 %files_symlinks = %$VAR3;
75 %files_ts_str = () unless defined %files_ts_str;
76 %files_symlinks = () unless defined %files_symlinks;
80 # Check for deleted files
81 for my $file (keys %files_ts) {
82 if ( ! -f $file || ( -l $file && ! -f readlink($file)) ) {
84 delete $files_ts{$file};
85 delete $files_ts_str{$file};
86 delete $files_symlinks{$file};
87 # debug(2, "deleting removed file '$file' from indexes");
95 my $curr_depth = $File::Find::dir =~ tr[/][];
96 if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
97 delete $files_ts{$File::Find::name};
98 delete $files_ts_str{$File::Find::name};
99 delete $files_symlinks{$File::Find::name};
103 # Return unless a match
104 return unless $File::Find::name =~
105 m! ^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$ !x;
108 # Return if an index, a dotfile, or unreadable
109 if ( $filename eq 'index' or $filename =~ /^\./ or ! -r $File::Find::name ) {
110 # debug(1, "(entries_timetamp) '$path/$filename.$blosxom::file_extension' is an index, a dotfile, or is unreadable - skipping\n");
114 # Get modification time
115 my $mtime = stat($File::Find::name)->mtime or return;
117 # Ignore if future unless $show_future_entries is set
118 return unless $blosxom::show_future_entries or $mtime <= time;
120 my @nice_date = blosxom::nice_date( $mtime );
122 # If a new symlink, add to %files_symlinks
123 if ( -l $File::Find::name ) {
124 if ( ! exists $files_symlinks{ $File::Find::name } ) {
125 $files_symlinks{$File::Find::name} = 1;
127 # Backwards compatibility deletes
128 delete $files_ts{$File::Find::name};
129 delete $files_ts_str{$File::Find::name};
130 # debug(2, "new file_symlinks entry $File::Find::name, index_mods now $index_mods");
134 # If a new file, add to %files_ts and %files_ts_str
136 if ( ! exists $files_ts{$File::Find::name} ) {
137 $files_ts{$File::Find::name} = $mtime;
139 # debug(2, "new file entry $File::Find::name, index_mods now $index_mods");
141 if ( ! exists $files_ts_str{$File::Find::name} ) {
142 my $date = join('-', @nice_date[5,2,3]);
143 my $time = sprintf '%s:%02d', $nice_date[4], (localtime($mtime))[0];
144 $files_ts_str{$File::Find::name} = join(' ', $date, $time, $nice_date[6]);
146 # debug(2, "new file_ts_str entry $File::Find::name, index_mods now $index_mods");
149 # If asked to reindex, check and sync machine timestamps to the human ones
150 if ( my $reindex = $q->param('reindex') ) {
151 if ( $reindex eq $reindex_password ) {
152 if ( my $reindex_ts = parse_ts( $files_ts_str{$File::Find::name} )) {
153 if ($reindex_ts != $files_ts{$File::Find::name}) {
154 # debug(1, "reindex: updating timestamp on '$File::Find::name'\n");
155 # debug(2, "reindex_ts $reindex_ts, files_ts $files_ts{$File::Find::name}");
156 $files_ts{$File::Find::name} = $reindex_ts;
161 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";
165 warn "(entries_timestamp) Warning: reindex requested with incorrect password\n";
171 if ($blosxom::static_entries) {
172 my $static_file = "$blosxom::static_dir/$path/index.$blosxom::static_flavours[0]";
173 if ( $q->param('-all')
175 or stat($static_file)->mtime < $mtime ) {
176 # debug(3, "static_file: $static_file");
178 $d = join('/', @nice_date[5,2,3]);
180 $path = $path ? "$path/" : '';
181 $indexes{ "$path$filename.$blosxom::file_extension" } = 1;
187 # If updates, save back to index
189 # debug(1, "index_mods $index_mods, saving \%files to $meta_dir/$entries_index");
190 if ( open ENTRIES, "> $meta_dir/$entries_index" ) {
191 print ENTRIES Data::Dumper->Dump([ \%files_ts_str, \%files_ts, \%files_symlinks ],
192 [ qw(TS_HUMAN TS_MACHINE SYMLINKS) ] );
196 warn "(entries_timestamp) couldn't open $meta_dir/$entries_index for writing: $!\n";
200 # Generate blosxom %files from %files_ts and %files_symlinks
201 my %files = %files_ts;
202 for (keys %files_symlinks) {
203 # Add to %files with mtime of referenced file
204 my $target = readlink $_;
205 # Note that we only support symlinks pointing to other posts
206 $files{ $_ } = $files{ $target } if exists $files{ $target };
209 return (\%files, \%indexes);
213 # Helper function to parse human-friendly %Y-%m-%d %T %z timestamps
217 if ($ts_str =~ m/^(\d{4})-(\d{2})-(\d{2}) # %Y-%m-%d
218 \s+ (\d{2}):(\d{2})(?::(\d{2}))? # %H-%M-%S
219 (?:\s+ [+-]?(\d{2})(\d{2})?)? # %z
221 my ($yy, $mm, $dd, $hh, $mi, $ss, $zh, $zm) = ($1, $2, $3, $4, $5, $6, $7, $8);
223 # FIXME: just use localtime for now
224 if (my $mtime = timelocal($ss, $mi, $hh, $dd, $mm, $yy)) {
238 entries_timestamp: blosxom plugin to capture and preserve the original
239 creation timestamp on blosxom posts
243 entries_timestamp is a blosxom plugin for capturing and preserving the
244 original creation timestamp on blosxom posts. It is based on Rael
245 Dornfest's original L<entries_index> plugin, and works in the same way:
246 it maintains an index file (configurable, but 'entries_timestamp.index',
247 by default) maintaining creation timestamps for all blosxom posts, and
248 replaces the default $blosxom::entries subrouting with one returning a
249 file hash using that index.
251 It differs from Rael's L<entries_index> as follows:
255 =item User-friendly timestamps
257 The index file contains two timestamps for every file - the
258 machine-friendly system L<time/2> version, for use by blosxom, and a
259 human-friendly stringified timestamp, to allow timestamps to be reviewed
260 and or modified easily.
262 entries_timestamp ordinarily just assumes those timestamps are in sync,
263 and ignores the string version. If you update the string version and want
264 that to override the system time, you should pass a
265 ?reindex=<reindex_password> argument to blosxom to force the system
266 timestamps to be checked and updated.
268 =item Separate symlink handling
270 entries_timestamp uses separate indexes for posts that are files and
271 posts that are symlinks, and doesn't bother to cache timestamps for
272 the latter at all, deriving them instead from the post they point to.
273 (Note that this means entries_timestamp currently doesn't support
274 symlinks to non-post files at all - they're just ignored).
276 =item Configurable index file name and location
278 I consider post timestamps to be metadata rather than state, so I
279 tend to use a separate C<meta> directory alongside by posts for this,
280 rather than the traditional $plugin_state_dir. You may note care. ;-)
282 =item A complete rewrite
284 Completely rewritten code, since the original used evil evil and-chains
285 and was pretty difficult to understand (imho).
291 L<entries_index>, L<entries_cache>, L<entries_cache_meta>
293 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
295 =head1 ACKNOWLEDGEMENTS
297 This plugin is largely based on Rael Dornfest's original
298 L<entries_index> plugin.
300 =head1 BUGS AND LIMITATIONS
302 entries_timestamp currently only supports symlinks to local post files,
303 not symlinks to arbitrary files outside your $datadir.
305 entries_timestamp doesn't currently do any kind caching, so it's not
306 directly equivalent to L<entries_cache> or L<entries_cache_meta>.
308 Please report bugs either directly to the author or to the blosxom
309 development mailing list: <blosxom-devel@lists.sourceforge.net>.
313 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
317 Copyright 2007, Gavin Carr.
319 This plugin is licensed under the same terms as blosxom itself i.e.
321 Permission is hereby granted, free of charge, to any person obtaining a
322 copy of this software and associated documentation files (the "Software"),
323 to deal in the Software without restriction, including without limitation
324 the rights to use, copy, modify, merge, publish, distribute, sublicense,
325 and/or sell copies of the Software, and to permit persons to whom the
326 Software is furnished to do so, subject to the following conditions:
328 The above copyright notice and this permission notice shall be included
329 in all copies or substantial portions of the Software.
331 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
332 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
333 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
334 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
335 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
336 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
337 OTHER DEALINGS IN THE SOFTWARE.