1 package File
::Rsync
::Mirror
::Recent
;
5 use File
::Rsync
::Mirror
::Recentfile
;
11 File::Rsync::Mirror::Recent - mirroring via rsync made efficient
19 package File
::Rsync
::Mirror
::Recent
;
21 use File
::Basename
qw(dirname fileparse);
22 use File
::Copy
qw(cp);
23 use File
::Path
qw(mkpath);
26 use List
::Util
qw(first max);
27 use Scalar
::Util
qw(reftype);
32 use version
; our $VERSION = qv
('0.0.1');
36 B<!!!! PRE-ALPHA ALERT !!!!>
38 Nothing in here is believed to be stable, nothing yet intended for
39 public consumption. The plan is to provide a script in one of the next
40 releases that acts as a frontend for all the backend functionality.
41 Option and method names will very likely change.
43 File::Rsync::Mirror::Recent is acting at a higher level than
44 File::Rsync::Mirror::Recentfile. File::Rsync::Mirror::Recent
45 establishes a view on a collection of recentfile objects and provides
46 abstractions spanning multiple intervals associated with those.
48 B<Mostly unimplemented as of yet>. Will need to shift some accessors
49 from recentfile to recent.
53 my $rr = File::Rsync::Mirror::Recent->new
55 ignore_link_stat_errors => 1,
56 localroot => "/home/ftp/pub/PAUSE/authors",
57 remote => "pause.perl.org::authors/RECENT.recent",
74 =head2 my $obj = CLASS->new(%hash)
76 Constructor. On every argument pair the key is a method name and the
77 value is an argument to that method name.
82 my($class, @args) = @_;
83 my $self = bless {}, $class;
85 my($method,$arg) = splice @args, 0, 2;
100 "_principal_recentfile",
106 split /\n/, <<'=cut'; push @accessors, grep {s/^=item\s+//} @pod_lines; }
110 =item ignore_link_stat_errors
112 as in F:R:M:Recentfile
116 Option to specify the local principal file for operations with a local
117 collection of recentfiles.
121 as in F:R:M:Recentfile
123 =item max_files_per_connection
125 as in F:R:M:Recentfile
133 XXX: this is (ATM) different from Recentfile!!!
135 =item remote_recentfile
137 Rsync address of the remote C<RECENT.recent> symlink or whichever name
138 the principal remote recentfile has.
142 Things like compress, links, times or checksums. Passed in to the
143 File::Rsync object used to run the mirror.
147 Boolean to turn on a bit verbosity.
153 use accessors
@accessors;
157 =head2 $arrayref = $obj->news ( %options )
159 XXX WORK IN PROGRESS XXX
161 Testing this ATM with:
163 perl -Ilib bin/rrr-news \
166 -local /home/ftp/pub/PAUSE/authors/RECENT.recent
168 perl -Ilib bin/rrr-news \
172 -localroot /home/ftp/pub/PAUSE/authors/ \
173 -remote pause.perl.org::authors/RECENT.recent
176 Note: all parameters that can be passed to recent_events can also be specified here.
178 Note: all data are kept in memory
183 my($self, %opt) = @_;
184 my $local = $self->local;
186 if (my $remote = $self->remote) {
188 if ($localroot = $self->localroot) {
189 # nice, they know what they are doing
191 die "FIXME: remote called without localroot should trigger File::Temp.... TBD, sorry";
194 die "Alert: neither local nor remote specified, cannot continue";
197 my $rfs = $self->recentfiles;
202 $locopt{before
} = $before;
204 $locopt{max
} -= scalar @
$ret;
205 last if $locopt{max
} <= 0;
208 my $res = $rf->recent_events(%locopt);
212 if ($opt{max
} && scalar @
$ret > $opt{max
}) {
216 if ( $locopt{info
}{last} && $locopt{info
}{last}{epoch
} < $opt{after
} ) {
219 if ( $opt{after
} > $locopt{info
}{first
}{epoch
} ) {
226 $before = $res->[-1]{epoch
};
227 $before = $opt{before
} if $opt{before
} && $opt{before
} < $before;
234 returns a string that summarizes the state of all recentfiles
235 collected in this Recent object.
240 my $rfs = $self->recentfiles;
242 RECENTFILE
: for my $rf (@
$rfs) {
243 my $re=$rf->recent_events;
246 my $span = $re->[0]{epoch
}-$re->[-1]{epoch
};
251 sprintf ("%.3f", $re->[0]{epoch
}),
252 sprintf ("%.3f", $re->[-1]{epoch
}),
253 sprintf ("%.3f", $span),
254 ($rf->interval eq "Z"
258 sprintf ("%5.1f%%", 100 * $span / $rf->interval_secs)
261 @rank{@
{$rfsummary}[2,3]} = ();
267 @rank{sort {$b <=> $a} keys %rank} = 1..keys %rank;
268 my $maxrank = max
values %rank;
270 my $string = " " x
$maxrank;
273 push @borders, $rank{$s->[$_]}-1;
275 for ($borders[0],$borders[1]) {
276 substr($string,$_,1) = "^";
281 for my $i (0..$#{$s[0]}) {
282 my $maxlength = max
map { length $_->[$i] } @s;
283 push @sprintf, "%" . $maxlength . "s";
285 my $sprintf = join " ", @sprintf;
287 my $headline = sprintf $sprintf,
297 join "", $headline, map { sprintf $sprintf, @
$_ } @s;
302 (Private method, not for public use) Keeping track of already handled
303 files. Currently it is a hash, will probably become a database with
310 my $db = $self->_pathdb;
311 unless (defined $db) {
317 =head2 $recentfile = $obj->principal_recentfile ()
319 returns the principal recentfile of this tree.
323 sub principal_recentfile
{
325 my $prince = $self->_principal_recentfile;
326 return $prince if defined $prince;
327 my $local = $self->local;
329 $prince = File
::Rsync
::Mirror
::Recentfile
->new_from_file ($local);
331 if (my $remote = $self->remote) {
333 if ($localroot = $self->localroot) {
334 # nice, they know what they are doing
336 die "FIXME: remote called without localroot should trigger File::Temp.... TBD, sorry";
338 my $rf0 = $self->_recentfile_object_for_remote;
341 die "Alert: neither local nor remote specified, cannot continue";
344 $self->_principal_recentfile($prince);
348 =head2 $recentfiles_arrayref = $obj->recentfiles ()
350 returns a reference to the complete list of recentfile objects that
351 describe this tree. No guarantee is given that the represented
352 recentfiles exist or have been read. They are just bare objects.
358 my $rfs = $self->_recentfiles;
359 return $rfs if defined $rfs;
360 my $rf0 = $self->principal_recentfile;
362 $rf0->_pathdb ( $db );
363 my $aggregator = $rf0->aggregator;
365 for my $agg (@
$aggregator) {
366 my $nrf = $rf0->_sparse_clone;
367 $nrf->interval ( $agg );
368 $nrf->have_mirrored ( 0 );
369 $nrf->_pathdb ( $db );
372 $self->_recentfiles(\
@rf);
376 =head2 $success = $obj->rmirror ( %options )
378 XXX WORK IN PROGRESS XXX
380 Mirrors all recentfiles of the I<remote> address. Afterwards it should
381 work through all of them.
383 Testing this ATM with:
385 use File::Rsync::Mirror::Recent;
386 my $rrr = File::Rsync::Mirror::Recent->new(
387 ignore_link_stat_errors => 1,
388 localroot => "/home/ftp/pub/PAUSE/authors",
389 remote => "pause.perl.org::authors/RECENT.recent",
390 max_files_per_connection => 5,
400 $rrr->rmirror ( "skip-deletes" => 1 );
406 my($self, %options) = @_;
408 # my $rf0 = $self->_recentfile_object_for_remote;
409 my $rfs = $self->recentfiles;
411 my $_once_per_20s = sub {
412 my $p = $self->principal_recentfile;
413 require YAML
::Syck
; YAML
::Syck
::DumpFile
("recent-rmirror-state-$$.yml",$self); # XXX
416 print STDERR
("TODO: refetch prince and let it reset what needs to be resetted\n");
421 # XXX exit gracefully (reminder)
423 my $minimum_time_per_loop = 20; # XXX needs accessor: warning, if
424 # set too low, we do nothing but
425 # mirror the principal!
427 my $ttleave = time + $minimum_time_per_loop;
429 RECENTFILE
: for my $i (0..$#$rfs) {
431 last RECENTFILE
if time > $ttleave;
433 $rfs->[$i+1]->done->merge($rf->done) if $i < $#$rfs;
436 WORKUNIT
: while (time < $ttleave) {
438 my $sleep = $rf->sleep_per_connection;
439 $sleep = 0.42 unless defined $sleep; # XXX double accessor!
440 for (($sleep)x5
) { # want a bit more
444 "Napping (%s/%s) ...\n",
449 Time
::HiRes
::sleep $_ if $_;
451 $rfs->[$i+1]->done->merge($rf->done) if $i < $#$rfs;
462 my $sleep = $ttleave - time;
466 "Retreat to the Dormitory (%s) ...",
471 # negative time not invented yet:)
476 # mirrors the recentfile and instantiates the recentfile object
477 sub _recentfile_object_for_remote
{
479 # get the remote recentfile
480 my $rrfile = $self->remote or die "Alert: cannot construct a recentfile object without the 'remote' attribute";
481 my($remoteroot,$rfilename) = $rrfile =~ m{(.+)/([^/]+)};
482 $self->remoteroot($remoteroot);
485 "ignore_link_stat_errors",
487 "max_files_per_connection",
492 my $rf0 = File
::Rsync
::Mirror
::Recentfile
->new (map {($_ => $self->$_)} @need_args);
493 my $lfile = $rf0->get_remote_recentfile_as_tempfile ($rfilename);
494 # while it is a symlink, resolve it
496 my $symlink = readlink $lfile;
497 if ($symlink =~ m
|/|) {
498 die "FIXME: filenames containing '/' not supported, got '$symlink'";
500 $lfile = $rf0->get_remote_recentfile_as_tempfile ($symlink);
504 $rfpeek = File
::Rsync
::Mirror
::Recentfile
->new_from_file ( $lfile );
506 die "Alert: recentfile '$lfile' is empty, cannot continue";
508 for my $peek (qw(_interval aggregator filenameroot protocol serializer_suffix)) {
509 $rf0->$peek($rfpeek->$peek);
511 for my $need_arg (@need_args) {
512 $rf0->$need_arg ( $self->$need_arg );
518 =head2 (void) $obj->rmirror_loop
520 (TBD) Run rmirror in an endless loop.
529 =head2 $hash = $obj->verify
531 (TBD) Runs find on the local tree, collects all existing files from
532 recentfiles, compares their names. The returned hash contains the keys
533 C<todelete> and C<toadd>.
548 Please report any bugs or feature requests through the web interface
550 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=File-Rsync-Mirror-Recent>.
551 I will be notified, and then you'll automatically be notified of
552 progress on your bug as I make changes.
556 You can find documentation for this module with the perldoc command.
558 perldoc File::Rsync::Mirror::Recent
560 You can also look for information at:
564 =item * RT: CPAN's request tracker
566 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=File-Rsync-Mirror-Recent>
568 =item * AnnoCPAN: Annotated CPAN documentation
570 L<http://annocpan.org/dist/File-Rsync-Mirror-Recent>
574 L<http://cpanratings.perl.org/d/File-Rsync-Mirror-Recent>
578 L<http://search.cpan.org/dist/File-Rsync-Mirror-Recent>
583 =head1 ACKNOWLEDGEMENTS
585 Thanks to RJBS for module-starter.
587 =head1 COPYRIGHT & LICENSE
589 Copyright 2008 Andreas König.
591 This program is free software; you can redistribute it and/or modify it
592 under the same terms as Perl itself.
597 1; # End of File::Rsync::Mirror::Recent