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 # --------------------------------
39 use vars qw($TS_MACHINE $TS_HUMAN $SYMLINKS $VAR1 $VAR2 $VAR3);
45 my(%indexes, %files_ts, %files_ts_str, %files_symlinks);
48 if ( open ENTRIES, "$meta_dir/$entries_index" ) {
49 my $index = join '', <ENTRIES>;
51 if ( $index =~ m/\$(TS_\w+|VAR1) = \{/ ) {
54 warn "(entries_timestamp) eval of $entries_index failed: $@";
58 if ($TS_MACHINE && keys %$TS_MACHINE) {
59 %files_ts = %$TS_MACHINE;
60 } elsif ($VAR1 && keys %$VAR1) {
63 if ($TS_HUMAN && keys %$TS_HUMAN) {
64 %files_ts_str = %$TS_HUMAN;
65 } elsif ($VAR2 && keys %$VAR2) {
66 %files_ts_str = %$VAR2;
68 if ($SYMLINKS && keys %$SYMLINKS) {
69 %files_symlinks = %$SYMLINKS;
70 } elsif ($VAR3 && keys %$VAR3) {
71 %files_symlinks = %$VAR3;
76 %files_ts_str = () unless defined %files_ts_str;
77 %files_symlinks = () unless defined %files_symlinks;
81 # Check for deleted files
82 for my $file (keys %files_ts) {
83 if ( ! -f $file || ( -l $file && ! -f readlink($file)) ) {
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");
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};
104 # Return unless a match
105 return unless $File::Find::name =~
106 m! ^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$ !x;
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");
115 # Get modification time
116 my $mtime = stat($File::Find::name)->mtime or return;
118 # Ignore if future unless $show_future_entries is set
119 return unless $blosxom::show_future_entries or $mtime <= time;
121 my @nice_date = blosxom::nice_date( $mtime );
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;
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");
135 # If a new file, add to %files_ts and %files_ts_str
137 if ( ! exists $files_ts{$File::Find::name} ) {
138 $files_ts{$File::Find::name} = $mtime;
140 # debug(2, "new file entry $File::Find::name, index_mods now $index_mods");
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]);
147 # debug(2, "new file_ts_str entry $File::Find::name, index_mods now $index_mods");
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;
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";
166 warn "(entries_timestamp) Warning: reindex requested with incorrect password\n";
172 if ($blosxom::static_entries) {
173 my $static_file = "$blosxom::static_dir/$path/index.$blosxom::static_flavours[0]";
174 if ( $q->param('-all')
176 or stat($static_file)->mtime < $mtime ) {
177 # debug(3, "static_file: $static_file");
179 $d = join('/', @nice_date[5,2,3]);
181 $path = $path ? "$path/" : '';
182 $indexes{ "$path$filename.$blosxom::file_extension" } = 1;
188 # If updates, save back to index
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) ] );
197 warn "(entries_timestamp) couldn't open $meta_dir/$entries_index for writing: $!\n";
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 };
210 return (\%files, \%indexes);
214 # Helper function to parse human-friendly %Y-%m-%d %T %z timestamps
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
222 my ($yy, $mm, $dd, $hh, $mi, $ss, $zh, $zm) = ($1, $2, $3, $4, $5, $6, $7, $8);
224 # FIXME: just use localtime for now
225 if (my $mtime = timelocal($ss, $mi, $hh, $dd, $mm, $yy)) {
239 entries_timestamp: blosxom plugin to capture and preserve the original
240 creation timestamp on blosxom posts
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.
252 It differs from Rael's L<entries_index> as follows:
256 =item User-friendly timestamps
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.
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.
269 =item Separate symlink handling
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).
277 =item Configurable index file name and location
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. ;-)
283 =item A complete rewrite
285 Completely rewritten code, since the original used evil evil and-chains
286 and was pretty difficult to understand (imho).
292 L<entries_index>, L<entries_cache>, L<entries_cache_meta>
294 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
296 =head1 ACKNOWLEDGEMENTS
298 This plugin is largely based on Rael Dornfest's original
299 L<entries_index> plugin.
301 =head1 BUGS AND LIMITATIONS
303 entries_timestamp currently only supports symlinks to local post files,
304 not symlinks to arbitrary files outside your $datadir.
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>.
309 Please report bugs either directly to the author or to the blosxom
310 development mailing list: <blosxom-devel@lists.sourceforge.net>.
314 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
318 Copyright 2007, Gavin Carr.
320 This plugin is licensed under the same terms as blosxom itself i.e.
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:
329 The above copyright notice and this permission notice shall be included
330 in all copies or substantial portions of the Software.
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.