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   10 use strict;
  2         2  
  2         59  
17 2     2   6 use warnings;
  2         2  
  2         1474  
18             our $VERSION = '0.013';
19              
20 2     2   9 use Doit::Log;
  2         2  
  2         1538  
21              
22 2     2 0 15 sub new { bless {}, shift }
23 2     2 0 5 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   39 my($self) = @_;
30 18 100       90 return $ua if $ua;
31 2         681 require LWP::UserAgent;
32 2         50511 $ua = LWP::UserAgent->new; # XXX options?
33             }
34             }
35              
36             sub lwp_mirror {
37 37     37 1 165 my($self, $url, $filename, %opts) = @_;
38 37 100       104 if (!defined $url) { error "url is mandatory" }
  1         3  
39 36 100       73 if (!defined $filename) { error "filename is mandatory" }
  1         4  
40 35   100     134 my $refresh = delete $opts{refresh} || 'always';
41 35 100       264 if (UNIVERSAL::isa($refresh, 'ARRAY')) {
    100          
42 18 100       67 if ($refresh->[0] ne 'digest') {
43 1         3 error "refresh in ARRAY form expects 'digest' as first element";
44             }
45 17 100 100     129 if (@$refresh < 2 || @$refresh > 3) {
46 2         4 error "refresh in ARRAY form expects two elements (string 'digest', the digest value and optionally digest type)";
47             }
48 1         3 } elsif ($refresh !~ m{^(always|never|unconditionally)$}) { error "refresh may be 'always', 'never' or 'unconditionally'" }
49 31         48 my $debug = delete $opts{debug};
50 31         52 my $ua = delete $opts{ua};
51 31 100       73 error "Unhandled options: " . join(" ", %opts) if %opts;
52              
53 30 100       729 if (-e $filename) {
54 21 100 66     251 if ($refresh eq 'never') {
    100          
55 1 50       6 if ($debug) {
56 0         0 info "$url -> $filename already exists, do not refresh";
57             }
58 1         16 return 0;
59             } elsif (UNIVERSAL::isa($refresh, 'ARRAY') && $refresh->[0] eq 'digest') {
60 10         25 my $digest = $refresh->[1];
61 10   100     59 my $type = $refresh->[2] || 'MD5';
62 10 100       70 if ($self->file_digest_matches($filename, $digest, $type)) {
63 5 50       80 if ($debug) {
64 0         0 info "$url -> $filename already exists and $type digest is as expected, do not refresh";
65             }
66 5         43 return 0;
67             } else {
68 5         23 $refresh = 'unconditionally';
69             }
70             }
71             }
72              
73 24   66     108 $ua ||= _get_cached_ua;
74              
75 24 50       3709 if ($self->is_dry_run) {
76 0         0 info "mirror $url -> $filename (dry-run)";
77             } else {
78 24         199 info "mirror $url -> $filename";
79 24         3813 my $resp;
80 24 100       83 if ($refresh eq 'unconditionally') {
81             $self->file_atomic_write
82             ($filename, sub {
83 7     7   16 my $fh = shift;
84 7 100       68 if (ref $ua eq 'HTTP::Tiny') {
85             $resp = $ua->get($url, {
86             data_callback => sub {
87 1         109321 my($data) = @_;
88 1         24 print $fh $data;
89             },
90 1         62 });
91             } else {
92             $resp = $ua->get($url,
93             ':content_cb' => sub {
94 6         146362 my($chunk) = @_;
95 6         51 print $fh $chunk;
96             },
97 6         88 );
98             }
99 7         97 });
100             } else {
101 17         129 $resp = $ua->mirror($url, $filename);
102             }
103 24 100       1734859 if (ref $ua eq 'HTTP::Tiny') {
104 6 100       26 if ($debug) {
105 2         717 require Data::Dumper;
106 2         6154 info "Response: " . Data::Dumper->new([$resp],[qw()])->Indent(1)->Useqq(1)->Sortkeys(1)->Terse(1)->Dump;
107             }
108 6 100       205 if (!$resp->{success}) {
    100          
109 2         5 my $msg = "mirroring failed: $resp->{status} $resp->{reason}";
110 2 100       6 if ($resp->{status} == 599) {
111 1         3 $msg .= ": $resp->{content}";
112             }
113 2         7 error $msg;
114             } elsif ($resp->{status} == 304) {
115 1         18 return 0;
116             } else {
117 3         38 return 1;
118             }
119             } else {
120 18 100       66 if ($debug) {
121 3         21 info "Response: " . $resp->as_string;
122             }
123 18 100       959 if ($resp->code == 304) {
    100          
    50          
124 2         63 return 0;
125             } elsif (!$resp->is_success) {
126 3         90 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         1084 return 1;
131             }
132             }
133             }
134             }
135              
136             1;
137              
138             __END__