package ProgressMonitor::Stringify::Fields::ETA;
use warnings;
use strict;
use ProgressMonitor::State;
require ProgressMonitor::Stringify::Fields::AbstractField if 0;
use Time::HiRes qw(time);
use constant MINUTE => 60;
use constant HOUR => 60 * MINUTE;
use constant DAY => 24 * HOUR;
no strict 'refs';
use classes
extends => 'ProgressMonitor::Stringify::Fields::AbstractField',
new => 'new',
attrs_pr => ['start', 'index', 'lastHH', 'lastMM', 'lastSS', 'lastDelim', 'lastTime', 'lastLeft'],
;
sub new
{
my $class = shift;
my $cfg = shift;
my $self = $class->SUPER::_new($cfg, $CLASS);
$cfg = $self->_get_cfg;
# we only wish to portray up to 23:59:49 at this point
#
my $delim = $cfg->get_mainDelimiter;
my $delimLen = length($delim);
$self->_set_width(2 + $delimLen + 2 + $delimLen + 2);
my $ofc = $cfg->get_unknownCharacter;
$self->{$ATTR_start} = 0;
$self->{$ATTR_index} = 0;
$self->{$ATTR_lastHH} = $self->{$ATTR_lastMM} = $self->{$ATTR_lastSS} = "$ofc$ofc";
$self->{$ATTR_lastDelim} = $delim;
$self->{$ATTR_lastTime} = 0;
$self->{$ATTR_lastLeft} = 0;
return $self;
}
sub render
{
my $self = shift;
my $state = shift;
my $ticks = shift;
my $totalTicks = shift;
my $clean = shift;
my $now = time;
my $timeSinceStart = $now - $self->{$ATTR_start};
my $cfg = $self->_get_cfg;
my $hh = $self->{$ATTR_lastHH};
my $mm = $self->{$ATTR_lastMM};
my $ss = $self->{$ATTR_lastSS};
my $delim = $self->{$ATTR_lastDelim};
if (!$self->{$ATTR_start})
{
# this is the first call - just render 'unknown'
#
$self->{$ATTR_start} = $self->{$ATTR_lastTime} = $now;
}
else
{
if ($state == STATE_DONE)
{
my $ofc = $cfg->get_unknownCharacter;
$hh = $mm = $ss = "$ofc$ofc";
($hh, $mm, $ss) = $self->__fmtHMS($timeSinceStart) if $timeSinceStart < DAY;
}
else
{
# to avoid too much flickering, only update at the given rate
#
if ($now >= $self->{$ATTR_lastTime} + $cfg->get_maxUpdateRate)
{
# flicker delimiter
#
my $seq = $cfg->get_idleDelimiterSequence;
$delim = $seq->[$self->{$ATTR_index}++ % @$seq];
# try to ensure we have some information to predict on
#
my $ratio = defined($totalTicks) && $totalTicks > 0 ? $ticks / $totalTicks : 0;
if ($ratio > $cfg->get_waitForRatio)
{
my $left = int($timeSinceStart * ((1 - $ratio) / $ratio));
$left = DAY if $left > DAY;
if ($clean || $left != $self->{$ATTR_lastLeft})
{
($hh, $mm, $ss) = $self->__fmtHMS($left);
$delim = $cfg->get_mainDelimiter;
}
$self->{$ATTR_lastLeft} = $left;
}
$self->{$ATTR_lastTime} = $now;
}
}
}
$self->{$ATTR_lastHH} = $hh;
$self->{$ATTR_lastMM} = $mm;
$self->{$ATTR_lastSS} = $ss;
$self->{$ATTR_lastDelim} = $delim;
return sprintf("%s%s%s%s%s", $hh, $delim, $mm, $delim, $ss);
}
sub completed
{
my $self = shift;
# We're done - report the actual time it took
# but check for overflow
#
my $cfg = $self->_get_cfg;
my $ofc = $cfg->get_unknownCharacter;
my ($hh, $mm, $ss);
$hh = $mm = $ss = "$ofc$ofc";
my $timeSinceStart = time - $self->{$ATTR_start};
($hh, $mm, $ss) = $self->__fmtHMS($timeSinceStart) if $timeSinceStart < DAY;
my $delim = $cfg->get_mainDelimiter;
return sprintf("%s%s%s%s%s", $hh, $delim, $mm, $delim, $ss);
}
sub __fmtHMS
{
my $self = shift;
my $time = shift;
my $fmt = '%02u';
my $hh = sprintf($fmt, int($time / DAY));
my $mm = sprintf("%02u", int(($time % DAY) / MINUTE));
my $ss = sprintf("%02u", $time % MINUTE);
return ($hh, $mm, $ss);
}
###
package ProgressMonitor::Stringify::Fields::ETAConfiguration;
use strict;
use warnings;
no strict 'refs';
use classes
extends => 'ProgressMonitor::Stringify::Fields::AbstractFieldConfiguration',
attrs => ['unknownCharacter', 'mainDelimiter', 'idleDelimiterSequence', 'waitForRatio', 'maxUpdateRate'],
;
sub defaultAttributeValues
{
my $self = shift;
return {
%{$self->SUPER::defaultAttributeValues()},
unknownCharacter => '-',
mainDelimiter => ':',
idleDelimiterSequence => [' ', ':'],
waitForRatio => 0.01,
maxUpdateRate => 1,
};
}
sub checkAttributeValues
{
my $self = shift;
$self->SUPER::checkAttributeValues;
X::Usage->throw("unknownCharacter should have a length of 1") unless length($self->get_unknownCharacter) == 1;
my $seq = $self->get_idleDelimiterSequence;
X::Usage->throw("idleDelimiterSequence must be an array") unless ref($seq) eq 'ARRAY';
my $len = length($self->get_mainDelimiter);
for (@$seq)
{
X::Usage->throw("all idleDelimiterSequence elements must have same length as mainDelimiter") if length($_) != $len;
}
X::Usage->throw("0 < waitForRatio <= 1") if ($self->get_waitForRatio < 0 || $self->get_waitForRatio > 1);
X::Usage->throw("maxUpdateRate can not be negative") if $self->get_waitForRatio < 0;
return;
}
############################
=head1 NAME
ProgressMonitor::Stringify::Field::ETA - a field implementation that renders progress
as a time-to-completion.
=head1 VERSION
Version 0.01
=head1 DESCRIPTION
@@TODO@@
=head1 AUTHOR
Kenneth Olwing, C<< <knth at cpan.org> >>
=head1 BUGS
I wouldn't be surprised! If you can come up with a minimal test that shows the
problem I might be able to take a look. Even better, send me a patch.
Please report any bugs or feature requests to
C<bug-progressmonitor at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ProgressMonitor>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc ProgressMonitor
You can also look for information at:
=over 4
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/ProgressMonitor>
=item * CPAN Ratings
L<http://cpanratings.perl.org/d/ProgressMonitor>
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ProgressMonitor>
=item * Search CPAN
L<http://search.cpan.org/dist/ProgressMonitor>
=back
=head1 ACKNOWLEDGEMENTS
Thanks to my family. I'm deeply grateful for you!
=head1 COPYRIGHT & LICENSE
Copyright 2006,2007 Kenneth Olwing, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1; # End of ProgressMonitor::Stringify::Fields::ETA