From a9ade04230fc99e5d12e7bd1af1e2dd31907d15a Mon Sep 17 00:00:00 2001 From: Olivier Thauvin Date: Sat, 2 Oct 2010 13:31:35 +0000 Subject: - handle distributions, protocol andothers things --- lib/MGA/Mirrors/Controller/Distrib.pm | 48 ++++++++++++++ lib/MGA/Mirrors/Controller/Mirrors.pm | 66 +++++++++++++++++++ lib/MGA/Mirrors/Controller/New.pm | 13 ++-- lib/MGA/Mirrors/DB.pm | 102 +++++++++++++++++++++++++++-- root/html/includes/header.tt | 3 + root/html/includes/host_information.tt | 50 ++++++++++++++ root/html/includes/mirrorslist.tt | 29 +++++++- root/html/includes/new/confirm.tt | 2 + root/html/includes/new/host_information.tt | 19 ------ root/html/includes/new/new_host.tt | 9 +++ root/html/includes/new/url_form.tt | 4 +- root/html/pages/index.tt | 3 - t/controller_Distrib.t | 9 +++ t/controller_Mirrors.t | 9 +++ 14 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 lib/MGA/Mirrors/Controller/Distrib.pm create mode 100644 lib/MGA/Mirrors/Controller/Mirrors.pm create mode 100644 root/html/includes/host_information.tt delete mode 100644 root/html/includes/new/host_information.tt create mode 100644 root/html/includes/new/new_host.tt create mode 100644 t/controller_Distrib.t create mode 100644 t/controller_Mirrors.t diff --git a/lib/MGA/Mirrors/Controller/Distrib.pm b/lib/MGA/Mirrors/Controller/Distrib.pm new file mode 100644 index 0000000..0d49e55 --- /dev/null +++ b/lib/MGA/Mirrors/Controller/Distrib.pm @@ -0,0 +1,48 @@ +package MGA::Mirrors::Controller::Distrib; +use Moose; +use namespace::autoclean; + +BEGIN {extends 'Catalyst::Controller'; } + +=head1 NAME + +MGA::Mirrors::Controller::Distrib - Catalyst Controller + +=head1 DESCRIPTION + +Catalyst Controller. + +=head1 METHODS + +=cut + + +=head2 index + +=cut + +sub index :Path :Args(0) { + my ( $self, $c ) = @_; + +} + +sub list :Path :Args(1) { + my ( $self, $c ) = @_; + $c->stash->{current_view} = 'TTBlock'; + $c->stash->{template} = 'distrib/distrib.tt'; +} + +=head1 AUTHOR + +Olivier Thauvin + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/MGA/Mirrors/Controller/Mirrors.pm b/lib/MGA/Mirrors/Controller/Mirrors.pm new file mode 100644 index 0000000..5d07ca5 --- /dev/null +++ b/lib/MGA/Mirrors/Controller/Mirrors.pm @@ -0,0 +1,66 @@ +package MGA::Mirrors::Controller::Mirrors; +use Moose; +use namespace::autoclean; + +BEGIN {extends 'Catalyst::Controller'; } + +=head1 NAME + +MGA::Mirrors::Controller::Mirrors - Catalyst Controller + +=head1 DESCRIPTION + +Catalyst Controller. + +=head1 METHODS + +=cut + + +=head2 index + +=cut + +sub index :Path :Args(0) { + my ( $self, $c ) = @_; + + $c->response->body('Matched MGA::Mirrors::Controller::Mirrors in Mirrors.'); +} + +sub mirror :Path :Args(1) { + my ( $self, $c, $host ) = @_; + $c->stash->{hostname} = $host; + + if ($c->req->param('hostinfo')) { + my $hinfo = $c->model('Mirrors')->find_mirrors({ + hostname => $host, + })->[0]; + if (! $hinfo->{readonly}) { + $c->model('Mirrors')->add_or_update_host($host, + bandwidth => $c->req->param('bandwidth'), + city => $c->req->param('city'), + country => $c->req->param('country'), + syncfrom=> $c->req->param('syncfrom'), + ); + } + } + + $c->stash->{host} = $c->model('Mirrors')->find_mirrors({ + hostname => $host, + })->[0]; +} + +=head1 AUTHOR + +Olivier Thauvin + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/lib/MGA/Mirrors/Controller/New.pm b/lib/MGA/Mirrors/Controller/New.pm index fbb1fbe..d3ec38c 100644 --- a/lib/MGA/Mirrors/Controller/New.pm +++ b/lib/MGA/Mirrors/Controller/New.pm @@ -44,8 +44,10 @@ sub index :Path :Args(0) { hostname => $uri->host, }); if (@{$urls || []}) { $c->stash->{exists_url} = $urls; - $c->stash->{subtemplate} = 'new/mirror_exists.tt'; - return; + if ($urls->[0]->{valid}) { + $c->stash->{subtemplate} = 'new/mirror_exists.tt'; + return; + } } if (!$c->model('Mirrors')->mirror_validity($uri)) { @@ -62,17 +64,18 @@ sub index :Path :Args(0) { my @ips = $c->model('Mirrors')->host_ips($uri->host); $c->stash->{location} = $c->model('Mirrors')->locate_ips(@ips); + $c->stash->{host}{country} = $c->stash->{location}{code}; my $mirror = $c->model('Mirrors')->find_mirrors( { hostname => $uri->host, }); if (@{ $mirror || []}) { $c->stash->{mirror} = $mirror->[0]; } elsif ($c->req->param('hostinfo')) { - foreach (qw(city country)) { + foreach (qw(city country syncfrom bandwidth)) { $c->session->{hostinfo}{$_} = $c->req->param($_); } } else { - $c->stash->{subtemplate} = 'new/host_information.tt'; + $c->stash->{subtemplate} = 'new/new_host.tt'; return; } @@ -94,6 +97,8 @@ sub confirm :Path :Args(1) { $uri->host, city => $c->session->{hostinfo}{city}, country => $c->session->{hostinfo}{country}, + bandwidth => $c->session->{hostinfo}{bandwidth}, + syncfrom => $c->session->{hostinfo}{syncfrom}, ); } else { return; diff --git a/lib/MGA/Mirrors/DB.pm b/lib/MGA/Mirrors/DB.pm index 5ff0649..31eb4cc 100644 --- a/lib/MGA/Mirrors/DB.pm +++ b/lib/MGA/Mirrors/DB.pm @@ -74,6 +74,35 @@ sub locate_ips { return; } +sub protocol_list { + my ($self) = @_; + my $select = $self->db->prepare(q{ + select name from protocol order by name + }); + $select->execute; + return keys %{ $select->fetchall_hashref([ 'name' ]) } +} + +sub bandwidth_name { + my ($self, $value) = @_; + my $select = $self->db->prepare(q{ + select name from bandwidth where value = ? + }); + $select->execute($value); + my $res = $select->fetchrow_hashref; + $select->finish; + $res->{name} +} + +sub bandwidth_list { + my ($self) = @_; + my $list = $self->db->prepare(q{ + select * from bandwidth order by value + }); + $list->execute; + return $list->fetchall_arrayref({}); +} + sub country_list { my ($self) = @_; my $list = $self->db->prepare(q{ @@ -83,6 +112,27 @@ sub country_list { return $list->fetchall_arrayref({}); } +sub version_list { + my ($self) = @_; + my $list = $self->db->prepare(q{ + select * from distributions + }); + $list->execute; + return $list->fetchall_hashref([qw(version)]); +} + +sub arch_list { + my ($self, $version) = @_; + my $list = $self->db->prepare(q{ + select * from distributions + } . ($version + ? q{ where version = ? } + : q{}) + ); + $list->execute($version ? $version : ()); + return $list->fetchall_hashref([qw(arch)]); +} + sub mirror_validity { my ($self, $uri) = @_; my $listf = $self->db->prepare(q{ @@ -153,7 +203,7 @@ sub _check_url { my ($fh, $filename) = tempfile(); close($fh); my $cmd = - $furi->scheme =~ /^http|ftp$/ ? "wget -nv -t 1 -T 4 -O $filename " . $furi->as_string : + $furi->scheme =~ /^http|ftp$/ ? "wget --no-check-certificate -nv -t 1 -T 4 -O $filename " . $furi->as_string : $furi->scheme eq 'rsync' ? "rsync --timeout 4 -q " . $furi->as_string . " $filename" : ''; my $ok = (system($cmd) == 0); unlink($filename); @@ -196,6 +246,7 @@ sub find_mirrors { } if (my $field = { protocol => 'protocol', + valid => 'url.valid', }->{$_}) { push(@uw, sprintf('%s = any(?)', $field)); push(@uvals, ref $filters->{$_} ? $filters->{$_} : [ $filters->{$_} ]); @@ -220,17 +271,59 @@ sub _find_urls { }; my @vals; if (keys %{ $filters || {} }) { - $query .= ' where '; my @w; foreach (keys %$filters) { + $filters->{$_} or next; + my $field = { + hostname => 'hosts.hostname', + protocol => 'urls.protocol', + }->{$_} or next; + + push(@w, sprintf('%s = any(?)', $field)); + push(@vals, ref $filters->{$_} ? $filters->{$_} : [ $filters->{$_} ]); + } + $query .= ' where ' if (@w); + $query .= join(' and ', @w); + } + my $list = $self->db->prepare($query); + $list->execute(@vals); + return $list->fetchall_arrayref({}); +} + +sub find_distributions { + my ($self, $filters, $key) = @_; + return [ + map { $_->{url} = $self->fmt_url($_); $_ } + @{ $self->_find_distributions($filters) || []}] +} + +sub _find_distributions { + my ($self, $filters, $key) = @_; + + my $query = q{ + select *, urls.path || distributions.relpath as path from hosts + join urls on urls.hostname = hosts.hostname + join mirrors_distributions on urls.key = mirrors_distributions.urlskey + join distributions on distributions.dkey = mirrors_distributions.distributionkey + }; + + my @vals; + if (keys %{ $filters || {} }) { + my @w; + foreach (keys %$filters) { + $filters->{$_} or next; my $field = { hostname => 'hosts.hostname', protocol => 'urls.protocol', + version => 'distributions.version', + arch => 'distributions.arch', + country => 'mirrors.country', }->{$_} or next; push(@w, sprintf('%s = any(?)', $field)); push(@vals, ref $filters->{$_} ? $filters->{$_} : [ $filters->{$_} ]); } + $query .= ' where ' if (@w); $query .= join(' and ', @w); } my $list = $self->db->prepare($query); @@ -258,7 +351,7 @@ sub add_or_update_host { my (@fields, @vals); while (my ($field, $val) = each(%info)) { push(@fields, $field); - push(@vals, $val); + push(@vals, $val || undef); } if (keys %info) { my $upd = $self->db->prepare(sprintf(q{ @@ -289,7 +382,7 @@ sub add_or_update_url { } my $update = $self->db->prepare(q{ - update urls set path = ?, port = ? + update urls set path = ?, port = ?, valid = true where hostname = ? and protocol = ? }); @@ -349,6 +442,7 @@ sub find_urls { sub fmt_url { my ($self, $dburl) = @_; + $dburl->{path} =~ s:/+:/:g; my $uri = URI->new( sprintf('%s://%s%s', $dburl->{protocol}, diff --git a/root/html/includes/header.tt b/root/html/includes/header.tt index 5319e52..7f8b1d5 100644 --- a/root/html/includes/header.tt +++ b/root/html/includes/header.tt @@ -8,3 +8,6 @@ +

Mageia mirrors database

+ +

Mirror not found ? register it

diff --git a/root/html/includes/host_information.tt b/root/html/includes/host_information.tt new file mode 100644 index 0000000..c0cb7cb --- /dev/null +++ b/root/html/includes/host_information.tt @@ -0,0 +1,50 @@ + +
+ + + + + + + + + + +[% FOREACH mirror = c.model('Mirrors').find_mirrors %] +[% IF loop.first %] + + + +[% END %] +[% END %] + + + +
Country + +
City + +
Synchronized from + +
Approximated bandwidth + +
+ +
diff --git a/root/html/includes/mirrorslist.tt b/root/html/includes/mirrorslist.tt index fd855f5..0986717 100644 --- a/root/html/includes/mirrorslist.tt +++ b/root/html/includes/mirrorslist.tt @@ -5,6 +5,13 @@ [% END %] + + @@ -16,7 +23,13 @@ [% FOREACH item = mirrorslist %] [% IF loop.first %] - + + + + + + + [% END %] [% urls = db.find_urls({ "hostname" => item.hostname }) %] @@ -29,8 +42,22 @@ [% END %] + + diff --git a/root/html/includes/new/confirm.tt b/root/html/includes/new/confirm.tt index b3eb3f3..e0bb7d4 100644 --- a/root/html/includes/new/confirm.tt +++ b/root/html/includes/new/confirm.tt @@ -1,4 +1,6 @@
+

Really adding [% c.session.new_uri | html %] ?

+ [% c.prototype.form_remote_tag({ url => c.uri_for('confirm'), update => 'foo' } ) %] diff --git a/root/html/includes/new/host_information.tt b/root/html/includes/new/host_information.tt deleted file mode 100644 index 78c04c2..0000000 --- a/root/html/includes/new/host_information.tt +++ /dev/null @@ -1,19 +0,0 @@ -Enter Info for [% uri.host %] - -
- - - -[% location.name %] / [% location.continent %]
- -Country: - - -City: - - diff --git a/root/html/includes/new/new_host.tt b/root/html/includes/new/new_host.tt new file mode 100644 index 0000000..a4296d2 --- /dev/null +++ b/root/html/includes/new/new_host.tt @@ -0,0 +1,9 @@ +Enter Info for [% uri.host %] + +
+ + + +[% location.name %] / [% location.continent %]
+ +[% INCLUDE 'host_information.tt' %] diff --git a/root/html/includes/new/url_form.tt b/root/html/includes/new/url_form.tt index f8840b9..169fe73 100644 --- a/root/html/includes/new/url_form.tt +++ b/root/html/includes/new/url_form.tt @@ -1,5 +1,7 @@ -Enter the url to the top level mirror tree:
+Enter the url to the top level mirror tree, supported protocol are +[% c.model('Mirrors').protocol_list.join(', ') | html %].
+ diff --git a/root/html/pages/index.tt b/root/html/pages/index.tt index f1f0251..9acd3cd 100644 --- a/root/html/pages/index.tt +++ b/root/html/pages/index.tt @@ -1,6 +1,3 @@ -

Mageia mirrors database

- -

Mirror not found ? register it

[% INCLUDE 'mirrorslist.tt' %] diff --git a/t/controller_Distrib.t b/t/controller_Distrib.t new file mode 100644 index 0000000..d1efc3c --- /dev/null +++ b/t/controller_Distrib.t @@ -0,0 +1,9 @@ +use strict; +use warnings; +use Test::More; + +BEGIN { use_ok 'Catalyst::Test', 'MGA::Mirrors' } +BEGIN { use_ok 'MGA::Mirrors::Controller::Distrib' } + +ok( request('/distrib')->is_success, 'Request should succeed' ); +done_testing(); diff --git a/t/controller_Mirrors.t b/t/controller_Mirrors.t new file mode 100644 index 0000000..bfb9912 --- /dev/null +++ b/t/controller_Mirrors.t @@ -0,0 +1,9 @@ +use strict; +use warnings; +use Test::More; + +BEGIN { use_ok 'Catalyst::Test', 'MGA::Mirrors' } +BEGIN { use_ok 'MGA::Mirrors::Controller::Mirrors' } + +ok( request('/mirrors')->is_success, 'Request should succeed' ); +done_testing(); -- cgit v1.2.1 wesN2㽸}{,-g 1-V^}PGL7pgO[JRuIRVTÀw Ux/R RNFp{-s[]p` ~2 %Kv{6Ȩ]|FUnh،aYÛ04[r6luurV WF?KW.e3P#FT:OZX8aDW{ɢ4=)ُf>^9 ڲN$bO?|]VM#4nM4pQ&+&B8mRQ]U7 :G# &FH"7kWFzb÷wMc]-s>XYZ#]A( l-s}*FFy~51ő(5jD$:}//bvDC ,9z{35-e%'tAQ:—#o`R(QG*voW`%<A'7ZӲ'Nۋjb~/)QL'c]i=,Ptt/kN^"1됯@r'`殮5#iĆ+~t ] n/Ee}[$riuq;x >C07K w~G6_^ݟu{2ʖo AH!DK &A)훉yJT@{& gWxN):|Y%UzG:յD/gH.HRW7&w7-뵡LܙKBmkaT[KeB6@,9߿y$_r<+%.١v_,(kiFM,Pb0LoR6wgXb~f8H͖(ֱ.מ0+&BossVE 66#g@N:62MCpB,K?4G Fߚ8LϏK1s g*ӌu]|W3Տko {5]&S`GLS?!Dћ~8|< `kUVFL Wc恶g9Z (rfs [L8;85= !04S\ݎJÄe\uNKk/Gt[GTL8iXx)BV[j7d4L@=}䉃VZA8$=r慷=A(yY2|q o XȲy{a#Sl)cӷ<֦jX̛!$GLŊޘ H.7 =cƭs[*:MMM0hgShjB?)ii8.8rGY10g󙷔J;}j׫lp=fΉw.FqK{[cs{|;&؃9qP/ wEP\WK(>Oz< yq"w*!IW: i9Ķ+9|ڐ"r@po2`R~DdƣfXZse=?y (Ra kP!6gKDW[ 1We;FGRq|<^A l wqinHOD6 ݩIo]ϩ},Ϡns*s v;$ Wؾ bSfblXFuPnC3VPfd1@47t@G8s'+!=а(W̔`z9J3yuC!šѮt塑jg\ b-Clf3vmve҃juSOf̬^J֭пh|Âϗ]ј3NO1QusI#K>]dEjVʖ <[4DY_%W_=Bw+;# o& ,£ 8&K^C҅&C'SBCʵ:t\`+\9ݯ}]签+G[.qA %\ψ)7 hG*04V6"hLDԸ.U=\_ίvWU4l }e'Y#zÌi/`۫^'[؁;e`v2CJK=*Tr>p~Dԁ4+I{۽ufFJrmZzq<5Ly|1i1bC5$ nd/:vNfE‰ˣ!勯"ndKG_SO S =E]obe"Q}EIX}•=jM ){mZ 8m`~ӡF$^ B:Y#ɱ~`T#r|&j.AWЎ=\(tc$•̅'LbzKn@ghddUrc-8B A-D(A:ߟwp= DAFD,84_=_\6j#'a4G$8`Elg;уv[@cj( Փ]èl ѧ?+2e?:RHi[i8)E}.s80b:Sɠ?р0=E$.ˋTBYO8l5b/{_~L⛑7fZݦ?پ޽kטda,?q5V7Pܘ ѻ<,Y]nuT37H^濨~_Em8ĘQt+%.SX2gcnxdc0७YYӼ]Sd50q/yT|>uQ%MmG5x{^ .S^E0hgq#pnQ1ӌF7Z31? 峸&_ ] :{<&^ 腔|-!<{U85>섣#6c° FalV %+ar.p0;\s^zn*0BN [tx\m1Pf@|/Y/rz8Jl:* A|b7naˤ$Lފcxy9S&C.kxyw宴>ʊdra螳`sOr(ɩV-xaKK~ΛC!"f,Zss [;ދZƃOg8>D#{>F&\i&G^l'\Bztؚ~ -P̚p ؼO滪/.6&) 1lVL`r ;TBFJ}J_5 bP@s.ჶ2{\񃔴 JAm {! K8("ɕqE=GՖTTfuMqQ_Vf_Ӓ{Ϥぐp1hkb+t&@;Ư"Ƅ3+l Y ($5|9}>_1ETY vŇd`/AQ'T6ZCblJ`Kgmϭl~yy0ؘr3,\5M9%!G+XEGxNUMOɮss,334G9aAXB)/~{ #0{ts .b?39ϸ{A0q q}gV"pP0z0p 1pP9OŒ3Hl-@YI0 A-riNn]+虎8j[n@yR fu2ءSHK1U؎b=Vd5Ű5sxŽ|9 #I9>.3e3Vh`T08!;|^Z{vf d:A_֦ ;w#9]޳}ZQЧvtcuʼnwbͶƕTѤ8{||(:uCD?]HvmETr% y2ߺ:JۍEObAq?2GqG\L#G@j#[N%'uRتN-Ycx_c7uٯ7 ox$zԙk(' 8\Mx8E\MB%zSsyHn/})w~jqG g<(VZ9hi =W<ȸ qٕ 5r&Cp'~\dOT "@ hiP^5X2wI TR!V%Zuo NYK B}Ydcp[Imִ[j5|O᯶?D0U8usC nW4NI5v>_F@s3s?NƲ\bV~-ʍO3:sxވ(7M7: ,Ŏl?G5,CF1]=}/_0|Rbiy/F&ǥup6k[dARbW}&$g BVEn*ޖz[ ?co6Fl0ȐFIP['=RO&$q)$>T)XÀ H\KVmEoT]Db(ˇd]pPr\5TZ**uQm\RXܛ!6M#Mgw,ruΚjۣдf RO<3vo? iI8G*+o I8w%~2U#ɲ); tW[\M"|40Ha.W֒+e\zc-͔~*Cڣ\T%k*] ae#u2Ue&הsOuEU8q.n=KMz[s>H%zז5,Rdu5xcpyi=<$p"ǧAQQ[cDKL q5(x'-chbB 'p1ޑx ʢ!GȖ`p49ח:fb'4L wPv.P {$(;ppl}DPK'8[1"_+7I̊ΰo<^ ,˳Z;J(6h }T=!=s?ZDAaL9ª!b ]VdI%s$r_-j]4srk{yЕx!)ADnt![3Tu-ַ1UAk1i-a:tZ -"eU='POazjHX`Rgi )BҀc"Xem0;]@LCgZ54Y'c% f ʏ Q)4Clrc( Q8X 7k6 p2VFTոHCGRjM4mk  A#A"1}eD>gY Ktچp'̻0'x !}֋A=rab"@ 5{鉓t+[~]s3!h -q6OAXܖV9?Ys~-oc 74?Hf1 `ʴŕW}&? -yV_zV)A 5o)alAt vp&ej#d(mst6Et6p,8t֝ KM0 qS{ n!Azo^slKejgyKerbxfe2M>K6חRQڣ$/9M׏$"tu  HEÎD4c֞ ro ZAhpFM倃,ߣɏynFË14 crZV*=ۨ?-Q絤J!bfܣc|y8D96}JQg:|**KWbx! >/~֭9-{Y?w 2 s9$@J t儂(=?w-ַO^ GX*ed:}cM|?78Ң#daIBkk Aἷba foνL*N)/L[Tb'ښu {5Fb/JvZ;`{A~[@镲4h&Xs%(ׯ>/hdn4ڍc$@᥽ sӜ#"+8Lz_룣ϣk:)yΓ{CLw)l\: Q0nߤE,3^(nf}MgjZHY#"B5he'5s:aQ|H#V 76r;Y9ץEMӷ7`Di2?1pJF?< J>i:b(k9L-f{iPf#ұ~)FD oIH0++I-m<3Rf"%IPVpCiGCzQ3N-'2C.luiBtfuymd'3 5tʉ"7cnJd<xqYQߞͫU'xWkɍ J "SՇҕg"!c;R}=bA/ Y.ʥgPשQ%cS]y~7=s$p}T{1)(,A|BG*Q] rdPlpgBϥR~FH>;s2lD)tt}͟8fmuz?TCrT!a[W|.wڽ?Oz6h=fJj d0RTձS1`:@)I" hEh$YlAm y?0Ow x`'5~fj w.=t\pU!)d@eaǚIz"~GbEILGiVopDnl)xk#1ܪ CG _hr[ZU Z2p]˟{Q J,=fj \jwb qmLr㍈ħ؇ON{ίo@,F(T>( at,%Z_Rg9ɎQyࡉD ò 4"gth*pG΋.x~MAk,R:j6 SO=kN:/0ܯ6N72Pa,&u(OiX8iD[舫:h//cn5Lq2ƍT`y?PH: :kq49am*;P; ͢BN؄Z+oj,Ҟ@sCAϊ 0ķЩ⑤o>R NJAZ:Y'#[ n8f*gou<,5/v598zG<o:^ גJ7h/HٕPLQYA ގ_M`B9 +?#7W WMjKPPϣ08̡9BSQ׺/wK{Q?zf b|}o§doi6Dc{K!xUclb8^bohјfV!XPsw 5\'A~!w\ӹy2/r]id"9Rfظ>c9S͸O8fG㳧/nj!4j4]^f"N2 S|I!g'ЬN0>XyLYa}1 VAloeT夦9Eϯl](KKAQwgv )`?hMMB?^8 o7I׍>`5|t~em%d7}HRonU*Bima45" 5FrO!4=oCLoNh OE.n[=uaxUn/0sH^1󬊬)Mօ\=7Qa[QLT˩Me8\PsJ -C`q;  jXj<  ~+(&٨vl2giw,u̸114D?`l[TGnW:Kգ9۪t9!4 L{\qm>E=WM_. O~@үR2GKwe@ >gκQv@Z&B2며==\Ñ4\*TT _g@*MئB;fMHggRѕjv( Βt3owG.xx]#1qzz]@C)-s[md\t6DA5Xj#=R2rSLOAjHBE>hL_#ZɮOP0_ 1': %y~vhz \qaL> ڄ7bHͷfB#42m՝|kT2+]7\d3;Ѓ~\SW̳J_256+ʀ YQ|lX)8krA{ܵi]-E~h"xstb`=R;́+u1j /B]9KU`e0}5c!u?GF*OpYSval"u|jztƠɬ dD.d6yt#i_ECR&܃/!*Ga3/!I0P:z9$7_h}=?BHDx삥V-HCm}@Q[lܾ6`}b{xNV?8l / L&ݣX{WWAgp;&t CKW;N#Su(o} ?88}(m&-괭&MLۘ l{Z:#З*yEcOtrK7 ;7ڔšrϰ)Q 9 ^['38>tr/~xQ`w5ߣk :Wv?89ٮxxJu VMMKk5o#0罅T3¾6ni=o``oBٟ a%|Óv峒vO`vTtHiR=qzEgvևEl/K y2Yܽ\:,lbI{CHu =5?6!.U)duZbѪGS=hBrqJnچlq)eR1-FmeoCX~G2߫t 7~ Q9ßprQ.jm'o~4/ Px)Y/6^VTtzjvTʰB?c#y"=-)R#4P;)#bPnkh0y"W{?K,?>=5lǥ Fz1H/^5Lݚ̔ow\!5g>f}%C` SńǍ[&t2&(GO yފz 44k{zeEmD*Z^4x!pe4 GvM&MjdWf?p/jqym+۠W<ذᓕ^zLR"RR۟n/DP6:]Y F:kxNXȁp[iFPE#%xS2g9D5q脵22-O94>;)~L=!Pkn?$4Na_DU6DfӼ.'%lPlW|%W 2a@XHGdtӼVc_Z-0ea -)FP,!Yq1,E3{0; Y8s2/ٮKvڢ ތ3e( M^5;/[t6>I0#E 4\1 *I+!3)t|g`1f1;W3.>DǠ z7Cl1` Ye+Z Dv)yI[LS f=O탻I#z!75c[g v ]lU*,ѭϊ+k0^jۗ!Xlѳ)4 ¡ )Ņ •pS:1oCtyxZ&5㜯Y%Xj""$` P%WM^_Z}f*J+6-6$/!FgqUr1nk45=]S4 g￰ N3$̽}Qa7.]vaHܯgNϖ;Xl ?gd Ib6)R9 yw[Xۆ`L~EƓ@cbK)o.>r{zK?TIO7,<)@<1,b; œ K*pg 2/_6PylQPf^`QM骥I]jM#|)I=2r]ҦB塩YЕ+zqX/*zd+c}4|$R ]>čԆbuHZ$G.YD)RCY*#Vxa{3ӽXNpmrОi]:^W5n7l ]!:\UW\`P^wϖGʟ "F>5aHZG-={72:bKJ]NuqʁGA/d;8}zˬ}`Yy+D^ka/vmA_${F7IHmyFl\ˑ=mqirʋ5)B ie$~72L(^S3m\M]!ܷO@sfzdVDJPđn>}da]M4b4u1R*?̽%J!*D'k#@}MօKpn8=Jvnj|P ʎ?@gn}e.9.QX1yW+5naHϛ ƅW'Kd #"K)3trG \Bق}~5)NܭVבMg#1,g|ɂaX1sQz?uR>UۓSV.C̪ClTXQ._W:ƴۺ0Sz>,K`\LδWknv}6=p6r-99p Ạ́軋H=2$I5uNn9؎Al{28lY>=/Ww26r m=<ग़-kZ82*2{$ UII*i31wENךZ{+;Gfs`:O?a~'ў"Q}a, 5.0(w^AɲbDG4h[Uj.|29Ga0\ /6Įk*X-@Wb XNyҡqf9& * M[Xoܭl`![h> )'Wg$yv갏Ļr=T41|L%B^F27Ԍ 9dD,hwQ!ly/^À_mzǽVtAtݵ 丣3 Xܿ=vX ai&/XA޼%P v*w}Σ)Y+A,YQ~PeךOde" j"g1 *1ٽO|"Ol 4L [zTGlxi f !?c?&gZeŊJ˘{M26:Ԡy ~q_kAqͿg# $Zd[&1KDb3GdD->uҙ]!}GR‹r1 w\v]-ۑ܁^; ["XB.@uZ?M+TG &>׽;N2h2nE05u-[І Qgc)YbL`d#{f <(C1U?Vg!ǻ6ĜbB\hz&|kTwzz(BFP Hw9'ϱa.%}>V^TJCa2'(̀H^—*jRQBc;]^_eœFp}/ld^kdG//hȍYKub"y5l*kwf 1R I֎:zwƛR[]vtPYy.ㆋ[q,[t>J 6LE`z0ZmVL~FӦq4Q#ڒ,~._~H JvJ*sj9ofZ^)]ΑkpYYIFI:Uٵ5TގqNc@9DXUx 0f6kHD~vFjw5Q|Я|D'rt4HA{KdzsGJNS˟U:g5i2vx1HY- Ժc{T;1z;u&o1@R:_ `H;vV%uXf$yx\ZaK^aZ/.@hlrU ϱ$NQCwGuU:qG_95 ,ft:E.veF%Nn{IQ1^79FsYQg##츣/3a֩6H;tO!4bO dTƆIE χګ[l}b4 Z=+x_گo5fQXzcC| !1fCowcViԒ2݆58PSvC1uFր4fJ>EN3KJgM=-i(AGgݞgf kQBp~ٛ[aN|Ȑ@]StAϫ`(ɇ@s-!JjV%Ҙ귫Cr}0nc}`.qx9`WtEO 9t]Ӏ%Ȇl)N5%wBT=ڡ v]43/?@zXx]/ fUm c0[}R 1Wcax.Iε:4̢0KcX_(i:3J{{3d}9T#`^䧇7~qtqZɔH /KmBVT?Xfyݢ4C? ˅~ GaG Z dkAI)p5&\N(Z< ewwψ}/oF#bЇ0QM: tkx:Lx"T?3Uv UaDhQmZOu^uD9 _@ F5'H G0L:~{#OR,(se&0 _Z"V^3^`L W?'X9ne9h:7hzmOhgLq g_12쯚$?Yx`$h;PV+{&w#)0xH N- x"\t~6ØXQ\XhEgr7%i$Y,z'xr[hY-9p\Xu;fP)ZSw%/ݽq buO^O", ѼZݯk=9t laG\Q~#1Qƛ(tׄW`i.kȆBO6NCwH.TTťV@Xl[]Fl<^8V~ȮWhNZK-Z .$qT&8,I\4axѲIrin*. g!t19 h8Q޶Yl]β#[j~ 13$ ! |z)0J6u5)L\[U FĢPºk=U&wZLgj3e ^wҷ"z TV-0 WV\V#7gbs8ڍWȂՑ'[ cwG#H\}4V7kID-Q?K:8KLK˲0ӬL%]~[^u8( )_t0})r_]< +;5?&Ikl/B<|'nڶ)&arU" w{,-+n)>/1>AippB=W6)I wN5N2G0``n\ҫL ;!xKKvsYE5ֽTE CF'%^SY/ Hv5b`*~;CЯYpΓ^H,v^hBN> 88 ieL 4Қ卐H4ےO9Etx{ M歺5 s!َylORTJDl_u 'gd/NC XtR@q[kmցC%mTRwUuiՊ? $)Hx8;,Osw0).7Y@~JC-Hq,E>BW]lJ]_;=gLU.2&-ʶm0=,۪BBBF w%Z"Bԫ2Nj'\ $z'Mt!i!qw=v<:걙{oI8'ɱd°ygPe5;Θnf s{~PO+;*y^]y-w{M?~2OƮxw͋?s;{ؓ-gY<¨z1 p&>YdI̮5 ډT+t^ivL^|-7OeԄM NHHpzړ/j%K*)@(_/GRп{^`K>Z$$)\]T-Ą7s?R/)-nٛ@Wٱ&`o>Yߠf'󾷋7wF;+, MzتUd~ m^_m͸D~æVƕ?vRDj§|U`q/asc9Kفϊ':Z@ 7Hqz ߵ8dRm'WiK(vZb/uVEquk'f:Ya5riANZrn k'@.IGDŧㄨ{`ʣc /$f&y!ƀ%T%!?;0j $-;HDhcl٪Ǫ<.-9 ƞQu G};/_-|XcJͣY=0x mQ'wI})}޺\Qn+uX?A7=J^JމS:  $~<"!U/VK[ٱj5?nM_uN2w̗߹߮ Ȣ7RI%FOe\7$ࣗ= L {i u+!d-9׮;Й)v [ݨ~F2PpS!IaynCS:)OUN7{@L7gON!jy6=6l9 X${ܠؿ4Rz; I38>A@vY^ny$w(f.e37x'+S|%?N pZI{:Grkن('IgEQ `0h\ U ZQ^5JWYL{8( Ɯt`V̌ Ab#f)K AYkQO-P@5EGy@\SKp5sK}p^|W=ҊsxfO)zdzy؛W u suCDV7FUр预N`UgNFpu޲ d7B[c22Uqd̚%` 7>|- 7p{!A^$4b2‹a}8|oW|!]hSR[ ~/u%n >K88d=sԣqffXu` P}TEl>1,{]$Wf ˿0Wʈu67ʛܔ̙due¹/ 3 JuQ#xSOEPDn삆H@J RtFR4[V70>yنĨh~f6f $"o?P[j0ys$1t`e85\D]q8N!fQs9Pgujw/  @,m+[ & `u2ƅ_2CnِZVze 2y8 aSZyHӽ҇ڑ0p  39 ^k.[20iJŶ+(Z.آ"#2$-oQ2$e@"kۑ~83ۻO02?5[,YGƹpY0d֚J{rDxjl(r:Pr~{eP[S2BG@zmⰨ3.[EQ{ ]! *4s)d1^``'ZdgA i"ǒbam{ 5duA\JO_$b5һ@Vb,\(@PNq3Tx?(CLMYFId`ޤT!BK_]A9jO 1;uQ5xUIlmSոRՊFɸ(XYK&M@ La.0~U! bdW`kfJO22ox5a6C< 8D?"  d|'"5aFnUwKrJhnu;<)t΍AD@w[lR͵' |8e>GCeHKNkji7@T_݉(a0DЧWX)j$$ʤ\gE.JNV a%P v-=djh4?ɜ#x >1]dsd+kȝͩ~}]̓K.,@qT̲ЋC&HVx5@U+OcogPcoyiiUJuxO6wCvrKTa!4Jd4&Yt~;쵮(;C-}Rg*c& KqY 6bT7:a9ѢB\!%/N&E$IH +$.ʖ2n$%äEv$F_> =UlDkqem0+[jנޠrjLweeճN82Z.t0 ;PoѯTΈ7 >5SX^AiA .bDzrI3k%4MQcr@M;#NER=f8b]{}/s+pccJ (%Sy@u7Zf%` <,)/:a/ 0}/y\(g{[|?S0 :N*Ӵ]DFD?JYސFR у,)Z =8tF<j VBes XW>coz" Df92.*.9텐tm/{`&_Q+_-$C.T%TS?Sd=y"gA+zo0-q 8맊L]]$)YHfiQzZ8 cáKL)?HBu1CEDhdH 8_>ՎOmTO?G %in*܃5M:q5e@|(ѝi#љ(ݮ12-P6ߒa ˟7f5i /{\n2 ,Y-h6_=;1)\m73rPJCVMw-^?-K5 0Wռ(!D2ũ'% '"΀h 6tȁu[jaq]Ӟ qaH/s cv9qK6&y@?3 M|"Rjwt\Y?&C9i ݸQv V'E,a,:nrTG'X-<ډH]ܱN@8&Ы4$&c75ϒ,+!2@gլ'xGI$'W mx"DZmǯ%NZ8 ZmǪ ;Z{(*= ru($(zE,9x?NVe488M"慍Z3dR2Ӆԙv8ߌYZG77ys)G2U ^ݱHP@0}1?tm#Ú,PV ӑ XιMw$wv&{j9(@p`GJ p3:7~`JO0˴6· !8sE8LVl5gHv>{B/Çjp ĸZW>aCHOIlo6 y#*+}>qu"%,Wcd Fm^6ܣ~Aޥ\xvRB_oW=.5@JG->J6? Wm$u;"F=•- OA%9ҹvRS jtN'Wbz V` `CWm#u)zVTd%a1`BD\EK=UUi z)IR+gڻZ>򘹩e9j"fuܻļai*_3U7'&@:3Ķ1ifyp)0cO#uVmq? TA-Mـт'B+o2@O}WRF,SIA hѪi*B3`cn H~93?,&; 6CE9&O&X\V8k~+O폜1Ô! mw> Sgv|r$mY׺XF-49Oґ>!8P,AzPs*OO<CaǺMS8ӻ/Cw˳("IZ/a2€Hv6V<|h&J?h掉wVI|cuhW j|? \z#.!it|wrA&M3~04x,B ri= is'ؕ[P)&} 7cJ}w[ ȏt^Z@I3yP[_oLMJӜ{yu}s_2FfC-(Z_rG~Дzau(Gi-8.rQIi/x W -B yN+ Qe+:l{G+5&p--^pBۯNa _[} /s Ջ bܸ˭* lL #l.aG_;=g4_hLW914Ɉ_MЎߝsKF5Q'yl3 ǞPJ2KkqSj0݊:  67SrF3ٲU". x SG~ /L.wz^LZ"6x#, sg)Z,]9 AYіO^HJ V􌩇Uܞ' 2DBfj){{G~Lo-بxxS)_1OZخ(Elo;ndL G>R>x:-U>JgR@Mnшe8JX@A4LQY6}iG7:f[E%nvzc=G7Y`I1 kx1jba=wy{ +@$T]7 (]3S*׉BLXY \LwZȦNPY*Yj"0 =RgbH:}Ge=jK]sb0f/jNx=s"h/V{P)-}uYg0=HmHt^XeBoѐڊˊdNJ L<J]LQe78>cRۊ= Nkuzø 5Mn>Ӑp}i.fb*Ar8Oz_a~Xz(pԔ% g0 }ȱ(u2L6PO (J%{%cg:撋$;N|d^ PE*&߁=L@?sP/@(t[i2"Aə"nUyJ]#C:W`#Dc PsX)@QerwVN!D-XIPJt:C0Wk~7is2SEpɛl^6BVׯh*pb*ׯ>@o'珪c8vu\%::A*(k8.ZH(½ VV+vVbsBE7 w9 Hͦ'lpUc0s(ql~㧠]"BIXposjl?y`Ts$K?us*07s#;rrO,LT1X&R/L3b akB$:Ґ/7S)RĂ]r'٣5C3iD{>#7q)gM4x0&= KaMN gG%£J~Lh=._&1H(n|Tdrpq,|)#cJi\R4,̗F0⺮7+p%rs^8aT`Y? Pv0/%`!QO*zIHlQ>E[ZjPue|i)r&r4ղw*O3\KurF8I0`)#pi4hݡ4EixL;!ER?1"!umw#O׍*LIu_Ke/){F>R1W+3Јhߤ6'\[e`CrDg*AP_ bGik14VjXBW`df{:kr2=S%4+#[s }~)UWń2eɛ'Wŀlj Q&A4K,Ѡ S8i|RQܩ.{7Zqԑ2aF> zZkM?wQDN{L Y9=VX'hQTu!߾W\mGǸe@gwS][E|ԙ qoĀ8qM%tF7)YR  gp @)`O6u4TPǻ[S^̏aFN$#Tl*ƾ`|̸( h;5a`KX TPi~j=4]7 cHRӆ6@J`1ifp*Ko~C8/K)67P!'G)JOR_z&:\~V64)Ai L8f`3TFd*4@ٹɂͷX ˫\BDp:9})7[X&+e{ MX}? n)ھd ҙRΨ2᩺c+J0/(SWШ<9l0IJ|EQC(k&PT8y [{\B vP(Tv_7./oED[`X^"x lf;œG295v>h4p%k6x:eOg\h%S D5ҿ?ΥЎ<qAFM\zKN twClviWk `(80q*5Ծ,YzFjǛnɓ98>B.BE5v͞Z`N8"w(Lye&l3϶bhь> g9uG_B2aj'`n'}/uJ ߔ  m+0/S+AKVˣ5ɽ bHp` ~;MQFubۘ,D& '[ޤ'hٷ<5iUIi$Ά^J1À`Xٴ:6HTIuZ䫄<ɓ`߈IZicsdlrG C>m'0n2i`D JKk%I-a[,`}+1&_Y'v4tF'3\dLjpK&s/s,\qpq w|ol},`Hi)=r /VhWHo)>ô>x # 2ljI320w"Dzj_'BU I@漷\6"8M~8\BD2sYVLli[is]qG(KbW7ќӍFb z!=M;#?ɂA;/cMi*;>'HIM%-ABA692cK:JS S%U"QK/7MKk^vO% ca[o8v8zڡ6J0SXQ]2֛tLqZ~|R| 1]̄->UV뤥oکI9uto|Vo)$m LA{^Ӵ Umܢ|<}[~Oȶ!'VT!AĎ"l(R>zrO|GΖ6!LM(PB+/z4zg2(lɓB K^/r7y2 hW P=q+,fWŊA*?w|6dߖ^CHdZޗAxXjpKzޕކr\)ևag}K}s޶}ڊEJ+Ƙ; Fek&.[# #[Ty`ǎmg9wj+pXZKN [`kLC-EbX\ ,5d!RϹ+:D?j'ǧ%2H*©iVX(}rW cMX!/"!%*<5?D,ȽPD]; Mf+_50Kpת( /a>K3F n/w4Nx ;cS)a4Ƈ)iYpnέV}e T߫$ }(-UBNqJe1C$J^rަ1^U"Døb 9LaYP=9&-josӎWD!uKuBߩKP3iiDY:C*VN+mG{oAG]>.j08U$q)_#t@SAuW49w雉ﺸ[Hߏ_|RKiug\Vcf1qpk.&kƥH8Ѡw˿#ɑ7]>3YʗvبA|fxL :6ݞ̐O74td'hZLfS*|Q3VI0]RToho/Vts2SO>w+O@M7 W@輜:5Xӂ RiW]}Y:u蓧 Ïs@֣䜿xh^念-q*HaWZ; o9._N8Ȑ<~R%m]HVxjaq虊qӍ<⊶ laf.=B .0(<)J-, P&&#]Չ lHu}#X9=qVs(sF7w\wc9*O6g~)Ka\az jg`.Z#R)!fӊX~;'vo=E+| qte&:p//%hZq: )b(ťoXɔmʊ+&C8ǹx/  w>h?E[) 9, 2M:r<.]TcNőzDӰNYt' x(o> _onD֔`#7B1szۨ@ ttTU®잯e 2#;k
Server nameLocationUrls
Server nameLocationBandwidthSourceUrls
+[% c.model('Mirrors').bandwidth_name(item.bandwidth) | html %] + +[% item.syncfrom | html %] + +[% IF item.public %] [% FOREACH u = urls %] +[% IF u.valid %] [% u.protocol %] +[% ELSE %] +[% u.protocol %] +[% END %] +[% END %] +[% ELSE %] +Private access [% END %]