File Coverage

blib/lib/Mirror/JSON.pm
Criterion Covered Total %
statement 90 138 65.2
branch 14 46 30.4
condition 2 11 18.1
subroutine 25 30 83.3
pod 0 15 0.0
total 131 240 54.5


line stmt bran cond sub pod time code
1             package Mirror::JSON;
2              
3 2     2   25175 use 5.005;
  2         7  
  2         74  
4 2     2   10 use strict;
  2         3  
  2         60  
5 2     2   9 use Carp qw{ croak };
  2         12  
  2         118  
6 2     2   1769 use Params::Util qw{ _STRING _POSINT _ARRAY0 _INSTANCE };
  2         8953  
  2         159  
7 2     2   1147 use JSON ();
  2         13193  
  2         42  
8 2     2   8104 use URI ();
  2         12582  
  2         45  
9 2     2   2339 use Time::HiRes ();
  2         4707  
  2         63  
10 2     2   3086 use Time::Local ();
  2         4232  
  2         47  
11 2     2   2266 use LWP::Simple ();
  2         171531  
  2         67  
12 2     2   1447 use Mirror::JSON::URI ();
  2         6  
  2         47  
13              
14 2     2   14 use constant ONE_DAY => 86700; # 1 day plus 5 minutes fudge factor
  2         4  
  2         137  
15 2     2   10 use constant TWO_DAYS => 172800;
  2         3  
  2         85  
16 2     2   9 use constant THIRTY_DAYS => 2592000;
  2         3  
  2         83  
17              
18 2     2   8 use vars qw{$VERSION};
  2         3  
  2         76  
19             BEGIN {
20 2     2   2921 $VERSION = '0.01';
21             }
22              
23              
24              
25              
26              
27             #####################################################################
28             # Wrapper for the JSON::Tiny methods
29              
30             sub new {
31 1     1 0 3 my $class = shift;
32 1         7 my $self = bless { @_ }, $class;
33 1 50       21 if ( _STRING($self->{uri}) ) {
34 1         14 $self->{uri} = URI->new($self->{uri});
35             }
36 1 50 33     10376 if ( _STRING($self->{timestamp}) and ! _POSINT($self->{timestamp}) ) {
37 1 50       18 unless ( $self->{timestamp} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/ ) {
38 0         0 return undef;
39             }
40 1         12 $self->{timestamp} = Time::Local::timegm( $6, $5, $4, $3, $2 - 1, $1 );
41             }
42 1 50       48 unless ( _ARRAY0($self->{mirrors}) ) {
43 0         0 return undef;
44             }
45 1         2 foreach ( @{$self->{mirrors}} ) {
  1         3  
46 1 50       5 if ( _STRING($_->{uri}) ) {
47 1         6 $_->{uri} = URI->new($_->{uri});
48 1 50       67 $_ = Mirror::JSON::URI->new( %$_ ) or return undef;
49             }
50             }
51 1         13 return $self;
52             }
53              
54             sub read {
55 1     1 0 583 my $class = shift;
56 1         5 my $file = shift;
57              
58             # Read in the file
59 1         7 local *MIRROR;
60 1         12 local $/ = undef;
61 1 50       82 open( MIRROR, $file ) or croak("open: $!");
62 1         38 my $buffer = ;
63 1 50       20 close( MIRROR ) or croak("close: $!");
64              
65 1         9 $class->read_string( $buffer );
66             }
67              
68             sub read_string {
69 1     1 0 2 my $class = shift;
70 1         49 my $json = JSON->new->decode( shift );
71 1         20 $class->new( %$json );
72             }
73              
74             sub write {
75 0     0 0 0 my $self = shift;
76 0 0       0 my $file = shift or return croak('No file name provided');
77              
78             # Write it to the file
79 0 0       0 open( CFG, '>' . $file ) or croak("Failed to open file '$file' for writing: $!");
80 0         0 print CFG $self->write_string;
81 0         0 close CFG;
82              
83 0         0 return 1;
84             }
85              
86             sub write_string {
87 0     0 0 0 JSON->new->pretty->encode( $_[0]->as_scalar );
88             }
89              
90             sub as_scalar {
91 0     0 0 0 my $self = shift;
92 0         0 my $hash = { %$self };
93 0 0       0 if ( defined $hash->{source} ) {
94 0         0 $hash->{source} = "$hash->{source}";
95             }
96 0         0 $hash;
97             }
98              
99              
100              
101              
102              
103             #####################################################################
104             # Mirror::JSON Methods
105              
106             sub name {
107 1     1 0 755 $_[0]->{name};
108             }
109              
110             sub uri {
111 2     2 0 14 $_[0]->{uri};
112             }
113              
114             sub timestamp {
115 1     1 0 6 $_[0]->{timestamp};
116             }
117              
118             sub age {
119 1 50   1 0 24 $_[0]->{age} or time - $_[0]->{timestamp};
120             }
121              
122             sub benchmark {
123 0     0 0 0 $_[0]->{benchmark};
124             }
125              
126             sub mirrors {
127 3     3 0 10 @{ $_[0]->{mirrors} };
  3         19  
128             }
129              
130              
131              
132              
133              
134             #####################################################################
135             # Main Methods
136              
137             sub check_mirrors {
138 2     2 0 7 my $self = shift;
139 2         48 foreach my $mirror ( $self->mirrors ) {
140 2 100       13 next if defined $mirror->{live};
141 1         9 $mirror->get;
142             }
143 2         9 return 1;
144             }
145              
146             # Does the mirror with the newest timestamp newer than ours
147             # have a different master? If so, update our master server.
148             # This lets us survive major reorgansations, as long as some
149             # of the existing mirrors are retained.
150             sub check_master {
151 0     0 0 0 my $self = shift;
152              
153             # Make sure we have checked the mirrors
154 0         0 $self->check_mirrors;
155              
156             # Anti-hijacking measure: Only do this if our current
157             # age is more than 30 days. We can almost certainly
158             # handle a 1 month changeover period, otherwise things
159             # will only be bad for a month.
160 0 0       0 if ( $self->age < THIRTY_DAYS ) {
161 0         0 return 1;
162             }
163              
164             # Find all the servers updated in the last 2 days.
165             # All of them except 1 must agree (prevent hijacking,
166             # and handle accidents or anti-update attack from older server)
167 0         0 my %uri = ();
168 0 0       0 map { $uri{$_->uri}++ } grep { $_->age >= 0 and $_->age < TWO_DAYS } $self->mirrors;
  0         0  
  0         0  
169 0         0 my @uris = sort { $uri{$b} <=> $uri{$a} } keys %uri;
  0         0  
170 0 0 0     0 unless ( scalar(@uris) <= 2 and $uris[0] and $uris[0] >= (scalar($self->mirrors) - 1) ) {
      0        
171             # Data is weird or currupt
172 0         0 return 1;
173             }
174              
175             # Master has moved.
176             # Pull the new master server mirror.json
177 0 0       0 my $new_uri = Mirror::JSON::URI->new(
178             uri => URI->new( $uris[0] ),
179             ) or return 1;
180 0 0       0 $new_uri->get or return 1;
181              
182             # To avoid pulling a whole bunch of mirror.json files again
183             # copy any mirrors from our set to the new
184 0 0       0 my $new = $new_uri->json or return 1;
185 0         0 my %old = map { $_->uri => $_ } $self->mirrors;
  0         0  
186 0         0 foreach ( @{ $new->{mirrors} } ) {
  0         0  
187 0 0       0 if ( $old{$_->uri} ) {
188 0         0 $_ = $old{$_->uri};
189             } else {
190 0         0 $_->get;
191             }
192             }
193              
194             # Now overwrite ourself with the new one
195 0         0 %$self = %$new;
196              
197 0         0 return 1;
198             }
199              
200             # Select the "best" mirrors
201             sub select_mirrors {
202 1     1 0 951 my $self = shift;
203 1   50     50 my $wanted = _POSINT(shift) || 3;
204              
205             # Check the mirrors
206 1         18 $self->check_mirrors;
207              
208             # Start with the list of all live mirrors, and create
209             # some interesting subsets.
210 0         0 my @live = sort { $a->lag <=> $b->lag }
  1         7  
211 1         4 grep { $_->live } $self->mirrors;
212 1         5 my @current = grep { $_->json->age < ONE_DAY } @live;
  0         0  
213 1         3 my @ideal = grep { $_->lag < 2 } @current;
  0         0  
214              
215             # If there are enough fast and up-to-date mirrors
216             # (which should be common for many people) return them.
217 1 50       6 if ( @ideal >= $wanted ) {
218 0         0 return map { $_->uri } @ideal[0 .. $wanted];
  0         0  
219             }
220              
221             # If there are enough up-to-date mirrors
222             # (which should be common) return them.
223 1 50       5 if ( @current >= $wanted ) {
224 0         0 return map { $_->uri } @current[0 .. $wanted];
  0         0  
225             }
226              
227             # Are there ANY that are up to date
228 1 50       10 if ( @current ) {
229 0         0 return map { $_->uri } @current;
  0         0  
230             }
231              
232             # Something is weird, just use the master site
233 1         6 return ( $self->uri );
234             }
235              
236             1;
237              
238             __END__