summaryrefslogtreecommitdiffstats
path: root/perl-install/mirror.pm
blob: 11d55f06b4b9047eab1fe994bf5ae67fbc131571 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package mirror;

use diagnostics;
use strict;
use feature 'state';

use common;
use log;

=head1 SYNOPSYS

B<mirror> enables to manage Mageia distribution mirrors

=head1 Functions

=over

=cut

=item mirror2text($mirror)

Returns a displayable string from a mirror struct

=cut

sub mirror2text {
    my ($mirror) = @_;
    translate($mirror->{country})  . '|' . $mirror->{host} . ($mirror->{method} ? " ($mirror->{method})" : '');
}

=item register_downloader($func)

Sets a downloader program

=cut

my $downloader;
sub register_downloader {
    my ($func) = @_;
    $downloader = $func;
}

sub _mirrors_raw_install {
    my ($list) = @_;
    require install::http;
    my $f = install::http::getFile($list, "strict-certificate-check" => 1) or die "mirror list not found";
    local $SIG{ALRM} = sub { die "timeout" };
    alarm 60;
    log::l("using mirror list $list");
    my @lines;
    push @lines, $_ while <$f>;
    alarm 0;
    @lines;
}

sub _mirrors_raw_standalone {
    my ($list) = @_;
    my @lines;
    if (ref($downloader)) {
        @lines = $downloader->($list);
        @lines or die "mirror list not found";
    } else {
        die "Missing download callback";
    }
    @lines;
}

=item mirrors_raw($product_id)

Returns a list of mirrors hash refs from http://mirrors.mageia.org

Note that in standalone mode, one has to actually use register_downloader()
first in order to provide a downloader callback.

=cut

sub mirrors_raw {
    my ($product_id) = @_;

    #- contact the following URL to retrieve the list of mirrors.
    #- http://wiki.mageia.org/en/Product_id
    my $type = lc($product_id->{type}); $type =~ s/\s//g;
    my $list = "https://mirrors.mageia.org/api/$type.$product_id->{version}.$product_id->{arch}.list?product=$product_id->{product}";
    log::explanations("trying mirror list from $list");
    my @lines = $::isInstall ? _mirrors_raw_install($list) : _mirrors_raw_standalone($list);
    map { common::parse_LDAP_namespace_structure(chomp_($_)) } @lines;
}

=item list($product_id, $type)


Returns a list of mirrors hash refs as returned by mirrors_raw() but filters it.

One can select the type of mirrors ('distrib', 'updates', ...) or 'all'

=cut

sub list {
    my ($product_id, $type) = @_;

    our @mirrors_raw;
    if (!@mirrors_raw) {
        @mirrors_raw = eval { mirrors_raw($product_id) };
        if (my $err = $@) {
            log::explanations("failed to download mirror list");
            die $err;
        }
        @mirrors_raw or log::explanations("empty mirror list"), return;
    }

	my @mirrors = grep {
	    ($_->{method}, $_->{host}, $_->{dir}) = $_->{url} =~ m!^(ftp|https?)://(.*?)(/.*)!;
	    $_->{method} && (member($type, 'all', $_->{type}));
	} @mirrors_raw or log::explanations("no mirrors of type $type"), return;

    @mirrors && \@mirrors;
}

=item nearest($timezone, $mirrors)

Randomly returns one of the nearest mirror

=cut

#- TODO: The following code was forked from urpm::mirrors. Consider refactoring.

sub nearest {
    my ($timezone, $mirrors) = @_;

    my ($latitude, $longitude, $country_code);

    require Time::ZoneInfo;
    if (my $zones = Time::ZoneInfo->new) {
        if (($latitude, $longitude) = $zones->latitude_longitude_decimal($timezone)) {
            $country_code = $zones->country($timezone);
        }
    }
    defined $latitude && defined $longitude or return;

    foreach (@$mirrors) {
        $_->{latitude} || $_->{longitude} or next;
        my $PI = 3.14159265358979;
        my $x = $latitude - $_->{latitude};
        my $y = ($longitude - $_->{longitude}) * cos($_->{latitude} / 180 * $PI);
        $_->{proximity} = sqrt($x * $x + $y * $y);
    }
    my ($best) = sort { $a->{proximity} <=> $b->{proximity} } @$mirrors;

    foreach (@$mirrors) {
        $_->{proximity_corrected} = $_->{proximity} * _random_correction();
        $_->{proximity_corrected} *= _between_country_correction($country_code, $_->{country}) if $best;
        $_->{proximity_corrected} *= _between_continent_correction($best->{continent}, $_->{continent}) if $best;
        $_->{proximity_corrected} *= _protocol_correction($_->{url});
    }
    ($best) = sort { $a->{proximity_corrected} <=> $b->{proximity_corrected} } @$mirrors;
    $best;
}

# add +/- 5% random
sub _random_correction() {
    my $correction = 0.05;
    1 + (rand() - 0.5) * $correction * 2;
}

sub _between_country_correction {
    my ($here, $mirror) = @_;
    $here && $mirror or return 1;
    $here eq $mirror ? 0.5 : 1;
}

sub _between_continent_correction {
    my ($here, $mirror) = @_;
    $here && $mirror or return 1;
    $here eq $mirror ? 0.5 : # favor same continent
      $here eq 'SA' && $mirror eq 'NA' ? 0.9 : # favor going "South America" -> "North America"
	1;
}

sub _protocol_correction {
    my ($url) = @_;
    # favor encrypted protocols, then http
    ( $url =~ m!https://! ) and return 0.7;
    ( $url =~ m!ftps://! ) and return 0.8;
    ( $url =~ m!http://! ) and return 0.9;
    1;
}

=back

=cut

1;