File Coverage

blib/lib/File/AptFetch/Simple.pm
Criterion Covered Total %
statement 131 140 93.5
branch 71 86 82.5
condition 42 47 89.3
subroutine 14 14 100.0
pod 2 2 100.0
total 260 289 89.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 27     27   4377529 use strict;
  27         78  
  27         1090  
7 27     27   164 use warnings;
  27         60  
  27         1354  
8              
9             package File::AptFetch::Simple;
10 27     27   1565 use version 0.77; our $VERSION = version->declare( v0.1.7 );
  27         5319  
  27         210  
11 27     27   2421 use base qw| File::AptFetch |;
  27         58  
  27         18344  
12              
13 27     27   330 use Carp;
  27         52  
  27         2233  
14 27     27   168 use Cwd qw| abs_path |;
  27         62  
  27         1363  
15 27     27   199496 use String::Truncate qw| elide |;
  27         672221  
  27         203  
16 27     27   6218 use List::Util qw| shuffle |;
  27         61  
  27         3570  
17 27     27   26820 use POSIX qw| ceil |;
  27         206324  
  27         215  
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 162     162 1 27318985 my( $class, $args, @subj ) = @_;
271 162         1443 my $self;
272 162 100 66     5110 if( $class->isa( q|File::AptFetch| ) && !ref $class ) {
273 65 100       3926 defined $args or croak q|either {$method} or {%options} is required|;
274 59 100 100     2696 !ref $args || q|HASH| eq ref $args or croak
275             q|first must be either {$method} or {%options}|;
276 56 100       538 $args = { method => $args } unless q|HASH| eq ref $args;
277 56 100       1536 defined $args->{method} or croak q|{$options{method}} is required|;
278 53 100       430 $self->{force_file} = !!$args->{force_file} if
279             defined $args->{force_file};
280 53 100 100     754 my $method = $args->{method} eq q|file| && !$self->{force_file} ?
281             q|copy| : $args->{method};
282 53         1292 $self = File::AptFetch->init( $method );
283 34 100       1058 ref $self or croak $self;
284 33         478 bless $self, $class;
285 33 50       150 $self->{wink} = !!$args->{wink} if defined $args->{wink};
286 33 50       148 $self->{beat} = !!$args->{beat} if defined $args->{beat};
287             # FIXME:201405040354:whynot: Here F<0> has to be handled too.
288 33   100     770 $self->{location} = $args->{location} || '.' }
289             else {
290 97         3859 $self = $class;
291 97 100 100     2094 if( $args && q|HASH| ne ref $args ) {
    100          
292 20         235 unshift @subj, $args; $args = { } }
  20         118  
293             elsif( !$args ) {
294 7         39 $args = { } } }
295              
296             # FIXME:201404012258:whynot: Must handle F<0> specially.
297 130   66     10232 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 130 50       5280 my $wink =
    100          
301             defined $args->{wink} ? $args->{wink} :
302             defined $self->{wink} ? $self->{wink} :
303             File::AptFetch::ConfigData->config( q|wink| );
304 130 50       1628 my $beat =
    50          
305             defined $args->{beat} ? $args->{beat} :
306             defined $self->{beat} ? $self->{beat} :
307             File::AptFetch::ConfigData->config( q|beat| );
308              
309             # XXX:201405112010:whynot: That's just going to blow in your face.
310 130 100       1941 $self->{cheat_beat} = $beat ? "\r" : '';
311 118         474 my $rv = $self->SUPER::request( map {
312 130         1006 my $src = $_;
313 118 50       861 $src =~ s{^file:}{copy:} unless $self->{force_file};
314 118         975 my $bnam = ( split m{/} )[-1];
315 118         3500 qq|$loc/$bnam| => { uri => $src } } @subj );
316 130 50       933 $rv and croak $rv;
317              
318 130         304 while( %{$self->{trace}} ) {
  318         1540  
319 188         1434 $rv = $self->SUPER::gain;
320 188 50       476 $rv and croak $rv;
321 188         487 my $fn = $self->{message}{uri};
322 188 50       920 unless( $fn ) {
    50          
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 0         0 carp qq|got ($self->{status}) for ($fn) without [request]| }
329 188         3612 my $fnm = elide $fn, 25, { truncate => q|left| };
330 188 100       27581 if( grep $self->{Status} == $_, qw| 201 400 401 402 403 |) {
    50          
331 118         960 delete $self->{trace}{$fn};
332 118 100       420 print STDERR "\n" if $wink }
333             elsif( $self->{Status} == 200 ) {}
334             # TODO:201406121825:whynot: Be more infomative, plz.
335 188 100       29619 printf STDERR qq|%s(%s): (%s)\n|,
336             $self->{cheat_beat}, $fnm, $self->{status} if $wink }
337 130         413 delete $self->{cheat_beat};
338 130         20455 $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 196     196   2261566 my $slf = shift;
349 196 100       2346 defined $slf->{message}{uri} or return;
350 195         467 my $fn = $slf->{message}{uri};
351 195 100 100     1877 $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 192 100       688 $slf->{message}{size} =~ tr/0-9//c and return;
354 191 100       1321 $slf->{trace}{$fn}{final_size} = $slf->{message}{size} unless defined
355             $slf->{trace}{$fn}{final_size};
356 191         862 $slf->{pending} = 0;
357 191   100     392 $slf->{pending} += $_ || 0 foreach map $_->{final_size},
  191         2831  
358             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   1918302 my $rec = shift;
370 47         947 my $rv = File::AptFetch::_read_callback $rec;
371 47 100       172 if( $rv ) {
372 45 100 66     511 my $diff = defined $rec->{size} && defined $rec->{back} ?
373             $rec->{size} - $rec->{back} : 0;
374 45 100       180 $stat{inc} += $diff if $diff > 0;
375 45         123 $stat{activity}++ }
376 47         199 $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 238 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 277     277   21987141 my $faf = shift;
489 277         992 my $sm = [ ];
490 277   100     4795 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 277 100 100     20719 unless( exists $stat{inc} || $stat{activity} ) {
    100 66        
495 258         1378 $sm->[0] = undef }
496             elsif( !$stat{inc} && $stat{activity} ) {
497 8         188 unshift @void, pop @void;
498 8         92 push @$sm, get_oscillator }
499             else {
500 11         45 my $fix = 0;
501 11         203 $fix++ until 100 > ceil $stat{inc}/$mark/2**($fix*10);
502 11         706 push @$sm, sprintf q|%5.2f%s/s|,
503             $stat{inc}/$mark/2**($fix*10), $marks[$fix] }
504 277   100     6868 $stat{inc} ||= 0;
505 277         1233 my $bit = $stat{inc}/$mark;
506 277         512 unshift @{$stat{trace}}, ( $bit ) x $mark;
  277         2006  
507 277         857 push @$sm, [ ], [ ];
508 277   100     2318 my $pending = $faf->{pending} || 0;
509 277   100     713 $pending -= $_ foreach map $_->{size} || 0, values %{$faf->{trace}};
  277         2943  
510 277         1641 for( my $ix = 0; $ix < @indexes; $ix++ ) {
511 831 100       1086 if( @{$stat{trace}} < $indexes[$ix] ) { next }
  831         3040  
  623         2102  
512 208 100       1239 unless( $stat{speeds}[$ix] ) {
513 193         837 $stat{speeds}[$ix] += $_ foreach
  193         1891  
514             @{$stat{trace}}[0 .. $indexes[$ix]-1];
515 193         698 $stat{speeds}[$ix] /= $indexes[$ix] }
516             else {
517 15         73 $stat{speeds}[$ix] += $_/$indexes[$ix] foreach
  15         121  
518 15         185 @{$stat{trace}}[0 .. $mark-1],
519             map -$_,
520             @{$stat{trace}}[$indexes[$ix] .. $indexes[$ix]+$mark-1] }
521             # XXX:201406081721:whynot: And it really is. Not mine, that's rounding error.
522 208 50       1007 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 208         486 my $fix = 0;
527 208         4688 $fix++ until 100 > ceil $stat{speeds}[$ix]/2**($fix * 10);
528 208         431 push @{$sm->[1]}, sprintf q|%5.2f%s/s|,
  208         9218  
529             $stat{speeds}[$ix]/2**($fix*10), $marks[$fix];
530 208   100     2147 my $lag = $pending/($stat{speeds}[$ix] || 1);
531 208 50 66     403 push @{$sm->[2]}, sprintf q|%5.2f%s|,
  208 50       3621  
    50          
    100          
532             !$stat{speeds}[$ix] || $lag > 432000 ? ( 99.99, q|y| ) :
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 277         517 pop @{$stat{trace}} while @{$stat{trace}} > $indexes[2];
  277         1693  
  0         0  
539              
540 19         74 printf STDERR qq|%s[%s] [%s] [%s] |, $faf->{cheat_beat},
541 277 100 100     2158 $sm->[0], join( ' ', @{$sm->[1]} ), join( ' ', @{$sm->[2]} ) if
  19         1287  
542             $faf->{cheat_beat} && defined $sm->[0];
543 277         941 $stat{mark} = time;
544 277         2807 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