focus on delete events and be more defensive when a recentfile cannot be stat-ed
[rersyncrecent.git] / lib / File / Rsync / Mirror / Recentfile / Done.pm
blobf19525ed686ce7c51e4f8cea6b80b04553d489b1
1 package File::Rsync::Mirror::Recentfile::Done;
3 # use warnings;
4 use strict;
6 use File::Rsync::Mirror::Recentfile::FakeBigFloat qw(:all);
8 =encoding utf-8
10 =head1 NAME
12 File::Rsync::Mirror::Recentfile::Done - intervals of already rsynced timespans
14 =cut
16 use version; our $VERSION = qv('0.0.5');
18 =head1 SYNOPSIS
20 my $done = File::Rsync::Mirror::Recentfile::Done->new;
21 $done->register ( $recent_events, [3,4,5,9] ); # registers elements 3-5 and 9
22 my $boolean = $done->covered ( $epoch );
24 =head1 DESCRIPTION
26 Keeping track of already rsynced timespans.
28 =head1 EXPORT
30 No exports.
32 =head1 CONSTRUCTORS
34 =head2 my $obj = CLASS->new(%hash)
36 Constructor. On every argument pair the key is a method name and the
37 value is an argument to that method name.
39 =cut
41 sub new {
42 my($class, @args) = @_;
43 my $self = bless {}, $class;
44 while (@args) {
45 my($method,$arg) = splice @args, 0, 2;
46 $self->$method($arg);
48 return $self;
51 =head1 ACCESSORS
53 =cut
55 my @accessors;
57 BEGIN {
58 @accessors = (
59 "__intervals",
60 "_logfile", # undocced: a small yaml dump appended on every change
61 "_rfinterval", # undocced: the interval of the holding rf
64 my @pod_lines =
65 split /\n/, <<'=cut'; push @accessors, grep {s/^=item\s+//} @pod_lines; }
67 =over 4
69 =item verbose
71 Boolean to turn on a bit verbosity.
73 =back
75 =cut
77 use accessors @accessors;
79 =head1 METHODS
81 =head2 $boolean = $obj->covered ( $epoch1, $epoch2 )
83 =head2 $boolean = $obj->covered ( $epoch )
85 The first form returns true if both timestamps $epoch1 and $epoch2 in
86 floating point notation have been registered within one interval,
87 otherwise false.
89 The second form returns true if this timestamp has been registered.
91 =cut
92 sub _is_sorted {
93 my($self,$ivs) = @_;
94 my $Lup;
95 my $is_sorted = 1;
96 for my $i (0..$#$ivs) {
97 if (defined $Lup) {
98 if (_bigfloatge ($ivs->[$i][0],$Lup)) {
99 warn "Warning (may be harmless): F:R:M:R:Done object contains unsorted internal data";
100 $DB::single++;
101 return 0;
104 $Lup = $ivs->[$i][0];
106 return $is_sorted;
108 sub covered {
109 my($self, $epoch_high, $epoch_low) = @_;
110 die "Alert: covered() called without or with undefined first argument" unless defined $epoch_high;
111 my $intervals = $self->_intervals;
112 return unless @$intervals;
113 if (defined $epoch_low) {
114 ($epoch_high,$epoch_low) = ($epoch_low,$epoch_high) if _bigfloatgt($epoch_low,$epoch_high);
116 my $is_sorted = $self->_is_sorted($intervals);
117 for my $iv (@$intervals) {
118 my($upper,$lower) = @$iv; # may be the same
119 if (defined $epoch_low) {
120 my $goodbound = 0;
121 for my $e ($epoch_high,$epoch_low) {
122 $goodbound++ if
123 $e eq $upper || $e eq $lower || (_bigfloatlt($e,$upper) && _bigfloatgt($e,$lower));
125 return 1 if $goodbound > 1;
126 } else {
127 if ( _bigfloatle ( $epoch_high, $upper ) ) {
128 if ( _bigfloatge ( $epoch_high, $lower )) {
129 return 1; # "between"
131 } elsif ($is_sorted) {
132 return 0; # no chance anymore
136 return 0;
139 =head2 (void) $obj1->merge ( $obj2 )
141 Integrates all intervals in $obj2 into $obj1. Overlapping intervals
142 are conflated/folded/consolidated. Sort order is preserved as decreasing.
144 =cut
145 sub merge {
146 my($self, $other) = @_;
147 my $intervals = $self->_intervals;
148 my $ointervals = $other->_intervals;
149 OTHER: for my $oiv (@$ointervals) {
150 my $splicepos;
151 if (@$intervals) {
152 SELF: for my $i (0..$#$intervals) {
153 my $iv = $intervals->[$i];
154 if ( _bigfloatlt ($oiv->[0],$iv->[1]) ) {
155 # both oiv lower than iv => next
156 next SELF;
158 if ( _bigfloatgt ($oiv->[1],$iv->[0]) ) {
159 # both oiv greater than iv => insert
160 $splicepos = $i;
161 last SELF;
163 # larger(left-iv,left-oiv) becomes left, smaller(right-iv,right-oiv) becomes right
164 $iv->[0] = _bigfloatmax ($oiv->[0],$iv->[0]);
165 $iv->[1] = _bigfloatmin ($oiv->[1],$iv->[1]);
166 next OTHER;
168 unless (defined $splicepos) {
169 if ( _bigfloatlt ($oiv->[0], $intervals->[-1][1]) ) {
170 $splicepos = @$intervals;
171 } else {
172 die "Panic: left-oiv[$oiv->[0]] should be smaller than smallest[$intervals->[-1][1]]";
175 splice @$intervals, $splicepos, 0, [@$oiv];
176 } else {
177 $intervals->[0] = [@$oiv];
182 =head2 (void) $obj->register ( $recent_events_arrayref, $register_arrayref )
184 =head2 (void) $obj->register ( $recent_events_arrayref )
186 The first arrayref is a list of hashes that contain a key called
187 C<epoch> which is a string looking like a number. The second arrayref
188 is a list if integers which point to elements in the first arrayref to
189 be registered.
191 The second form registers all events in $recent_events_arrayref.
193 =cut
195 sub register {
196 my($self, $re, $reg) = @_;
197 my $intervals = $self->_intervals;
198 unless ($reg) {
199 $reg = [0..$#$re];
201 REGISTRANT: for my $i (@$reg) {
202 my $logfile = $self->_logfile;
203 if ($logfile) {
204 require YAML::Syck;
205 open my $fh, ">>", $logfile or die "Could not open '$logfile': $!";
206 print $fh YAML::Syck::Dump({
207 At => "before",
208 Brfinterval => $self->_rfinterval,
209 Ci => $i,
210 ($i>0 ? ("Dre-1" => $re->[$i-1]) : ()),
211 "Dre-0" => $re->[$i],
212 ($i<$#$re ? ("Dre+1" => $re->[$i+1]) : ()),
213 Eintervals => $intervals,
216 $self->_register_one
218 i => $i,
219 re => $re,
220 intervals => $intervals,
222 if ($logfile) {
223 require YAML::Syck;
224 open my $fh, ">>", $logfile or die "Could not open '$logfile': $!";
225 print $fh YAML::Syck::Dump({
226 At => "after",
227 intervals => $intervals,
233 sub _register_one {
234 my($self, $one) = @_;
235 my($i,$re,$intervals) = @{$one}{qw(i re intervals)};
236 die sprintf "Panic: illegal i[%d] larger than number of events[%d]", $i, $#$re
237 if $i > $#$re;
238 my $epoch = $re->[$i]{epoch};
239 return if $self->covered ( $epoch );
240 if (@$intervals) {
241 my $registered = 0;
242 IV: for my $iv (@$intervals) {
243 my($ivhi,$ivlo) = @$iv; # may be the same
244 if ($i > 0
245 && _bigfloatge($re->[$i-1]{epoch}, $ivlo)
246 && _bigfloatle($re->[$i-1]{epoch}, $ivhi)
247 && _bigfloatge($iv->[1],$epoch)
249 # if left neighbor in re belongs to this interval,
250 # then I belong to it too; let us lower the ivlo
251 $iv->[1] = $epoch;
252 $registered++;
254 if ($i < $#$re
255 && _bigfloatle($re->[$i+1]{epoch}, $ivhi)
256 && _bigfloatge($re->[$i+1]{epoch}, $ivlo)
257 && _bigfloatle($iv->[0],$epoch)
259 # ditto for right neighbor; increase the ivhi
260 $iv->[0] = $epoch;
261 $registered++;
263 last IV if $registered>=2;
265 if ($registered == 2) {
266 $self->_register_one_fold2
268 $intervals,
269 $epoch,
271 } elsif ($registered == 1) {
272 $self->_register_one_fold1 ($intervals);
273 } else {
274 $self->_register_one_fold0
276 $intervals,
277 $epoch,
280 } else {
281 $intervals->[0] = [($epoch)x2];
285 sub _register_one_fold0 {
286 my($self,
287 $intervals,
288 $epoch,
289 ) = @_;
290 my $splicepos;
291 for my $i (0..$#$intervals) {
292 if (_bigfloatgt ($epoch, $intervals->[$i][0])) {
293 $splicepos = $i;
294 last;
297 unless (defined $splicepos) {
298 if (_bigfloatlt ($epoch, $intervals->[-1][1])) {
299 $splicepos = @$intervals;
300 } else {
301 die "Panic: epoch[$epoch] should be smaller than smallest[$intervals->[-1][1]]";
304 splice @$intervals, $splicepos, 0, [($epoch)x2];
307 # conflate: eliminate overlapping intervals
308 sub _register_one_fold1 {
309 my($self,$intervals) = @_;
310 LOOP: while () {
311 my $splicepos;
312 for my $i (0..$#$intervals-1) {
313 if (_bigfloatle ($intervals->[$i][1],
314 $intervals->[$i+1][0])) {
315 $intervals->[$i+1][0] = $intervals->[$i][0];
316 $splicepos = $i;
317 last;
320 if (defined $splicepos) {
321 splice @$intervals, $splicepos, 1;
322 } else {
323 last LOOP;
328 sub _register_one_fold2 {
329 my($self,
330 $intervals,
331 $epoch,
332 ) = @_;
333 # we know we have hit twice, like in
334 # 40:[45,40], [40,35]
335 # 40:[45,40],[42,37],[40,35]
336 # 45:[45,40], [45,35]
337 # 45:[45,40],[42,37],[45,35]
338 # 35:[45,35], [40,35]
339 # 35:[45,35],[42,37],[40,35]
340 my($splicepos, $splicelen, %assert_between);
341 for my $i (0..$#$intervals) {
342 if ( $epoch eq $intervals->[$i][0]
343 or $epoch eq $intervals->[$i][1]
345 for (my $j = 1; $i+$j <= $#$intervals; $j++) {
346 if ( $epoch eq $intervals->[$i+$j][0]
347 or $epoch eq $intervals->[$i+$j][1]) {
348 $intervals->[$i+$j][0] = _bigfloatmax($intervals->[$i][0],$intervals->[$i+$j][0]);
349 $intervals->[$i+$j][1] = _bigfloatmin($intervals->[$i][1],$intervals->[$i+$j][1]);
350 $splicepos = $i;
351 $splicelen = $j;
352 last;
353 } else {
354 for my $k (0,1) {
355 $assert_between{$intervals->[$i+$j][$k]}++;
361 if (defined $splicepos) {
362 for my $k (keys %assert_between) {
363 if (_bigfloatgt($k,$intervals->[$splicepos+$splicelen][0])
364 or _bigfloatlt($k,$intervals->[$splicepos+$splicelen][1])){
365 $DB::single=1;
366 require Data::Dumper;
367 die "Panic: broken intervals:".Data::Dumper::Dumper($intervals);
370 splice @$intervals, $splicepos, $splicelen;
371 } else {
372 $DB::single=1;
373 die "Panic: Could not find an interval position to insert '$epoch'";
377 =head2 reset
379 Forgets everything ever done and gives way for a new round of
380 mirroring. Usually called when the dirtymark on upstream has changed.
382 =cut
384 sub reset {
385 my($self) = @_;
386 $self->_intervals(undef);
389 =head1 PRIVATE METHODS
391 =head2 _intervals
393 =cut
394 sub _intervals {
395 my($self,$set) = @_;
396 if (@_ >= 2) {
397 $self->__intervals($set);
399 my $x = $self->__intervals;
400 unless (defined $x) {
401 $x = [];
402 $self->__intervals ($x);
404 return $x;
407 =head1 COPYRIGHT & LICENSE
409 Copyright 2008, 2009 Andreas König.
411 This program is free software; you can redistribute it and/or modify it
412 under the same terms as Perl itself.
414 =cut
416 1; # End of File::Rsync::Mirror::Recentfile
418 # Local Variables:
419 # mode: cperl
420 # cperl-indent-level: 4
421 # End: