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 # Uncomment next line to enable debug output (don't uncomment debug() lines)
16 #use Blosxom::Debug debug_level => 2;
18 # --- Configurable variables -----
22 # Where should I store the entries_timestamp index file?
23 # IMO timestamps are metadata rather than state, but you may well not care.
24 $config{meta_dir} = "$blosxom::datadir/../meta";
25 #$config{meta_dir} = $blosxom::plugin_state_dir;
27 # What name should my entries_timestamp index file be called?
28 # If you want to migrate from entries_index, you can just use the original
29 # entries_index .entries_index.index file, or or just copy/rename it.
30 $config{entries_index} = 'entries_timestamp.index';
31 #$config{entries_index} = '.entries_index.index';
33 # Reindexing password. If entries_timestamp finds a '?reindex=$reindex_password'
34 # parameter it will check and resync machine timestamps to the human versions
35 $config{reindex_password} = 'abracad'; # CHANGEME!
37 # --------------------------------
42 use vars qw($TS_MACHINE $TS_HUMAN $SYMLINKS $VAR1 $VAR2 $VAR3);
48 my(%indexes, %files_ts, %files_ts_str, %files_symlinks);
50 # Read $config{entries_index}
51 if ( open ENTRIES, "$config{meta_dir}/$config{entries_index}" ) {
52 my $index = join '', <ENTRIES>;
54 if ( $index =~ m/\$(TS_\w+|VAR1) = \{/ ) {
57 warn "(entries_timestamp) eval of $config{entries_index} failed: $@";
61 if ($TS_MACHINE && keys %$TS_MACHINE) {
62 %files_ts = %$TS_MACHINE;
63 } elsif ($VAR1 && keys %$VAR1) {
66 if ($TS_HUMAN && keys %$TS_HUMAN) {
67 %files_ts_str = %$TS_HUMAN;
68 } elsif ($VAR2 && keys %$VAR2) {
69 %files_ts_str = %$VAR2;
71 if ($SYMLINKS && keys %$SYMLINKS) {
72 %files_symlinks = %$SYMLINKS;
73 } elsif ($VAR3 && keys %$VAR3) {
74 %files_symlinks = %$VAR3;
79 %files_ts_str = () unless defined %files_ts_str;
80 %files_symlinks = () unless defined %files_symlinks;
84 # Check for deleted files
85 for my $file (keys %files_ts) {
86 if ( ! -f $file || ( -l $file && ! -f readlink($file)) ) {
88 delete $files_ts{$file};
89 delete $files_ts_str{$file};
90 delete $files_symlinks{$file};
91 # debug(2, "deleting removed file '$file' from indexes");
99 my $curr_depth = $File::Find::dir =~ tr[/][];
100 if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
101 delete $files_ts{$File::Find::name};
102 delete $files_ts_str{$File::Find::name};
103 delete $files_symlinks{$File::Find::name};
107 # Return unless a match
108 return unless $File::Find::name =~
109 m! ^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$ !x;
112 # Return if an index, a dotfile, or unreadable
113 if ( $filename eq 'index' or $filename =~ /^\./ or ! -r $File::Find::name ) {
114 # debug(1, "(entries_timetamp) '$path/$filename.$blosxom::file_extension' is an index, a dotfile, or is unreadable - skipping\n");
118 # Get modification time
119 my $mtime = stat($File::Find::name)->mtime or return;
121 # Ignore if future unless $show_future_entries is set
122 return unless $blosxom::show_future_entries or $mtime <= time;
124 my @nice_date = blosxom::nice_date( $mtime );
126 # If a new symlink, add to %files_symlinks
127 if ( -l $File::Find::name ) {
128 if ( ! exists $files_symlinks{ $File::Find::name } ) {
129 $files_symlinks{$File::Find::name} = 1;
131 # Backwards compatibility deletes
132 delete $files_ts{$File::Find::name};
133 delete $files_ts_str{$File::Find::name};
134 # debug(2, "new file_symlinks entry $File::Find::name, index_mods now $index_mods");
138 # If a new file, add to %files_ts and %files_ts_str
140 if ( ! exists $files_ts{$File::Find::name} ) {
141 $files_ts{$File::Find::name} = $mtime;
143 # debug(2, "new file entry $File::Find::name, index_mods now $index_mods");
145 if ( ! exists $files_ts_str{$File::Find::name} ) {
146 my $date = join('-', @nice_date[5,2,3]);
147 my $time = sprintf '%s:%02d', $nice_date[4], (localtime($mtime))[0];
148 $files_ts_str{$File::Find::name} = join(' ', $date, $time, $nice_date[6]);
150 # debug(2, "new file_ts_str entry $File::Find::name, index_mods now $index_mods");
153 # If asked to reindex, check and sync machine timestamps to the human ones
154 if ( my $reindex = $q->param('reindex') ) {
155 if ( $reindex eq $config{reindex_password} ) {
156 if ( my $reindex_ts = parse_ts( $files_ts_str{$File::Find::name} )) {
157 if ($reindex_ts != $files_ts{$File::Find::name}) {
158 # debug(1, "reindex: updating timestamp on '$File::Find::name'\n");
159 # debug(2, "reindex_ts $reindex_ts, files_ts $files_ts{$File::Find::name}");
160 $files_ts{$File::Find::name} = $reindex_ts;
165 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";
169 warn "(entries_timestamp) Warning: reindex requested with incorrect password\n";
175 if ($blosxom::static_entries) {
176 my $static_file = "$blosxom::static_dir/$path/index.$blosxom::static_flavours[0]";
177 if ( $q->param('-all')
179 or stat($static_file)->mtime < $mtime ) {
180 # debug(3, "static_file: $static_file");
182 $d = join('/', @nice_date[5,2,3]);
184 $path = $path ? "$path/" : '';
185 $indexes{ "$path$filename.$blosxom::file_extension" } = 1;
191 # If updates, save back to index
193 # debug(1, "index_mods $index_mods, saving \%files to $config{meta_dir}/$config{entries_index}");
194 if ( open ENTRIES, "> $config{meta_dir}/$config{entries_index}" ) {
195 print ENTRIES Data::Dumper->Dump([ \%files_ts_str, \%files_ts, \%files_symlinks ],
196 [ qw(TS_HUMAN TS_MACHINE SYMLINKS) ] );
200 warn "(entries_timestamp) couldn't open $config{meta_dir}/$config{entries_index} for writing: $!\n";
204 # Generate blosxom %files from %files_ts and %files_symlinks
205 my %files = %files_ts;
206 for (keys %files_symlinks) {
207 # Add to %files with mtime of referenced file
208 my $target = readlink $_;
209 # Note that we only support symlinks pointing to other posts
210 $files{ $_ } = $files{ $target } if exists $files{ $target };
213 return (\%files, \%indexes);
217 # Helper function to parse human-friendly %Y-%m-%d %T %z timestamps
221 if ($ts_str =~ m/^(\d{4})-(\d{2})-(\d{2}) # %Y-%m-%d
222 \s+ (\d{2}):(\d{2})(?::(\d{2}))? # %H-%M-%S
223 (?:\s+ [+-]?(\d{2})(\d{2})?)? # %z
225 my ($yy, $mm, $dd, $hh, $mi, $ss, $zh, $zm) = ($1, $2, $3, $4, $5, $6, $7, $8);
227 # FIXME: just use localtime for now
228 if (my $mtime = timelocal($ss, $mi, $hh, $dd, $mm, $yy)) {
242 entries_timestamp: blosxom plugin to capture and preserve the original
243 creation timestamp on blosxom posts
247 entries_timestamp is a blosxom plugin for capturing and preserving the
248 original creation timestamp on blosxom posts. It is based on Rael
249 Dornfest's original L<entries_index> plugin, and works in the same way:
250 it maintains an index file (configurable, but 'entries_timestamp.index',
251 by default) maintaining creation timestamps for all blosxom posts, and
252 replaces the default $blosxom::entries subrouting with one returning a
253 file hash using that index.
255 It differs from Rael's L<entries_index> as follows:
259 =item User-friendly timestamps
261 The index file contains two timestamps for every file - the
262 machine-friendly system L<time/2> version, for use by blosxom, and a
263 human-friendly stringified timestamp, to allow timestamps to be reviewed
264 and or modified easily.
266 entries_timestamp ordinarily just assumes those timestamps are in sync,
267 and ignores the string version. If you update the string version and want
268 that to override the system time, you should pass a
269 ?reindex=<reindex_password> argument to blosxom to force the system
270 timestamps to be checked and updated.
272 =item Separate symlink handling
274 entries_timestamp uses separate indexes for posts that are files and
275 posts that are symlinks, and doesn't bother to cache timestamps for
276 the latter at all, deriving them instead from the post they point to.
277 (Note that this means entries_timestamp currently doesn't support
278 symlinks to non-post files at all - they're just ignored).
280 =item Configurable index file name and location
282 I consider post timestamps to be metadata rather than state, so I
283 tend to use a separate C<meta> directory alongside by posts for this,
284 rather than the traditional $plugin_state_dir. You may note care. ;-)
286 =item A complete rewrite
288 Completely rewritten code, since the original used evil evil and-chains
289 and was pretty difficult to understand (imho).
295 L<entries_index>, L<entries_cache>, L<entries_cache_meta>
297 Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
299 =head1 ACKNOWLEDGEMENTS
301 This plugin is largely based on Rael Dornfest's original
302 L<entries_index> plugin.
304 =head1 BUGS AND LIMITATIONS
306 entries_timestamp currently only supports symlinks to local post files,
307 not symlinks to arbitrary files outside your $datadir.
309 entries_timestamp doesn't currently do any kind caching, so it's not
310 directly equivalent to L<entries_cache> or L<entries_cache_meta>.
312 Please report bugs either directly to the author or to the blosxom
313 development mailing list: <blosxom-devel@lists.sourceforge.net>.
317 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
321 Copyright 2007, Gavin Carr.
323 This plugin is licensed under the same terms as blosxom itself i.e.
325 Permission is hereby granted, free of charge, to any person obtaining a
326 copy of this software and associated documentation files (the "Software"),
327 to deal in the Software without restriction, including without limitation
328 the rights to use, copy, modify, merge, publish, distribute, sublicense,
329 and/or sell copies of the Software, and to permit persons to whom the
330 Software is furnished to do so, subject to the following conditions:
332 The above copyright notice and this permission notice shall be included
333 in all copies or substantial portions of the Software.
335 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
336 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
337 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
338 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
339 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
340 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
341 OTHER DEALINGS IN THE SOFTWARE.