File Coverage

blib/lib/Doit/Lwp.pm
Criterion Covered Total %
statement 73 77 94.8
branch 48 52 92.3
condition 11 13 84.6
subroutine 9 9 100.0
pod 1 4 25.0
total 142 155 91.6


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # Author: Slaven Rezic
5             #
6             # Copyright (C) 2017,2018,2023 Slaven Rezic. All rights reserved.
7             # This package is free software; you can redistribute it and/or
8             # modify it under the same terms as Perl itself.
9             #
10             # Mail: slaven@rezic.de
11             # WWW: http://www.rezic.de/eserte/
12             #
13              
14             package Doit::Lwp; # Convention: all commands here should be prefixed with 'lwp_'
15              
16 2     2   11 use strict;
  2         4  
  2         65  
17 2     2   7 use warnings;
  2         3  
  2         100  
18             our $VERSION = '0.013';
19              
20 2     2   8 use Doit::Log;
  2         4  
  2         1880  
21              
22 2     2 0 20 sub new { bless {}, shift }
23 2     2 0 8 sub functions { qw(lwp_mirror) }
24 2     2 0 4 sub add_components { qw(file) }
25              
26             {
27             my $ua; # XXX cache in object?
28             sub _get_cached_ua {
29 18     18   52 my($self) = @_;
30 18 100       149 return $ua if $ua;
31 2         720 require LWP::UserAgent;
32 2         58944 $ua = LWP::UserAgent->new; # XXX options?
33             }
34             }
35              
36             sub lwp_mirror {
37 37     37 1 239 my($self, $url, $filename, %opts) = @_;
38 37 100       126 if (!defined $url) { error "url is mandatory" }
  1         4  
39 36 100       114 if (!defined $filename) { error "filename is mandatory" }
  1         4  
40 35   100     159 my $refresh = delete $opts{refresh} || 'always';
41 35 100       365 if (UNIVERSAL::isa($refresh, 'ARRAY')) {
    100          
42 18 100       81 if ($refresh->[0] ne 'digest') {
43 1         3 error "refresh in ARRAY form expects 'digest' as first element";
44             }
45 17 100 100     140 if (@$refresh < 2 || @$refresh > 3) {
46 2         7 error "refresh in ARRAY form expects two elements (string 'digest', the digest value and optionally digest type)";
47             }
48 1         5 } elsif ($refresh !~ m{^(always|never|unconditionally)$}) { error "refresh may be 'always', 'never' or 'unconditionally'" }
49 31         137 my $debug = delete $opts{debug};
50 31         64 my $ua = delete $opts{ua};
51 31 100       100 error "Unhandled options: " . join(" ", %opts) if %opts;
52              
53 30 100       883 if (-e $filename) {
54 21 100 66     328 if ($refresh eq 'never') {
    100          
55 1 50       10 if ($debug) {
56 0         0 info "$url -> $filename already exists, do not refresh";
57             }
58 1         42 return 0;
59             } elsif (UNIVERSAL::isa($refresh, 'ARRAY') && $refresh->[0] eq 'digest') {
60 10         37 my $digest = $refresh->[1];
61 10   100     93 my $type = $refresh->[2] || 'MD5';
62 10 100       95 if ($self->file_digest_matches($filename, $digest, $type)) {
63 5 50       64 if ($debug) {
64 0         0 info "$url -> $filename already exists and $type digest is as expected, do not refresh";
65             }
66 5         77 return 0;
67             } else {
68 5         29 $refresh = 'unconditionally';
69             }
70             }
71             }
72              
73 24   66     152 $ua ||= _get_cached_ua;
74              
75 24 50       5654 if ($self->is_dry_run) {
76 0         0 info "mirror $url -> $filename (dry-run)";
77             } else {
78 24         172 info "mirror $url -> $filename";
79 24         3310 my $resp;
80 24 100       158 if ($refresh eq 'unconditionally') {
81             $self->file_atomic_write
82             ($filename, sub {
83 7     7   29 my $fh = shift;
84 7 100       50 if (ref $ua eq 'HTTP::Tiny') {
85             $resp = $ua->get($url, {
86             data_callback => sub {
87 1         111987 my($data) = @_;
88 1         20 print $fh $data;
89             },
90 1         57 });
91             } else {
92             $resp = $ua->get($url,
93             ':content_cb' => sub {
94 6         152563 my($chunk) = @_;
95 6         63 print $fh $chunk;
96             },
97 6         129 );
98             }
99 7         122 });
100             } else {
101 17         112 $resp = $ua->mirror($url, $filename);
102             }
103 24 100       1783132 if (ref $ua eq 'HTTP::Tiny') {
104 6 100       72 if ($debug) {
105 2         817 require Data::Dumper;
106 2         14271 info "Response: " . Data::Dumper->new([$resp],[qw()])->Indent(1)->Useqq(1)->Sortkeys(1)->Terse(1)->Dump;
107             }
108 6 100       211 if (!$resp->{success}) {
    100          
109 2         5 my $msg = "mirroring failed: $resp->{status} $resp->{reason}";
110 2 100       8 if ($resp->{status} == 599) {
111 1         2 $msg .= ": $resp->{content}";
112             }
113 2         7 error $msg;
114             } elsif ($resp->{status} == 304) {
115 1         16 return 0;
116             } else {
117 3         58 return 1;
118             }
119             } else {
120 18 100       75 if ($debug) {
121 3         30 info "Response: " . $resp->as_string;
122             }
123 18 100       601 if ($resp->code == 304) {
    100          
    50          
124 2         68 return 0;
125             } elsif (!$resp->is_success) {
126 3         126 error "mirroring failed: " . $resp->status_line;
127             } elsif ($resp->header('X-Died')) {
128 0         0 error "mirroring failed: " . $resp->header('X-Died');
129             } else {
130 13         1446 return 1;
131             }
132             }
133             }
134             }
135              
136             1;
137              
138             __END__