1 # -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*-
2 # vim: ts=4 sts=4 sw=4:
5 use vars qw($VERSION $urllist $silent);
6 $VERSION = "1.770001"; # 1.77 + local patches for bleadperl
13 my ($class, $file) = @_;
19 my $handle = FileHandle->new;
21 or croak "Couldn't open $file: $!";
22 flock $handle, LOCK_SH;
23 $self->_parse($file,$handle);
24 flock $handle, LOCK_UN;
27 # populate continents & countries
34 return keys %{$self->{geography}};
38 my ($self, @continents) = @_;
39 @continents = $self->continents unless @continents;
41 for my $c (@continents) {
42 push @countries, keys %{ $self->{geography}{$c} };
48 my ($self, @countries) = @_;
49 return @{$self->{mirrors}} unless @countries;
50 my %wanted = map { $_ => 1 } @countries;
52 for my $m (@{$self->{mirrors}}) {
53 push @found, $m if exists $wanted{$m->country};
59 my ($self, %args) = @_;
60 my $how_many = $args{how_many} || 1;
61 my $callback = $args{callback};
62 my $verbose = $args{verbose};
63 my $conts = $args{continents} || [];
64 $conts = [$conts] unless ref $conts;
69 print "Searching for the best continent ...\n" if $verbose;
70 my @best = $self->_find_best_continent($seen, $verbose, $callback);
72 # how many continents to find enough mirrors? We should scan
73 # more than we need -- arbitrarily, we'll say x2
77 $count += $self->mirrors( $self->countries($c) );
78 last if $count >= 2 * $how_many;
82 print "Scanning " . join(", ", @$conts) . " ...\n" if $verbose;
85 for my $m ($self->mirrors($self->countries(@$conts))) {
87 my $hostname = $m->hostname;
88 if ( $seen->{$hostname} ) {
89 push @timings, $seen->{$hostname}
90 if defined $seen->{$hostname}[1];
94 next unless defined $ping;
95 push @timings, [$m, $ping];
96 $callback->($m,$ping) if $callback;
99 return unless @timings;
100 $how_many = @timings if $how_many > @timings;
103 sort { $a->[1] <=> $b->[1] } @timings;
105 return wantarray ? @best[0 .. $how_many-1] : $best[0];
108 sub _find_best_continent {
109 my ($self, $seen, $verbose, $callback) = @_;
112 CONT: for my $c ( $self->continents ) {
113 my @mirrors = $self->mirrors( $self->countries($c) );
114 next CONT unless @mirrors;
116 my $n = (@mirrors < $sample) ? @mirrors : $sample;
118 RANDOM: while ( @mirrors && @tests < $n ) {
119 my $m = splice( @mirrors, int(rand(@mirrors)), 1 );
121 $callback->($m,$ping) if $callback;
122 # record undef so we don't try again
123 $seen->{$m->hostname} = [$m, $ping];
124 next RANDOM unless defined $ping;
127 next CONT unless @tests;
128 @tests = sort { $a <=> $b } @tests;
130 $median{$c} = $tests[0];
132 elsif ( @tests % 2 ) {
133 $median{$c} = $tests[ int(@tests / 2) ];
136 my $mid_high = int(@tests/2);
137 $median{$c} = ($tests[$mid_high-1] + $tests[$mid_high])/2;
141 my @best_cont = sort { $median{$a} <=> $median{$b} } keys %median ;
144 print "Median result by continent:\n";
145 for my $c ( @best_cont ) {
146 printf( " %d ms %s\n", int($median{$c}*1000+.5), $c );
150 return wantarray ? @best_cont : $best_cont[0];
153 # Adapted from Parse::CPAN::MirroredBy by Adam Kennedy
155 my ($self, $file, $handle) = @_;
156 my $output = $self->{mirrors};
157 my $geo = $self->{geography};
164 my $string = <$handle>;
165 last if ! defined $string;
168 # Remove the useless lines
170 next if $string =~ /^\s*$/;
171 next if $string =~ /^\s*#/;
173 # Hostname or property?
174 if ( $string =~ /^\s/ ) {
176 unless ( $string =~ /^\s+(\w+)\s+=\s+\"(.*)\"$/ ) {
177 croak("Invalid property on line $line");
179 my ($prop, $value) = ($1,$2);
181 if ( $prop eq 'dst_location' ) {
182 my (@location,$continent,$country);
183 @location = (split /\s*,\s*/, $value)
184 and ($continent, $country) = @location[-1,-2];
185 $continent =~ s/\s\(.*//;
186 $continent =~ s/\W+$//; # if Jarkko doesn't know latitude/longitude
187 $geo->{$continent}{$country} = 1 if $continent && $country;
188 $mirror->{continent} = $continent || "unknown";
189 $mirror->{country} = $country || "unknown";
191 elsif ( $prop eq 'dst_http' ) {
192 $mirror->{http} = $value;
194 elsif ( $prop eq 'dst_ftp' ) {
195 $mirror->{ftp} = $value;
197 elsif ( $prop eq 'dst_rsync' ) {
198 $mirror->{rsync} = $value;
202 $mirror->{$prop} = $value;
206 unless ( $string =~ /^([\w\.-]+)\:\s*$/ ) {
207 croak("Invalid host name on line $line");
209 my $current = $mirror;
210 $mirror = { hostname => "$1" };
212 push @$output, CPAN::Mirrored::By->new($current);
217 push @$output, CPAN::Mirrored::By->new($mirror);
223 #--------------------------------------------------------------------------#
225 package CPAN::Mirrored::By;
234 sub hostname { shift->{hostname} }
235 sub continent { shift->{continent} }
236 sub country { shift->{country} }
237 sub http { shift->{http} || '' }
238 sub ftp { shift->{ftp} || '' }
239 sub rsync { shift->{rsync} || '' }
243 return $self->{ftp} || $self->{http};
248 my $ping = Net::Ping->new("tcp",1);
249 my ($proto) = $self->url =~ m{^([^:]+)};
250 my $port = $proto eq 'http' ? 80 : 21;
252 $ping->port_number($port);
254 my ($alive,$rtt) = $ping->ping($self->hostname);
255 return $alive ? $rtt : undef;