File Coverage

blib/lib/HTTP/Tiny/Bandwidth.pm
Criterion Covered Total %
statement 21 109 19.2
branch 0 48 0.0
condition 0 31 0.0
subroutine 7 15 46.6
pod 2 2 100.0
total 30 205 14.6


line stmt bran cond sub pod time code
1             package HTTP::Tiny::Bandwidth;
2 1     1   40905 use strict;
  1         2  
  1         26  
3 1     1   6 use warnings;
  1         2  
  1         25  
4 1     1   3038 use Time::HiRes ();
  1         1612  
  1         24  
5 1     1   6 use Carp ();
  1         2  
  1         33  
6              
7             our $VERSION = '0.01';
8 1     1   706 use parent 'HTTP::Tiny';
  1         286  
  1         7  
9              
10             our $CHECK_INTERVAL_SECOND = 0.001;
11              
12 1     1   58174 use constant BUFSIZE => 32768;
  1         2  
  1         92  
13 1     1   4 use constant DEBUG => $ENV{HTTP_TINY_BANDWIDTH_DEBUG};
  1         2  
  1         1135  
14              
15             sub _download_data_callback {
16 0     0     my ($self, $fh, $limit_bps) = @_;
17 0 0         if (!$limit_bps) {
18 0     0     return sub { print {$fh} $_[0] };
  0            
  0            
19             }
20 0           my $previous;
21             sub {
22 0     0     print {$fh} $_[0];
  0            
23 0   0       $previous ||= [ [Time::HiRes::gettimeofday], 0 ];
24 0           my $elapsed = Time::HiRes::tv_interval($previous->[0]);
25 0 0         return 1 if $elapsed < $CHECK_INTERVAL_SECOND;
26 0           my $sleep = 8 * (tell($fh) - $previous->[1]) / $limit_bps - $elapsed;
27 0 0         if ($sleep > 0) {
28 0           DEBUG and warn "-> (download) sleep $sleep\n";
29 0           select undef, undef, undef, $sleep;
30 0           $previous->[0] = [Time::HiRes::gettimeofday];
31 0           $previous->[1] = tell($fh);
32             }
33 0           };
34             }
35              
36             sub _upload_data_callback {
37 0     0     my ($self, $fh, $limit_bps) = @_;
38 0 0         if (!$limit_bps) {
39             return sub {
40 0     0     my $len = read $fh, my $buf, BUFSIZE;
41 0 0         if (!defined $len) {
    0          
42 0           die "file read error: $!";
43             } elsif ($len == 0) {
44 0           undef; # EOF, finish
45             } else {
46 0           $buf;
47             }
48 0           };
49             }
50              
51 0           my $previous;
52             sub {
53 0   0 0     $previous ||= [ [Time::HiRes::gettimeofday], 0 ];
54 0           my $len = read $fh, my $buf, BUFSIZE;
55 0 0         if (!defined $len) {
    0          
56 0           die "file read error: $!";
57             } elsif ($len == 0) {
58 0           undef; # EOF, finish
59             } else {
60 0           $previous->[1] += $len;
61 0           my $elapsed = Time::HiRes::tv_interval($previous->[0]);
62 0 0         if ($elapsed > $CHECK_INTERVAL_SECOND) {
63 0           my $sleep = 8 * $previous->[1] / $limit_bps - $elapsed;
64 0 0         if ($sleep > 0) {
65 0           DEBUG and warn "-> (upload) sleep $sleep\n";
66 0           select undef, undef, undef, $sleep;
67 0           $previous->[0] = [Time::HiRes::gettimeofday];
68 0           $previous->[1] = 0;
69             }
70             }
71 0           $buf;
72             }
73             }
74 0           }
75              
76             sub request {
77 0     0 1   my ($self, $method, $url, $args) = @_;
78 0   0       $args ||= +{};
79 0 0 0       if ($args->{content_file} || $args->{content_fh}) {
80 0           my $fh = $args->{content_fh};
81 0 0         unless ($fh) {
82 0           my $file = $args->{content_file};
83 0 0         open $fh, "<", $file or Carp::croak("Error: Could not open $file: $!");
84 0           binmode $fh;
85             }
86 0           my $upload_limit_bps = $args->{upload_limit_bps};
87 0           $args->{content} = $self->_upload_data_callback($fh, $upload_limit_bps);
88 0   0       ($args->{headers} ||= +{})->{'content-length'} = -s $fh;
89             # XXX set content-type via Plack::MIME?
90             }
91              
92 0           my $set_bandwidth_data_callback;
93 0           my ($download_content, $download_content_fh);
94 0 0         if (my $download_limit_bps = $args->{download_limit_bps}) {
95 0 0         if ($args->{data_callback}) {
96 0           Carp::croak("Error: Can not specify both download_limit_bps "
97             . "and data_callback at the same time");
98             }
99 0           open $download_content_fh, ">", \$download_content;
100             $args->{data_callback}
101 0           = $self->_download_data_callback($download_content_fh, $download_limit_bps);
102 0           $set_bandwidth_data_callback++;
103             }
104              
105 0           my $res = $self->SUPER::request($method, $url, $args);
106 0 0         if ($set_bandwidth_data_callback) {
107 0           close $download_content_fh;
108 0 0 0       if (length($res->{content} || '') == 0) {
109 0           $res->{content} = $download_content;
110             }
111             }
112 0           $res;
113             }
114              
115             # copy from HTTP::Tiny
116             sub mirror {
117 0     0 1   my ($self, $url, $file, $args) = @_;
118 0 0 0       @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
      0        
119             or Carp::croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/ . "\n");
120 0 0 0       if ( -e $file and my $mtime = (stat($file))[9] ) {
121 0   0       $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime);
122             }
123 0           my $tempfile = $file . int(rand(2**31));
124              
125 0           require Fcntl;
126 0 0         sysopen my $fh, $tempfile, Fcntl::O_CREAT()|Fcntl::O_EXCL()|Fcntl::O_WRONLY()
127             or Carp::croak(qq/Error: Could not create temporary file $tempfile for downloading: $!\n/);
128 0           binmode $fh;
129 0           $args->{data_callback} = $self->_download_data_callback($fh, $args->{download_limit_bps});
130 0           local $args->{download_limit_bps}; # so that request method does not set bandwith data callback
131 0           my $response = $self->request('GET', $url, $args);
132 0 0         close $fh
133             or Carp::croak(qq/Error: Caught error closing temporary file $tempfile: $!\n/);
134              
135 0 0         if ( $response->{success} ) {
136 0 0         rename $tempfile, $file
137             or Carp::croak(qq/Error replacing $file with $tempfile: $!\n/);
138 0           my $lm = $response->{headers}{'last-modified'};
139 0 0 0       if ( $lm and my $mtime = $self->_parse_http_date($lm) ) {
140 0           utime $mtime, $mtime, $file;
141             }
142             }
143 0   0       $response->{success} ||= $response->{status} eq '304';
144 0           unlink $tempfile;
145 0           return $response;
146             }
147              
148             1;
149             __END__