File Coverage

blib/lib/ProgressMonitor/AbstractStatefulMonitor.pm
Criterion Covered Total %
statement 123 137 89.7
branch 18 40 45.0
condition 3 8 37.5
subroutine 28 28 100.0
pod 6 7 85.7
total 178 220 80.9


line stmt bran cond sub pod time code
1             package ProgressMonitor::AbstractStatefulMonitor;
2            
3 10     10   9189 use warnings;
  10         19  
  10         284  
4 10     10   51 use strict;
  10         18  
  10         303  
5            
6 10     10   826 use ProgressMonitor::Exceptions;
  10         15  
  10         216  
7 10     10   5589 use ProgressMonitor::State;
  10         25  
  10         737  
8 10     10   5510 use ProgressMonitor::SetMessageFlags;
  10         27  
  10         1250  
9            
10             require ProgressMonitor if 0;
11            
12             use classes
13 10         118 extends => 'ProgressMonitor',
14             class_methods => ['_new'],
15             methods => {render => 'ABSTRACT',},
16             attrs_pr => [
17             'cfg', 'canceled', 'state', 'totalTicks', 'ticks', 'multiplier',
18             'message', 'prepareMessage', 'beginMessage', 'tickMessage', 'endMessage',
19             ],
20             throws => ['X::ProgressMonitor::InvalidState', 'X::ProgressMonitor::TooManyTicks', 'X::ProgressMonitor::UnknownSetMessageFlag',],
21 10     10   58 ;
  10         19  
22            
23             sub begin
24             {
25 12     12 1 199 my $self = shift;
26 12         59 my $totalTicks = shift;
27            
28             # it is allowed to call begin directly if there's no preparatory
29             # work to be done - so we call prepare if not already done
30             #
31 12         44 my $state = $self->_get_state;
32 12 50       56 $self->prepare if $state == STATE_NEW;
33            
34             # enter the active state, signalling 'prepare complete'
35             #
36 12         67 $self->__shiftState(STATE_PREPARING, STATE_ACTIVE);
37            
38             # record the total ticks that can be expected
39             # (may be undef, signalling 'unknown')
40             #
41 12         31 $self->{$ATTR_totalTicks} = $totalTicks;
42            
43             # check for the presence of a 'begin' message...
44             #
45             #
46 12 50       88 if ($self->{$ATTR_beginMessage})
47             {
48 0         0 $self->_set_message($self->{$ATTR_beginMessage});
49 0         0 $self->{$ATTR_beginMessage} = undef;
50             }
51            
52             # conclude with a rendering of this state
53             #
54 12         1543 $self->render;
55            
56 12         30 return;
57             }
58            
59             sub end
60             {
61 12     12 1 304 my $self = shift;
62            
63             # it is allowed to call end *directly* if there's no work
64             # to be done at all - so we call begin if not already done
65             #
66 12         130 my $state = $self->_get_state;
67 12 50 33     99 $self->begin if $state == STATE_NEW || $state == STATE_PREPARING;
68            
69             # going to the end state from the active state
70             #
71 12         51 $self->__shiftState(STATE_ACTIVE, STATE_DONE);
72            
73             # automatically complete the ticks, if requested
74             #
75 12 50       133 $self->{$ATTR_ticks} = $self->{$ATTR_totalTicks} if ($self->{$ATTR_cfg}->get_completeAtEnd);
76            
77             # check for the presence of an 'end' message...
78             #
79             #
80 12 50       168 if ($self->{$ATTR_endMessage})
81             {
82 0         0 $self->_set_message($self->{$ATTR_endMessage});
83 0         0 $self->{$ATTR_endMessage} = undef;
84             }
85            
86             # ensure a final rendering is performed
87             #
88            
89 12         49 $self->render;
90            
91 12         1726 return;
92             }
93            
94             sub isCanceled
95             {
96 411     411 1 587 my $self = shift;
97            
98             # return the cancellation state
99             #
100 411         2457 return $self->{$ATTR_canceled};
101             }
102            
103             sub prepare
104             {
105 12     12 1 151 my $self = shift;
106            
107             # this is the first state transition after creation - signal prep stage
108             #
109 12         100 $self->__shiftState(STATE_NEW, STATE_PREPARING);
110            
111             # check for the presence of a 'prepare' message...
112             #
113             #
114 12 50       56 if ($self->{$ATTR_prepareMessage})
115             {
116 0         0 $self->_set_message($self->{$ATTR_prepareMessage});
117 0         0 $self->{$ATTR_prepareMessage} = undef;
118             }
119            
120             # render this state
121             #
122 12         67 $self->render;
123            
124 12         28 return;
125             }
126            
127             sub setCanceled
128             {
129 411     411 1 512 my $self = shift;
130            
131             # set the cancellation status
132             # only advisory - the client must call isCanceled actively
133             #
134 411 50       1022 $self->{$ATTR_canceled} = shift() ? 1 : 0;
135            
136 411         869 return;
137             }
138            
139             sub setMessage
140             {
141 200     200 0 320 my $self = shift;
142 200         230 my $msg = shift;
143 200   50     747 my $when = shift || SM_NOW;
144            
145 200 50       479 if ($when == SM_NOW)
    0          
    0          
    0          
    0          
146             {
147 200         692 $self->_set_message($msg);
148 200         525 $self->render;
149             }
150             elsif ($when == SM_PREPARE)
151             {
152 0         0 $self->{$ATTR_prepareMessage} = $msg;
153             }
154             elsif ($when == SM_BEGIN)
155             {
156 0         0 $self->{$ATTR_beginMessage} = $msg;
157             }
158             elsif ($when == SM_TICK)
159             {
160 0         0 $self->{$ATTR_tickMessage} = $msg;
161             }
162             elsif ($when == SM_END)
163             {
164 0         0 $self->{$ATTR_endMessage} = $msg;
165             }
166             else
167             {
168 0         0 X::ProgressMonitor::UnknownSetMessageFlag->throw("Unknown value: $when");
169             }
170            
171 200         615 return;
172             }
173            
174             sub tick
175             {
176 188     188 1 769 my $self = shift;
177 188         237 my $ticks = shift;
178            
179             # this method can be called during either prep or active states
180             #
181 188         596 $self->__assertAnyState([STATE_PREPARING, STATE_ACTIVE]);
182            
183             # STATE_PREPARING is implicitly 'unknown', thus any supplied ticks are
184             # ignored unless we're in the active state
185             #
186 188 100       691 if ($self->{$ATTR_state} == STATE_ACTIVE)
187             {
188             # ...but even in active state, there may have been 'unknown' indicated
189             #
190 108 50       297 if (defined($self->{$ATTR_totalTicks}))
191             {
192             # to avoid silly rounding errors at the end we round the tick number down by a small margin
193             #
194 108         346 my $m = $self->{$ATTR_multiplier};
195 108 50 33     745 $self->{$ATTR_ticks} += (int($ticks * $m + 1) / $m) if ($ticks && $ticks >= 0);
196            
197             # complain if we get too many ticks
198             #
199 108 50       403 X::ProgressMonitor::TooManyTicks->throw("$self->{$ATTR_ticks} exceeds $self->{$ATTR_totalTicks}")
200             if int($self->{$ATTR_ticks}) > int($self->{$ATTR_totalTicks});
201             }
202             else
203             {
204             # for 'unknown', we inc the ticks by one, the renderer may be interested in displaying the number of calls for example
205             #
206 0         0 $self->{$ATTR_ticks}++;
207             }
208             }
209            
210             # check for the presence of a 'tick' message...
211             #
212             #
213 188 50       494 if ($self->{$ATTR_tickMessage})
214             {
215 0         0 $self->_set_message($self->{$ATTR_tickMessage});
216 0         0 $self->{$ATTR_tickMessage} = undef;
217             }
218            
219             # render is always called!
220             #
221 188         541 $self->render;
222            
223 188         353 return;
224            
225             }
226            
227             ### protected
228            
229             sub _get_cfg
230             {
231 1445     1445   1776 my $self = shift;
232            
233 1445         12061 return $self->{$ATTR_cfg};
234             }
235            
236             sub _get_state
237             {
238 435     435   522 my $self = shift;
239            
240 435         1226 return $self->{$ATTR_state};
241             }
242            
243             sub _get_ticks
244             {
245 411     411   480 my $self = shift;
246            
247 411         1157 return $self->{$ATTR_ticks};
248             }
249            
250             sub _get_totalTicks
251             {
252 411     411   464 my $self = shift;
253            
254 411         1130 return $self->{$ATTR_totalTicks};
255             }
256            
257             sub _get_message
258             {
259 600     600   815 my $self = shift;
260            
261 600         2186 return $self->{$ATTR_message};
262             }
263            
264             sub _set_message
265             {
266 220     220   251 my $self = shift;
267 220         238 my $msg = shift;
268            
269 220         869 $self->{$ATTR_message} = $msg;
270             }
271            
272             # the protected ctor
273             #
274             sub _new
275             {
276 12     12   49 my $self = classes::new_only(shift);
277 12         66 my $cfg = shift;
278 12         25 my $cfgPkg = shift;
279            
280             # make sure we have a (populated) cfg object
281             #
282 12         43 $cfg = $self->{$ATTR_cfg} = ProgressMonitor::AbstractConfiguration::ensureCfgObject($cfg, $cfgPkg);
283            
284             # initialize the rest
285             #
286 12         52 $self->{$ATTR_state} = STATE_NEW;
287 12         37 $self->{$ATTR_canceled} = 0;
288 12         35 $self->{$ATTR_ticks} = 0;
289 12         53 $self->{$ATTR_totalTicks} = undef;
290 12         51 $self->{$ATTR_multiplier} = 0 + ("1" . "0" x $cfg->get_resolution);
291 12         158 $self->{$ATTR_message} = undef;
292 12         50 $self->{$ATTR_prepareMessage} = undef;
293 12         41 $self->{$ATTR_beginMessage} = undef;
294 12         36 $self->{$ATTR_tickMessage} = undef;
295 12         52 $self->{$ATTR_endMessage} = undef;
296            
297 12         42 return $self;
298             }
299            
300             ### private
301            
302             # assert that our state is the one expected
303             #
304             sub __assertState
305             {
306 36     36   57 my $self = shift;
307 36         51 my $state = shift;
308            
309 36         261 $self->__assertAnyState([$state]);
310            
311 36         77 return;
312             }
313            
314             # assert that our state is any one of the provided
315             #
316             sub __assertAnyState
317             {
318 224     224   295 my $self = shift;
319 224         244 my $states = shift;
320            
321 224         267 my $match = 0;
322 224 100       1999 $match += ($self->{$ATTR_state} == $_) ? 1 : 0 for (@$states);
323 224 50       684 X::ProgressMonitor::InvalidState->throw($self->{$ATTR_state}) unless $match;
324            
325 224         356 return;
326             }
327            
328             # move from one state to another
329             #
330             sub __shiftState
331             {
332 36     36   64 my $self = shift;
333 36         57 my $state = shift;
334 36         48 my $newState = shift;
335            
336 36         177 $self->__assertState($state);
337 36         84 $self->{$ATTR_state} = $newState;
338            
339 36         83 return;
340             }
341            
342             ###
343            
344             package ProgressMonitor::AbstractStatefulMonitorConfiguration;
345            
346 10     10   25207 use strict;
  10         20  
  10         399  
347 10     10   47 use warnings;
  10         21  
  10         958  
348            
349             # The configuration class for the above class
350             #
351             # resolution
352             # Allow the user to set the number of decimals when rounding.
353             # Unlikely to ever need changing...
354             #
355             use classes
356 10         50 extends => 'ProgressMonitor::AbstractConfiguration',
357             attrs => ['resolution', 'completeAtEnd'],
358 10     10   56 ;
  10         17  
359            
360             sub defaultAttributeValues
361             {
362 12     12   26 my $self = shift;
363            
364 12         23 return {%{$self->SUPER::defaultAttributeValues()}, resolution => 8, completeAtEnd => 1};
  12         82  
365             }
366            
367             sub checkAttributeValues
368             {
369 12     12   26 my $self = shift;
370            
371 12         96 $self->SUPER::checkAttributeValues;
372            
373 12 50       119 X::Usage->throw("resolution must be positive") if $self->get_resolution < 0;
374            
375 12         99 return;
376             }
377            
378             ############################
379            
380             =head1 NAME
381            
382             ProgressMonitor::AbstractStatefulMonitor - a reusable/abstract monitor implementation
383             keeping track of state
384            
385             =head1 SYNOPSIS
386            
387             ...
388             use classes
389             extends => 'ProgressMonitor::AbstractStatefulMonitor',
390             new => 'new',
391             ...
392             ;
393            
394             sub new
395             {
396             my $class = shift;
397             my $cfg = shift;
398            
399             my $self = $class->SUPER::_new($cfg, $CLASS);
400            
401             ...
402             }
403            
404             sub render
405             {
406             my $self = shift;
407            
408             ...
409             }
410            
411             =head1 DESCRIPTION
412            
413             This class implements the fully abstract ProgressMonitor interface and is what
414             generally should be used as a base. It deals with tracking the state changes
415             and cancellation and calls the subclass through 'render' at appropriate
416             times. It is strict and throws exceptions if misused.
417            
418             When extended it provides several accessors for 'protected' data, i.e. only for
419             the use of subclasses. These accessors are prefixed with '_'.
420            
421             Subclassing this normally entails only defining the render method.
422            
423             See L for the general description of a progress monitor behavior
424             with regard to state etc.
425            
426             Inherits from ProgressMonitor.
427            
428             =head1 METHODS
429            
430             =over 2
431            
432             =item begin( $totalTicks )
433            
434             Enters the active state from the preparing state, setting the total ticks that
435             should be reached, or use undef to indicate that the number of ticks is unknown.
436            
437             Throws X::InvalidState for an incorrect calling sequence.
438            
439             =item end
440            
441             Enters the done state from the active state, and the monitor can then not be used again.
442            
443             Throws X::InvalidState for an incorrect calling sequence.
444            
445             =item isCanceled
446            
447             Returns the cancellation flag.
448            
449             =item prepare
450            
451             Enters the preparing state from the new state, and the monitor can now be used
452             while the code is figuring out how many ticks it will need for the active state.
453            
454             Throws X::ProgressMonitor::InvalidState for an incorrect calling sequence.
455            
456             =item setCanceled( $boolean )
457            
458             Sets the cancellation flag.
459            
460             =item tick( $ticks )
461            
462             Advances the tick count towards the total tick count (depending on if its is in
463             the preparing state or if the total is unknown).
464            
465             Throws X::ProgressMonitor::TooManyTicks if the tick count exceeds the total.
466            
467             =back
468            
469             =head1 PROTECTED METHODS
470            
471             =over 2
472            
473             =item _new( $hashRef, $package )
474            
475             The constructor, needs to be called by subclasses.
476            
477             Configuration data:
478             resolution (default => 8)
479             Should not needed to be used. Makes sure to round the results down to the given size
480             decimals so as to avoid wacky floating point rounding errors when using inexact
481             floating point values in calculations (this happens when using subtasks).
482            
483             completeAtEnd (default => 1)
484             Will automatically add any ticks not performed when end is called.
485            
486             =item _get_cfg
487            
488             Returns the configuration object.
489            
490             =item _get_state
491            
492             Returns the current state value.
493            
494             =item _get_ticks
495            
496             Returns the current tick value.
497            
498             =item _get_totalTicks
499            
500             Returns the total tick value.
501            
502             =back
503            
504             =head1 AUTHOR
505            
506             Kenneth Olwing, C<< >>
507            
508             =head1 BUGS
509            
510             I wouldn't be surprised! If you can come up with a minimal test that shows the
511             problem I might be able to take a look. Even better, send me a patch.
512            
513             Please report any bugs or feature requests to
514             C, or through the web interface at
515             L.
516             I will be notified, and then you'll automatically be notified of progress on
517             your bug as I make changes.
518            
519             =head1 SUPPORT
520            
521             You can find general documentation for this module with the perldoc command:
522            
523             perldoc ProgressMonitor
524            
525             =head1 ACKNOWLEDGEMENTS
526            
527             Thanks to my family. I'm deeply grateful for you!
528            
529             =head1 COPYRIGHT & LICENSE
530            
531             Copyright 2006,2007 Kenneth Olwing, all rights reserved.
532            
533             This program is free software; you can redistribute it and/or modify it
534             under the same terms as Perl itself.
535            
536             =cut
537            
538             1; # End of ProgressMonitor::AbstractStatefulMonitor