File Coverage

blib/lib/File/AptFetch/Simple.pm
Criterion Covered Total %
statement 88 140 62.8
branch 31 86 36.0
condition 19 47 40.4
subroutine 13 14 92.8
pod 2 2 100.0
total 153 289 52.9


line stmt bran cond sub pod time code
1             # $Id: Simple.pm 510 2014-08-11 13:26:00Z whynot $
2             # Copyright 2014 Eric Pozharski
3             # GNU LGPLv3
4             # AS-IS, NO-WARRANTY, HOPE-TO-BE-USEFUL
5              
6 8     8   637000 use strict;
  8         94  
  8         233  
7 8     8   43 use warnings;
  8         20  
  8         360  
8              
9             package File::AptFetch::Simple;
10 8     8   912 use version 0.77; our $VERSION = version->declare( v0.1.7 );
  8         4130  
  8         62  
11 8     8   821 use base qw| File::AptFetch |;
  8         26  
  8         4479  
12              
13 8     8   60 use Carp;
  8         25  
  8         435  
14 8     8   49 use Cwd qw| abs_path |;
  8         20  
  8         392  
15 8     8   3677 use String::Truncate qw| elide |;
  8         140676  
  8         55  
16 8     8   1980 use List::Util qw| shuffle |;
  8         18  
  8         886  
17 8     8   4342 use POSIX qw| ceil |;
  8         52270  
  8         159  
18              
19             =head1 NAME
20              
21             File::AptFetch::Simple - convenience wrapper over File::AptFetch
22              
23             =head1 SYNOPSIS
24              
25             # TODO:
26              
27             =head1 DESCRIPTION
28              
29             When B was started it was believed that it must be bare-bone
30             simple.
31             Then RL came (refer to I for details).
32             Besides B needed loads of handling on user's side of code.
33             Thus B was born.
34              
35             The sole purpose of B is to reach unimaginable simplicity to limits
36             of being usable in one-liner (and beyond).
37             To further convinience there's only one method what is also a constructor.
38             That combine has name L>.
39             Just like in parent class.
40             Only --
41             it won't B unless all transfers are finished;
42             and it B object;
43             and it Bs on errors.
44              
45             Enjoy.
46              
47             =head1 API
48              
49             =over
50              
51             =cut
52              
53             =item B
54              
55             Has two modes: constructor and utility.
56             In either case a F::AF::S Bed object is returned.
57             Unless B B object reported any problem,
58             then Bs.
59             However, if that's a condition the parent doesn't care about
60             (as a matter of fact, B doesn't care that much about consistency of
61             messages and such)
62             but it looks terrible (and probably would lead to eventual timeout)
63             such conditions are Bed.
64              
65             =over
66              
67             =item Constructor Mode
68              
69             # complete CM -- cCM
70             $fafs = File::AptFetch::Simple->request( { %options }, @uris );
71             # simplified CM -- sCM
72             $fafs = File::AptFetch::Simple->request( $method, @uris );
73              
74             I<%options> are some parameters what will be somehow processed upon
75             construction and mostly saved for later use.
76             However, if defaults are ok then only one required parameter
77             (that is I<$options{method}>) can be passed as first scalar.
78             Known keys (and I<$method>) are described a bit later.
79              
80             I<@uris> is a list of scalars.
81             If empty, then constructor just blows through construction and returns
82             (it doesn't mean it's in vein, the requested method is initialized).
83             In detail description of I<%options> a bit later.
84              
85             =item Utility Mode
86              
87             # complete UM -- cUM
88             $fafs->request( { %options }, @uris );
89             # simplified UM -- sUM
90             $fafs->request( @uris );
91              
92             If first argument isn't a HASH,
93             then B believes that I<%options> is omitted.
94             However, there's a quirk.
95             Due implementation idiosyncrasy,
96             if first argument is FALSE it's ignored completely.
97             Consider those are reserved (even if they are not).
98             Are we cool now?
99              
100             If I<@uris> is empty then silently succeedes.
101             In detail description of I<@uris> a bit later.
102              
103             =item I<%options>
104              
105             Unless explicitly noted:
106             any option used in C sets defaults for this instance;
107             any option used in C sets for this invocation.
108              
109             =over
110              
111             =item I<$options{beat}>
112              
113             (optional, TRUE, I/I.)
114             That's the first progress reporting option --
115             this one is user-friendly.
116             L> has detailed description.
117             B<(bug)>
118             Default should depend on I being visible in terminal.
119              
120             =item I<$options{force_file}>
121              
122             (optional, FALSE, I/I.)
123             Disables C schema special handling (L> has more).
124             It's for setting in C and is retained forever,
125             in C silently ignored.
126             C<(caveat)> (probably bug)
127             Doesn't affect L> and L>.
128              
129             =item I<$options{location}>
130              
131             (optional, CWD.)
132             Sets dirname where acquired file will be placed.
133              
134             B<(caveat)>
135             When applied I<$options{location}> will be expanded to be absolute
136             (as required by APT method API).
137             However, that expansion is performed with each B
138             and, as mentioned above, transparently.
139             Thus if *you* set I<$options{location}> to non-absolute dirname,
140             than B once,
141             then *your* script changes CWD,
142             then B again,
143             then those Bs will put results in two different dirctories.
144              
145             B<(bug)>
146             Neither checks nor makes sure I<$options{location}> is anyway usable.
147              
148             B<(bug)>
149             Passively resists setting to value C<0>.
150              
151             =item I<$options{method}>
152              
153             =item I<$method>
154              
155             In C<[cs]CM> required, otherwise silently ignored.
156             If there's no such F installed Bs immeidately.
157             C is silengtly replaced with C;
158             C is passed through.
159              
160             B<(note)>
161             You should understand.
162             B is a B wrapper about B.
163             Second, B interfaces with APT methods what are all Debian.
164             It's reasonable to foresee that URIs will be constructed from those found in
165             F
166             (and, probably, nothing else).
167             But there's no URI of C type,
168             you should do that substitution yourself.
169             Else B could do it for you.
170             Seealso L>.
171              
172             =item I<$options{wink}>
173              
174             (optional, TRUE, I/I.)
175             That's the second progress reporting option --
176             this one is log-friendly.
177             Overwrites L>'s output (if any).
178             Tries to be terminal saving too.
179             B<(bug)>
180             Should actually detect if there's any terminal on I.
181              
182             Hints for filename and what APT method has said about it.
183             Not much.
184              
185             =back
186              
187             =item I<@uris>
188              
189             Requirements for I<%source> described in L|File::AptFetch/request()>
190             still apply.
191             Shortly:
192             full pathnames,
193             no schema,
194             one (local mehtods) or two (remote methods) leading slashes.
195             B<(bug)>
196             That's not convinient in any reasonable way.
197              
198             I<$target> (of underlying B of B) isn't required.
199             It's constructed from requested URI:
200             current value of I<$options{location}> will be concatenated with a basename of
201             currently processed I<$uris[]>.
202             The separator is slash.
203             (What else, it's *nix, for kernel's sake.)
204             B<(bug)>
205             As a matter of fact there's no way it can be anyhow affected.
206              
207             =back
208              
209             Diagnostics
210             (fatal conditions are specially marked)
211             (all errors that come from the parent are fatal by definition,
212             refer for B for details):
213              
214             =over
215              
216             =item {$options{method}} is required
217              
218             B<(fatal)> B<(cCM)>
219             There's I<%options> HASH in I<@_>.
220             Unfortunately I is FALSE.
221             No way to proceede with this.
222             B<(caveat)>
223             That hopes that there won't be a method named C<0>.
224             BTW parent will B on C<0> anyway.
225              
226             =item either {$method} of {%options} is required
227              
228             B<(fatal)> B<([cs]CM)>
229             During construction a method has to be initialized
230             what means it has to be picked up.
231             Invoking code must provide a method's name;
232             It didn't.
233             As a matter of fact I<@_> is totally empty.
234              
235             =item first must be either {$method} of {%options}
236              
237             B<(fatal)> B<([cs]CM)>
238             In this case I<@_> isn't empty,
239             but its leader is neither scalar ({$method}) nor HASH ({%options}).
240             Initialization code has no way to handle this.
241              
242             =item got (%s) for (%s) without [request]
243              
244             B<([cs]UM)>
245             Something wrong.
246             A message came in about I<$uri> (the latter C<%s>)
247             (it has I<$status> (the former C<%s>)).
248             It's surprise,
249             that I<$uri> was never requested.
250             B<(bug)>
251             Should dump the message.
252              
253             =item got (%s) without {URI:}
254              
255             B<([cs]UM)>
256             Something wrong.
257             A message just came in and it has no I<$uri>
258             (it has I<$status> (C<%s>)).
259             It's surprise,
260             I've never seen messages without that identification.
261             B<(bug)>
262             Should dump the damn message.
263              
264             =back
265              
266             =cut
267              
268             my %stat = ( mark => time, trace => [ ] );
269             sub request {
270 0     0 1 0 my( $class, $args, @subj ) = @_;
271 0         0 my $self;
272 0 0 0     0 if( $class->isa( q|File::AptFetch| ) && !ref $class ) {
273 0 0       0 defined $args or croak q|either {$method} or {%options} is required|;
274 0 0 0     0 !ref $args || q|HASH| eq ref $args or croak
275             q|first must be either {$method} or {%options}|;
276 0 0       0 $args = { method => $args } unless q|HASH| eq ref $args;
277 0 0       0 defined $args->{method} or croak q|{$options{method}} is required|;
278             $self->{force_file} = !!$args->{force_file} if
279 0 0       0 defined $args->{force_file};
280             my $method = $args->{method} eq q|file| && !$self->{force_file} ?
281 0 0 0     0 q|copy| : $args->{method};
282 0         0 $self = File::AptFetch->init( $method );
283 0 0       0 ref $self or croak $self;
284 0         0 bless $self, $class;
285 0 0       0 $self->{wink} = !!$args->{wink} if defined $args->{wink};
286 0 0       0 $self->{beat} = !!$args->{beat} if defined $args->{beat};
287             # FIXME:201405040354:whynot: Here F<0> has to be handled too.
288 0   0     0 $self->{location} = $args->{location} || '.' }
289             else {
290 0         0 $self = $class;
291 0 0 0     0 if( $args && q|HASH| ne ref $args ) {
    0          
292 0         0 unshift @subj, $args; $args = { } }
  0         0  
293             elsif( !$args ) {
294 0         0 $args = { } } }
295              
296             # FIXME:201404012258:whynot: Must handle F<0> specially.
297 0   0     0 my $loc = abs_path $args->{location} || $self->{location};
298             # TODO:201405020116:whynot: I is just behind the corner, you know.
299             # TODO:201405120124:whynot: Both should check for C<-t STDERR>.
300             my $wink =
301             defined $args->{wink} ? $args->{wink} :
302             defined $self->{wink} ? $self->{wink} :
303 0 0       0 File::AptFetch::ConfigData->config( q|wink| );
    0          
304             my $beat =
305             defined $args->{beat} ? $args->{beat} :
306             defined $self->{beat} ? $self->{beat} :
307 0 0       0 File::AptFetch::ConfigData->config( q|beat| );
    0          
308              
309             # XXX:201405112010:whynot: That's just going to blow in your face.
310 0 0       0 $self->{cheat_beat} = $beat ? "\r" : '';
311             my $rv = $self->SUPER::request( map {
312 0         0 my $src = $_;
  0         0  
313 0 0       0 $src =~ s{^file:}{copy:} unless $self->{force_file};
314 0         0 my $bnam = ( split m{/} )[-1];
315 0         0 qq|$loc/$bnam| => { uri => $src } } @subj );
316 0 0       0 $rv and croak $rv;
317              
318 0         0 while( %{$self->{trace}} ) {
  0         0  
319 0         0 $rv = $self->SUPER::gain;
320 0 0       0 $rv and croak $rv;
321 0         0 my $fn = $self->{message}{uri};
322 0 0 0     0 unless( $fn ) {
323             # TODO:201403302300:whynot: Not in test-suite.
324             # TODO:201403302300:whynot: Additional diagnostics is missing.
325 0         0 carp qq|got ($self->{status}) without {URI:}|; next }
  0         0  
326             elsif( !$self->{trace}{$fn} ) {
327             # TODO:201403221929:whynot: Not in test-suite.
328             carp qq|got ($self->{status}) for ($fn) without [request]| }
329 0         0 my $fnm = elide $fn, 25, { truncate => q|left| };
330 0 0       0 if( grep $self->{Status} == $_, qw| 201 400 401 402 403 |) {
    0          
331 0         0 delete $self->{trace}{$fn};
332 0 0       0 print STDERR "\n" if $wink }
333             elsif( $self->{Status} == 200 ) {}
334             # TODO:201406121825:whynot: Be more infomative, plz.
335             printf STDERR qq|%s(%s): (%s)\n|,
336 0 0       0 $self->{cheat_beat}, $fnm, $self->{status} if $wink }
337 0         0 delete $self->{cheat_beat};
338 0         0 $self }
339              
340             =item B<_gain_callback()>
341              
342             This finishes size sampling for L> (if applicable).
343             Also does a significant number of assertions (most probably useless).
344              
345             =cut
346              
347             sub _gain_callback {
348 8     8   91902 my $slf = shift;
349 8 100       64 defined $slf->{message}{uri} or return;
350 7         48 my $fn = $slf->{message}{uri};
351 7 100 100     133 $slf->{trace}{$fn} && defined $slf->{message}{size} or return;
352             # NOTE:201408010056:whynot: There're two points where I appears: C<200> and C<201>/C<400>/... Even if sizes mismatch it's too late to update.
353 5 100       59 $slf->{message}{size} =~ tr/0-9//c and return;
354             $slf->{trace}{$fn}{final_size} = $slf->{message}{size} unless defined
355 4 100       43 $slf->{trace}{$fn}{final_size};
356 4         18 $slf->{pending} = 0;
357 4   50     14 $slf->{pending} += $_ || 0 foreach map $_->{final_size},
358 4         63 values %{$slf->{trace}} }
359              
360             =item B<_read_callback()>
361              
362             This does all required sampling for L>.
363             Routine for L|File::AptFetch/_read> is provided by
364             L.
365              
366             =cut
367              
368             sub _read_callback {
369 47     47   197657 my $rec = shift;
370 47         862 my $rv = File::AptFetch::_read_callback $rec;
371 47 100       268 if( $rv ) {
372             my $diff = defined $rec->{size} && defined $rec->{back} ?
373 45 100 66     617 $rec->{size} - $rec->{back} : 0;
374 45 100       194 $stat{inc} += $diff if $diff > 0;
375 45         143 $stat{activity}++ }
376 47         205 $rv }
377              
378             =item B
379              
380             Service routine for L>.
381             It's public (in contrary with) because one day it will accept configuration
382             for oscillator.
383             Returns five bytes that somehow represent transfer went sleep.
384              
385             =cut
386              
387             my @void = qw| p e r l 5 |;
388 8     8 1 215 sub get_oscillator { join( '', @void = shuffle @void ) . q|X/s| }
389              
390             =item B<_select_callback()>
391              
392             This one does actual beat indicator,
393             unless forbidden (I of I<%opts> of L>).
394             Even if forbidden statistics is collected anyway.
395             Beat looks like this
396              
397             [24.00K/s] [17.60K/s 4.36M/s 3.13M/s] [ 4.17h 0.99m 1.37m]
398              
399             B<(bug)>
400             Beats are output completely terminal blind --
401             no cleanups, no width checks;
402             simple leading C<\r>.
403              
404             Beats are made with each I<$tick>.
405             The very last beat (before finish wink) is left visisble.
406              
407             In brackets are:
408              
409             =over
410              
411             =item *
412              
413             Speed over last tick.
414              
415             =item *
416              
417             SMA of speed calculated over 5sec, 1min, and 5min.
418             As long as a subset haven't been accumulated they won't be shown
419             (however, due timer early initialization 5sec SMA will probably appear
420             instantly).
421             Subsets are package wide -- probably B
422             (problem is sampling is made in L> what doesn't know about
423             object).
424             Subsets are kept between invocations;
425             what gives, different transports obviously perform differently,
426             transfers over different paths obviously perform differently --
427             that doesn't mix well.
428             But being an eye candy, well, it could stay this way forever.
429              
430             If transfer get stuck then speed is present with an oscillator --
431             you really don't want to know what it is, you gonna hate it.
432             B<(note)>
433             Now, when transfer speed goes to ground so does SMA
434             (that's what SMA is by design after all);
435             then, if transfer stalls long enough with probability ~50% SMA will hit
436             through C<0> and go negative
437             (rounding errors);
438             it was decided to present it with oscillator
439             (that one you already hate).
440             And when it stays positive it will be C<0.00b/s>.
441             (Those rounding errors are really small -- ~0.5e-8 small.)
442              
443             Speeds are based on 1024.
444             Format is C<%5.2f>.
445             With prefixes only -- no unit;
446             unless there should not be any prefix -- then lone C is used.
447             Supported prefixes are:
448             C, C, C, C, C, C, C, and C
449             (or C, C, C, C, C, C, C, and
450             C, to make IEC happy)
451             (hard to imagine speeds like that).
452              
453             =item *
454              
455             SMAs are used to estimate times to finish.
456             Because SMAs are running and run differently so estimations will be different
457             too
458             (it's fun to watch them).
459              
460             In some circumstances estimations can get really high or negative
461             (that's an example, there's no way it could be for real):
462              
463             [1MEGAy 99.99y 0.00s]
464              
465             Those are placeholders and should be ignored
466             (I just can't think a better way to handle those corner cases).
467             B<(bug)>
468             As of negative estimations I can't debug them right now --
469             ought to do my homework first.
470              
471             Estimations are expressed in up to 30sec, 30min, 6hour, or forever
472             (10hour is really forever if you think about it).
473              
474             =back
475              
476             B<(bug)>
477             Subset ranges should be configurable.
478              
479             B<(bug)>
480             Final performance isn't left visible for further eye candy.
481              
482             =cut
483              
484             my @marks = qw| b K M G T P E Z Y |;
485             my @indexes = ( 5, 60, 300 );
486              
487             sub _select_callback {
488 20     20   20311003 my $faf = shift;
489 20         167 my $sm = [ ];
490 20   50     204 my $mark = time - $stat{mark} || 1;
491             # NOTE:201407040056:whynot: Resources that were used to understand how it works:
492             # http://en.wikipedia.org/wiki/Simple_moving_average#Simple_moving_average
493             # http://cpansearch.perl.org/src/JETTERO/stockmonkey-2.9405/Business/SMA.pm
494 20 100 100     616 unless( exists $stat{inc} || $stat{activity} ) {
    100          
495 1         8 $sm->[0] = undef }
496 0 100       0 elsif( !$stat{inc} && $stat{activity} ) {
497 8         158 unshift @void, pop @void;
498 8         154 push @$sm, get_oscillator }
499             else {
500 11         74 my $fix = 0;
501 11         238 $fix++ until 100 > ceil $stat{inc}/$mark/2**($fix*10);
502             push @$sm, sprintf q|%5.2f%s/s|,
503 11         422 $stat{inc}/$mark/2**($fix*10), $marks[$fix] }
504 20   100     364 $stat{inc} ||= 0;
505 20         130 my $bit = $stat{inc}/$mark;
506 20         62 unshift @{$stat{trace}}, ( $bit ) x $mark;
  20         128  
507 20         83 push @$sm, [ ], [ ];
508 20   50     264 my $pending = $faf->{pending} || 0;
509 20   100     63 $pending -= $_ foreach map $_->{size} || 0, values %{$faf->{trace}};
  20         312  
510 20         145 for( my $ix = 0; $ix < @indexes; $ix++ ) {
511 60 100       106 if( @{$stat{trace}} < $indexes[$ix] ) { next }
  60         230  
  44         168  
512 16 100       108 unless( $stat{speeds}[$ix] ) {
513 1         9 $stat{speeds}[$ix] += $_ foreach
514 1         14 @{$stat{trace}}[0 .. $indexes[$ix]-1];
515 1         9 $stat{speeds}[$ix] /= $indexes[$ix] }
516             else {
517 15         108 $stat{speeds}[$ix] += $_/$indexes[$ix] foreach
518 15         98 @{$stat{trace}}[0 .. $mark-1],
519             map -$_,
520 15         126 @{$stat{trace}}[$indexes[$ix] .. $indexes[$ix]+$mark-1] }
521             # XXX:201406081721:whynot: And it really is. Not mine, that's rounding error.
522 16 50       92 if( $stat{speeds}[$ix] < 0 ) {
523 0         0 push @{$sm->[1]}, get_oscillator;
  0         0  
524 0         0 push @{$sm->[2]}, q|1MEGAy|;
  0         0  
525 0         0 next }
526 16         42 my $fix = 0;
527 16         165 $fix++ until 100 > ceil $stat{speeds}[$ix]/2**($fix * 10);
528 16         325 push @{$sm->[1]}, sprintf q|%5.2f%s/s|,
529 16         49 $stat{speeds}[$ix]/2**($fix*10), $marks[$fix];
530 16   50     116 my $lag = $pending/($stat{speeds}[$ix] || 1);
531 16         561 push @{$sm->[2]}, sprintf q|%5.2f%s|,
532 16 50 33     39 !$stat{speeds}[$ix] || $lag > 432000 ? ( 99.99, q|y| ) :
    50          
    50          
    50          
533             $lag > 43200 ? ( $lag/86400, q|d| ) :
534             $lag > 1800 ? ( $lag/3600, q|h| ) :
535             $lag > 30 ? ( $lag/60, q|m| ) :
536             ( $lag, q|s| ) }
537              
538 20         49 pop @{$stat{trace}} while @{$stat{trace}} > $indexes[2];
  20         84  
  0         0  
539              
540             printf STDERR qq|%s[%s] [%s] [%s] |, $faf->{cheat_beat},
541 19         142 $sm->[0], join( ' ', @{$sm->[1]} ), join( ' ', @{$sm->[2]} ) if
  19         1105  
542 20 100 66     414 $faf->{cheat_beat} && defined $sm->[0];
543 20         151 $stat{mark} = time;
544 20         412 delete @stat{qw| inc activity |} }
545              
546             File::AptFetch::set_callback
547             read => \&_read_callback,
548             select => \&_select_callback,
549             gain => \&_gain_callback;
550              
551             =back
552              
553             =head1 SEE ALSO
554              
555             L
556              
557             =head1 AUTHOR
558              
559             Eric Pozharski,
560              
561             =head1 COPYRIGHT & LICENSE
562              
563             Copyright 2014 by Eric Pozharski
564              
565             This library is free in sense: AS-IS, NO-WARANRTY, HOPE-TO-BE-USEFUL.
566             This library is released under GNU LGPLv3.
567              
568             =cut
569              
570             1