File Coverage

blib/lib/Data/Mirror.pm
Criterion Covered Total %
statement 103 112 91.9
branch 16 30 53.3
condition 2 6 33.3
subroutine 31 32 96.8
pod 7 10 70.0
total 159 190 83.6


line stmt bran cond sub pod time code
1             package Data::Mirror;
2             # ABSTRACT: a simple way to efficiently retrieve data from the World Wide Web.
3 1     1   306173 use Carp;
  1         9  
  1         119  
4 1     1   750 use Digest::SHA qw(sha256_hex);
  1         4063  
  1         128  
5 1     1   13 use Encode;
  1         4  
  1         145  
6 1     1   8 use File::Basename qw(basename);
  1         2  
  1         106  
7 1     1   883 use File::Slurp;
  1         21116  
  1         130  
8 1     1   14 use File::Spec;
  1         3  
  1         37  
9 1     1   683 use File::stat;
  1         10152  
  1         126  
10 1     1   21 use HTTP::Date;
  1         11  
  1         84  
11 1     1   1075 use JSON::XS;
  1         8141  
  1         119  
12 1     1   11 use List::Util qw(any max);
  1         2  
  1         77  
13 1     1   7 use LWP::UserAgent;
  1         2  
  1         38  
14 1     1   6 use POSIX qw(getlogin);
  1         3  
  1         11  
15 1     1   1387 use Text::CSV_XS qw(csv);
  1         19706  
  1         155  
16 1     1   1062 use XML::LibXML;
  1         50691  
  1         11  
17 1     1   1089 use YAML::XS;
  1         4651  
  1         90  
18 1     1   687 use IO::File;
  1         1472  
  1         174  
19 1     1   10 use base qw(Exporter);
  1         2  
  1         149  
20 1     1   1053 use open qw(:std :utf8);
  1         1798  
  1         8  
21 1     1   196 use strict;
  1         4  
  1         35  
22 1     1   652 use utf8;
  1         366  
  1         9  
23 1     1   86 use vars qw($VERSION %EXPORT_TAGS $TTL_SECONDS $UA $JSON $CSV);
  1         3  
  1         1535  
24              
25             $VERSION = '0.07';
26              
27             $EXPORT_TAGS{'all'} = [qw(
28             mirror_str
29             mirror_csv
30             mirror_fh
31             mirror_file
32             mirror_json
33             mirror_xml
34             mirror_yaml
35             )];
36              
37             Exporter::export_ok_tags('all');
38              
39              
40             #
41             # global TTL, used if the $ttl method argument to the mirror_* methods isn't
42             # specified
43             #
44             $TTL_SECONDS = 300;
45              
46              
47             $UA = LWP::UserAgent->new('agent' => sprintf(
48             '%s/%s, LWP::UserAgent %s, Perl %s',
49             __PACKAGE__, $VERSION || 'dev',
50             $LWP::UserAgent::VERSION,
51             $^V,
52             ));
53              
54              
55             $JSON = JSON::XS->new->utf8;
56              
57              
58             $CSV = Text::CSV_XS->new ({
59             'binary' => 1,
60             });
61              
62              
63             sub mirror_file {
64 6     6 1 1755 my ($url, $ttl) = @_;
65              
66 6 50       21 $ttl = $TTL_SECONDS unless (defined($ttl));
67              
68 6         19 my $file = filename($url);
69              
70 6 100       670 return $file unless (stale($url));
71              
72             #
73             # update the local file
74             #
75 4         29 my $result = $UA->mirror($url, $file);
76              
77 4 50   8   2334420 if (any { $_ == $result->code } (304, 200)) {
  8         75  
78             #
79             # if the response had the Expires: header, use that, otherwise use
80             # the later of the current mtime or now
81             #
82 4   33     63 my $expires = str2time($result->header('expires')) || max(stat($file)->mtime, time());
83              
84 4 50       738 utime($expires, $expires, $file) if (-e $file);
85             }
86              
87 4 50       109 carp($result->status_line) if ($result->code >= 400);
88              
89 4 50       665 if (-e $file) {
90 4         79 chmod(0600, $file);
91 4         200 return $file;
92             }
93              
94 0         0 return undef;
95             }
96              
97              
98             sub mirror_str {
99              
100 2     2 1 556340 my $file = mirror_file(@_);
101              
102 2 50       10 if ($file) {
103 2         16 return encode('UTF-8', read_file($file, 'binmode' => ':utf8'));
104             }
105              
106 0         0 return undef;
107             }
108              
109              
110             sub mirror_fh {
111              
112 2     2 1 9 my $file = mirror_file(@_);
113              
114 2 50       8 if ($file) {
115 2         22 my $fh = IO::File->new($file);
116              
117 2         228 $fh->binmode(':utf8');
118              
119 2         30 return $fh;
120             }
121              
122 0         0 return undef;
123             }
124              
125              
126             sub mirror_xml {
127              
128 0     0 1 0 my $file = mirror_file(@_);
129              
130 0 0       0 return XML::LibXML->load_xml('location' => $file) if ($file);
131              
132 0         0 return undef;
133             }
134              
135              
136             sub mirror_json {
137              
138 1     1 1 285 my $str = mirror_str(@_);
139              
140 1 50       663 return $JSON->decode($str) if ($str);
141              
142 0         0 return undef;
143             }
144              
145              
146             sub mirror_yaml {
147              
148 1     1 1 393 my $file = mirror_file(@_);
149              
150 1 50       29 return YAML::XS::LoadFile($file) if ($file);
151              
152 0         0 return undef;
153             }
154              
155              
156             sub mirror_csv {
157              
158 1     1 1 6 my $fh = mirror_fh(@_);
159              
160 1 50       7 if ($fh) {
161 1         3 my @rows;
162              
163 1         827 while (my $row = $CSV->getline($fh)) {
164 5         95 push(@rows, $row);
165             }
166              
167 1         10 $fh->close;
168              
169 1         31 return \@rows;
170             }
171              
172 0         0 return undef;
173             }
174              
175              
176             sub filename {
177 16     16 0 378 my $url = shift;
178              
179             #
180             # the local filename is based on the hash of the URL, salted by the user's
181             # login
182             #
183 16 50       814 return File::Spec->catfile(
184             File::Spec->tmpdir,
185             join('.', (
186             __PACKAGE__,
187             sha256_hex(
188             getlogin(),
189             ':',
190             ($url->isa('URI') ? $url->canonical->as_string : $url),
191             ),
192             'dat'
193             ))
194             );
195             }
196              
197              
198 2     2 0 1876 sub mirrored { -e filename(@_) }
199              
200              
201             sub stale {
202 7     7 0 20 my ($url, $ttl) = @_;
203              
204 7         16 my $file = filename($url);
205              
206 7 100       598 return 1 unless (-e $file);
207              
208 3 50 33     37 return 1 unless stat($file)->mtime > time() - ($ttl || $TTL_SECONDS);
209              
210 3         869 return undef;
211             }
212              
213              
214             1;
215              
216             __END__