File Coverage

blib/lib/Time/ETA.pm
Criterion Covered Total %
statement 144 144 100.0
branch 60 60 100.0
condition n/a
subroutine 26 26 100.0
pod 14 14 100.0
total 244 244 100.0


line stmt bran cond sub pod time code
1             package Time::ETA;
2             {
3             $Time::ETA::VERSION = '1.1.1';
4             }
5              
6             # ABSTRACT: calculate estimated time of accomplishment
7              
8              
9 9     9   268342 use warnings;
  9         20  
  9         1861  
10 9     9   152 use strict;
  9         18  
  9         471  
11              
12 9     9   50 use Carp;
  9         155  
  9         1449  
13 9         59 use Time::HiRes qw(
14             gettimeofday
15             tv_interval
16 9     9   12785 );
  9         39788  
17              
18 9     9   13567 use YAML;
  9         209589  
  9         22173  
19              
20             my $true = 1;
21             my $false = '';
22              
23             our $SERIALIZATION_API_VERSION = 3;
24              
25              
26             sub new {
27 23     23 1 10173 my ($class, %params) = @_;
28 23         47 my $self = {};
29 23         56 bless $self, $class;
30              
31 23 100       283 croak "Expected to get parameter 'milestones'. Stopped" if not defined $params{milestones};
32 22 100       68 croak "Parameter 'milestones' should be positive integer. Stopped" if not $self->_is_positive_integer($params{milestones});
33              
34 7         46 $self->{_milestones} = $params{milestones};
35 7         19 $self->{_passed_milestones} = 0;
36 7         18 $self->{_elapsed} = 0;
37 7         50 $self->{_start} = [gettimeofday];
38 7         20 $self->{_is_paused} = $false;
39              
40 7         24 return $self;
41             }
42              
43              
44             sub get_elapsed_seconds {
45 60     60 1 23268 my ($self) = @_;
46              
47 60         1775 my $elapsed_seconds;
48              
49 60 100       185 if ($self->is_completed()) {
50 16         773 $elapsed_seconds = tv_interval($self->{_start}, $self->{_end});
51             } else {
52 44         271 $elapsed_seconds = tv_interval($self->{_start}, [gettimeofday]);
53             }
54              
55 60         990 return $elapsed_seconds;
56             }
57              
58              
59             sub get_remaining_seconds {
60 65     65 1 7972 my ($self) = @_;
61              
62 65 100       528 croak "There is not enough data to calculate estimated time of accomplishment. Stopped" if not $self->can_calculate_eta();
63              
64 61 100       183 return 0 if $self->is_completed();
65              
66 37         174 my $elapsed_before_milestone = tv_interval($self->{_start}, $self->{_milestone_pass});
67 37         528 my $elapsed_after_milestone = tv_interval($self->{_milestone_pass}, [gettimeofday()]);
68              
69 37         410 my $remaining_milestones = $self->{_milestones} - $self->{_passed_milestones};
70              
71 37         69 my $one_milestone_completion_time = $elapsed_before_milestone/$self->{_passed_milestones};
72 37         76 my $remaining_seconds = ($one_milestone_completion_time * $remaining_milestones) - $elapsed_after_milestone;
73              
74 37         107 return $remaining_seconds;
75             }
76              
77              
78             sub get_remaining_time {
79 8     8 1 18 my ($self) = @_;
80              
81 8         27 my $time = $self->_get_time_from_seconds($self->get_remaining_seconds());
82              
83 8         48 return $time;
84             }
85              
86              
87             sub get_completed_percent {
88 61     61 1 1459 my ($self) = @_;
89              
90 61         261 my $completed_percent = (100 * $self->{_passed_milestones}) / $self->{_milestones};
91              
92 61         622 return $completed_percent;
93             }
94              
95              
96             sub is_completed {
97 177     177 1 41653 my ($self) = @_;
98              
99 177 100       2610 return ($self->{_passed_milestones} == $self->{_milestones})
100             ? $true
101             : $false
102             ;
103             }
104              
105              
106             sub pass_milestone {
107 21     21 1 7673 my ($self) = @_;
108              
109 21 100       86 if ($self->{_passed_milestones} < $self->{_milestones}) {
110 13         29 $self->{_passed_milestones}++;
111             } else {
112 8         3541 croak "You have already completed all milestones. It it incorrect to run pass_milestone() now. Stopped";
113             }
114              
115 13         41 my $dt = [gettimeofday];
116              
117 13         38 $self->{_milestone_pass} = $dt;
118              
119 13 100       63 if ($self->{_passed_milestones} == $self->{_milestones}) {
120 2         6 $self->{_end} = $dt;
121             }
122              
123 13         85 return $false;
124             }
125              
126              
127             sub can_calculate_eta {
128 125     125 1 22045 my ($self) = @_;
129              
130 125 100       492 if ($self->{_passed_milestones} > 0) {
131 113         677 return $true;
132             } else {
133 12         692 return $false;
134             }
135             }
136              
137              
138             sub pause {
139 4     4 1 622 my ($self) = @_;
140              
141 4 100       14 croak "The object is already paused. Can't pause paused. Stopped" if $self->is_paused();
142              
143 3         21 my $elapsed_seconds = tv_interval($self->{_start}, [gettimeofday]);
144 3         43 $self->{_elapsed} += $elapsed_seconds;
145 3         9 $self->{_start} = undef;
146              
147 3         8 $self->{_is_paused} = $true;
148              
149 3         9 return $false;
150             }
151              
152              
153             sub is_paused {
154 39     39 1 10057 my ($self) = @_;
155              
156 39         1068 return $self->{_is_paused};
157             }
158              
159              
160             sub resume {
161 3     3 1 13 my ($self, $string) = @_;
162              
163 3 100       12 croak "The object isn't paused. Can't resume. Stopped" if not $self->is_paused();
164              
165             # Setting the start time
166             # Start time is the current time minus time that has already pass
167 2         12 my $timeofday = [gettimeofday];
168 2         12 my $start = ($timeofday->[0] * 1_000_000 + $timeofday->[1]) - int($self->{_elapsed} * 1_000_000);
169 2         11 $self->{_start} = [int($start / 1_000_000), $start % 1_000_000];
170              
171 2         7 $self->{_elapsed} = 0;
172 2         4 $self->{_is_paused} = $false;
173              
174 2         7 return $false;
175             }
176              
177              
178             sub serialize {
179 37     37 1 4363 my ($self) = @_;
180              
181 37         643 my $data = {
182             _version => $SERIALIZATION_API_VERSION,
183             _milestones => $self->{_milestones},
184             _passed_milestones => $self->{_passed_milestones},
185             _start => $self->{_start},
186             _milestone_pass => $self->{_milestone_pass},
187             _end => $self->{_end},
188             _is_paused => $self->{_is_paused},
189             _elapsed => $self->{_elapsed},
190             };
191              
192 37         178 my $string = Dump($data);
193              
194 37         246013 return $string;
195             }
196              
197              
198             sub spawn {
199 58     58 1 7245 my ($class, $string) = @_;
200              
201 58 100       573 croak "Can't spawn Time::ETA object. No serialized data specified. Stopped" if not defined $string;
202              
203 56         92 my $data;
204              
205 56         90 eval {
206 56         252 $data = Load($string);
207             };
208              
209 56 100       483562 if ($@) {
210 2         265 croak "Can't spawn Time::ETA object. Got error from YAML parser:\n" . $@ . "Stopped";
211             }
212              
213 54 100       11687 croak "Can't spawn Time::ETA object. Got incorrect serialized data. Stopped" if ref $data ne "HASH";
214              
215 52 100       538 croak "Can't spawn Time::ETA object. Serialized data does not contain version. Stopped" if not defined $data->{_version};
216              
217 50         199 my $v = _get_version();
218 50 100       1164 croak "Can't spawn Time::ETA object. Version $v can work only with serialized data version $SERIALIZATION_API_VERSION. Stopped"
219             if $data->{_version} ne $SERIALIZATION_API_VERSION;
220              
221 46 100       210 croak "Can't spawn Time::ETA object. Serialized data contains incorrect number of milestones. Stopped"
222             if not _is_positive_integer(undef, $data->{_milestones});
223              
224 44 100       149 croak "Can't spawn Time::ETA object. Serialized data contains incorrect number of passed milestones. Stopped"
225             if not _is_positive_integer_or_zero(undef, $data->{_passed_milestones});
226              
227 42 100       269 if (not $data->{_is_paused}) {
228 41         165 _check_gettimeofday(
229             undef,
230             value => $data->{_start},
231             name => "start time"
232             );
233             }
234              
235 36 100       1498 if (defined $data->{_end}) {
236 8         28 _check_gettimeofday(
237             undef,
238             value => $data->{_end},
239             name => "end time"
240             );
241             }
242              
243 36 100       151 if (defined $data->{_milestone_pass}) {
244 29         98 _check_gettimeofday(
245             undef,
246             value => $data->{_milestone_pass},
247             name => "last milestone pass time"
248             );
249             }
250              
251 36         415 my $self = {
252             _milestones => $data->{_milestones},
253             _passed_milestones => $data->{_passed_milestones},
254             _start => $data->{_start},
255             _milestone_pass => $data->{_milestone_pass},
256             _end => $data->{_end},
257             _is_paused => $data->{_is_paused},
258             _elapsed => $data->{_elapsed},
259             };
260              
261 36         151 bless $self, $class;
262              
263 36         373 return $self;
264             }
265              
266              
267             sub can_spawn {
268 13     13 1 8832 my ($class, $string) = @_;
269              
270 13         29 eval {
271 13         36 my $eta = spawn($class, $string);
272             };
273              
274 13 100       106 if (not $@) {
275 3         26 return $true;
276             } else {
277 10         42 return $false;
278             }
279             }
280              
281             sub _check_gettimeofday {
282 80     80   1112 my ($self, %params) = @_;
283              
284 80 100       1336 croak "Expected to get 'name'" unless defined $params{name};
285              
286 79 100       866 croak "Can't spawn Time::ETA object. Serialized data contains incorrect data for $params{name}. Stopped"
287             if ref $params{value} ne "ARRAY";
288              
289 77 100       213 croak "Can't spawn Time::ETA object. Serialized data contains incorrect seconds in $params{name}. Stopped"
290             if not _is_positive_integer_or_zero(undef, $params{value}->[0]);
291              
292 75 100       599 croak "Can't spawn Time::ETA object. Serialized data contains incorrect microseconds in $params{name}. Stopped"
293             if not _is_positive_integer_or_zero(undef, $params{value}->[1]);
294              
295 73         327 return $false;
296             }
297              
298             sub _is_positive_integer_or_zero {
299 272     272   5312 my ($self, $maybe_number) = @_;
300              
301 272 100       709 return $false if not defined $maybe_number;
302              
303             # http://www.perlmonks.org/?node_id=614452
304 271         901 my $check_result = $maybe_number =~ m{
305             \A # beginning of string
306             \+? # optional plus sign
307             [0-9]+ # mandatory digits, including zero
308             \z # end of string
309             }xms;
310              
311 271         5519 return $check_result;
312             }
313              
314             sub _is_positive_integer {
315 74     74   14300 my ($self, $maybe_number) = @_;
316              
317 74 100       271 return $false if not defined $maybe_number;
318              
319 73 100       347 return $false if $maybe_number eq '0';
320 71 100       407 return $false if $maybe_number eq '+0';
321              
322 70         251 return _is_positive_integer_or_zero(undef, $maybe_number);
323             }
324              
325             sub _get_time_from_seconds {
326 17     17   5066 my ($self, $input_sec) = @_;
327              
328 17         26 my $text;
329             {
330             # This is a quick solution. This like make code more robust.
331             # With this like the code will fail if $input_sec is not a number.
332 9     9   137 use warnings FATAL => 'all';
  9         25  
  9         2164  
  17         159  
333              
334 17         24 my $left_sec;
335 17         55 my $hour = int($input_sec/3600);
336 16         31 $left_sec = $input_sec - ($hour * 3600);
337              
338 16         27 my $min = int($left_sec/60);
339 16         20 $left_sec = $left_sec - ($min * 60);
340              
341 16         183 $text =
342             sprintf("%01d", $hour) . ":"
343             . sprintf("%02d", $min) . ":"
344             . sprintf("%02d", $left_sec)
345             ;
346             }
347              
348 16         82 return $text;
349             }
350              
351              
352             sub _get_version {
353 9     9   70 no warnings 'uninitialized';
  9         22  
  9         707  
354 51     51   152 my $v = "$Time::ETA::VERSION";
355 51         150 return $v;
356             }
357              
358              
359             1;
360              
361             __END__