File Coverage

blib/lib/PAR/Repository/Client/HTTP.pm
Criterion Covered Total %
statement 18 109 16.5
branch 0 46 0.0
condition 0 11 0.0
subroutine 6 14 42.8
pod 2 2 100.0
total 26 182 14.2


line stmt bran cond sub pod time code
1             package PAR::Repository::Client::HTTP;
2              
3 9     9   250 use 5.006;
  9         32  
  9         487  
4 9     9   61 use strict;
  9         19  
  9         327  
5 9     9   52 use warnings;
  9         19  
  9         376  
6              
7 9     9   51 use vars qw/$ua/;
  9         13  
  9         1126  
8             require LWP::Simple;
9             LWP::Simple->import('$ua');
10              
11 9     9   60 use base 'PAR::Repository::Client';
  9         19  
  9         1054  
12              
13 9     9   51 use Carp qw/croak/;
  9         19  
  9         14400  
14              
15             our $VERSION = '0.24';
16              
17             =head1 NAME
18              
19             PAR::Repository::Client::HTTP - PAR repository via HTTP
20              
21             =head1 SYNOPSIS
22              
23             use PAR::Repository::Client;
24            
25             my $client = PAR::Repository::Client->new(
26             uri => 'http:///foo/repository',
27             http_timeout => 20, # but default is 180s
28             );
29              
30             =head1 DESCRIPTION
31              
32             This module implements repository accesses via HTTP.
33              
34             If you create a new L object and pass it
35             an uri parameter which starts with C or C,
36             it will create an object of this class. It inherits from
37             C.
38              
39             The repository is accessed using L.
40              
41             =head2 EXPORT
42              
43             None.
44              
45             =head1 METHODS
46              
47             Following is a list of class and instance methods.
48             (Instance methods until otherwise mentioned.)
49              
50             =cut
51              
52              
53             =head2 fetch_par
54              
55             Fetches a .par distribution from the repository and stores it
56             locally. Returns the name of the local file or the empty list on
57             failure.
58              
59             First argument must be the distribution name to fetch.
60              
61             =cut
62              
63             sub fetch_par {
64 0     0 1   my $self = shift;
65 0           $self->{error} = undef;
66 0           my $dist = shift;
67 0 0         return() if not defined $dist;
68              
69 0           my $url = $self->{uri};
70 0           $url =~ s/\/$//;
71              
72 0           my ($n, $v, $a, $p) = PAR::Dist::parse_dist_name($dist);
73 0           $url .= "/$a/$p/$n-$v-$a-$p.par";
74              
75 0           my $file = $self->_fetch_file($url);
76              
77 0 0         if (not defined $file) {
78 0           $self->{error} = "Could not fetch distribution from URI '$url'";
79 0           return();
80             }
81              
82 0           return $file;
83             }
84              
85              
86             {
87             my %escapes;
88             sub _fetch_file {
89 0     0     my $self = shift;
90 0           $self->{error} = undef;
91 0           my $file = shift;
92             #warn "FETCHING FILE: $file";
93              
94 0           my $cache_dir = $self->{cache_dir}; # used to be PAR_TEMP, but now configurable
95 0 0         %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255 unless %escapes;
  0            
96              
97 0           $file =~ m!/([^/]+)$!;
98 0 0         my $local_file = (defined($1) ? $1 : $file);
99 0           $local_file =~ s/([^\w\._])/$escapes{$1}/g;
100 0           $local_file = File::Spec->catfile( $self->{cache_dir}, $local_file );
101              
102 0           my $timeout = $self->{http_timeout};
103 0           my $old_timeout = $ua->timeout();
104 0 0         $ua->timeout($timeout) if defined $timeout;
105 0           my $rc = LWP::Simple::mirror( $file, $local_file );
106 0 0         $ua->timeout($old_timeout) if defined $timeout;
107 0 0 0       if (!LWP::Simple::is_success($rc) and not $rc == HTTP::Status::RC_NOT_MODIFIED()) {
108 0           $self->{error} = "Error $rc: " . LWP::Simple::status_message($rc) . " ($file)\n";
109 0           return();
110             }
111              
112 0 0         return $local_file if -f $local_file;
113 0           return();
114             }
115             }
116              
117              
118             sub _fetch_as_data {
119 0     0     my $self = shift;
120 0           $self->{error} = undef;
121 0           my $file = shift;
122             #warn "FETCHING DATA: $file";
123              
124 0           my $timeout = $self->{http_timeout};
125 0           my $old_timeout = $ua->timeout();
126 0 0         $ua->timeout($timeout) if defined $timeout;
127 0           my $data = LWP::Simple::get( $file );
128 0 0         $ua->timeout($old_timeout) if defined $timeout;
129              
130 0 0         return $data if defined $data;
131              
132 0           $self->{error} = "Could not get '$file' from repository";
133 0           return();
134             }
135              
136              
137             =head2 validate_repository
138              
139             Makes sure the repository is valid. Returns the empty list
140             if that is not so and a true value if the repository is valid.
141              
142             Checks that the repository version is compatible.
143              
144             The error message is available as C<$client->error()> on
145             failure.
146              
147             =cut
148              
149             sub validate_repository {
150 0     0 1   my $self = shift;
151 0           $self->{error} = undef;
152              
153 0           my $mod_db = $self->modules_dbm;
154 0 0         return() if not defined $mod_db;
155              
156 0 0         return() if not $self->validate_repository_version;
157              
158 0           return 1;
159             }
160              
161              
162             =head2 _repository_info
163              
164             Returns a YAML::Tiny object representing the repository meta
165             information.
166              
167             This is a private method.
168              
169             =cut
170              
171             sub _repository_info {
172 0     0     my $self = shift;
173 0           $self->{error} = undef;
174 0 0         return $self->{info} if defined $self->{info};
175              
176 0           my $url = $self->{uri};
177 0           $url =~ s/\/$//;
178              
179 0           my $file = $self->_fetch_file(
180             $url.'/'.PAR::Repository::Client::REPOSITORY_INFO_FILE()
181             );
182              
183 0 0         return() if not defined $file;
184              
185 0           my $yaml = YAML::Tiny->new->read($file);
186 0 0         if (not defined $yaml) {
187 0           $self->{error} = "Error reading repository info from YAML file.";
188 0           return();
189             }
190              
191             # workaround for possible YAML::Syck/YAML::Tiny bug
192             # This is not the right way to do it!
193 0 0         @$yaml = ($yaml->[1]) if @$yaml > 1;
194              
195 0           $self->{info} = $yaml;
196 0           return $yaml;
197             }
198              
199              
200             =head2 _fetch_dbm_file
201              
202             This is a private method.
203              
204             Fetches a dbm (index) file from the repository and
205             returns the name of the temporary local file or the
206             empty list on failure.
207              
208             An error message is available via the C
209             method in case of failure.
210              
211             =cut
212              
213             sub _fetch_dbm_file {
214 0     0     my $self = shift;
215 0           $self->{error} = undef;
216 0           my $file = shift;
217 0 0         return if not defined $file;
218              
219 0           my $url = $self->{uri};
220 0           $url =~ s/\/$//;
221              
222 0           my $local = $self->_fetch_file("$url/$file");
223 0 0 0       return() if not defined $local or not -f $local;
224              
225 0           return $local;
226             }
227              
228              
229             =head2 _dbm_checksums
230              
231             This is a private method.
232              
233             If the repository has a checksums file (new feature of
234             C 0.15), this method returns a hash
235             associating the DBM file names (e.g. C)
236             with their MD5 hashes (base 64).
237              
238             This method B queries the repository and never caches
239             the information locally. That's the whole point of having the
240             checksums.
241              
242             In case the repository does not have checksums, this method
243             returns the empty list, so check the return value!
244             The error message (see the C method) will be
245             I<"Repository does not support checksums"> in that case.
246              
247             =cut
248              
249             sub _dbm_checksums {
250 0     0     my $self = shift;
251 0           $self->{error} = undef;
252              
253 0           my $url = $self->{uri};
254 0           $url =~ s/\/$//;
255              
256             # if we're running on a "trust-the-checksums-for-this-long" basis...
257             # ... return if the timeout hasn't elapsed
258 0 0 0       if ($self->{checksums} and $self->{checksums_timeout}) {
259 0           my $time = time();
260 0 0         if ($time - $self->{last_checksums_refresh} < $self->{checksums_timeout}) {
261 0           return($self->{checksums});
262             }
263             }
264              
265 0           my $data = $self->_fetch_as_data(
266             $url.'/'.PAR::Repository::Client::DBM_CHECKSUMS_FILE()
267             );
268              
269 0 0         if (not defined $data) {
270 0           $self->{error} = "Repository does not support checksums";
271 0           return();
272             }
273              
274 0           return $self->_parse_dbm_checksums(\$data);
275             }
276              
277              
278             =head2 _init
279              
280             This private method is called by the C method of
281             L. It is used to initialize
282             the client object and C passes it a hash ref to
283             its arguments.
284              
285             Should return a true value on success.
286              
287             =cut
288              
289             sub _init {
290 0     0     my $self = shift;
291 0   0       my $args = shift || {};
292             # We implement additional object attributes here
293 0           $self->{http_timeout} = $args->{http_timeout};
294 0 0         $self->{http_timeout} = 180 if not defined $self->{http_timeout};
295              
296 0           return 1;
297             }
298              
299              
300              
301             1;
302             __END__