aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier Thauvin <nanardon@mageia.org>2010-09-27 10:51:18 +0000
committerOlivier Thauvin <nanardon@mageia.org>2010-09-27 10:51:18 +0000
commit7c16f1d36bd584c36f187bd0401fb39e94771765 (patch)
tree2bf89b42bc81f1d4b4f8c557a717cbc25d446195
parent57beda663bb4448ad838b599e5df5bf50375a5cb (diff)
downloadmgamirrors-7c16f1d36bd584c36f187bd0401fb39e94771765.tar
mgamirrors-7c16f1d36bd584c36f187bd0401fb39e94771765.tar.gz
mgamirrors-7c16f1d36bd584c36f187bd0401fb39e94771765.tar.bz2
mgamirrors-7c16f1d36bd584c36f187bd0401fb39e94771765.tar.xz
mgamirrors-7c16f1d36bd584c36f187bd0401fb39e94771765.zip
- code base
-rw-r--r--lib/MGA/Mirrors.pm5
-rw-r--r--lib/MGA/Mirrors/Controller/New.pm125
-rw-r--r--lib/MGA/Mirrors/Controller/Root.pm2
-rw-r--r--lib/MGA/Mirrors/DB.pm364
-rw-r--r--lib/MGA/Mirrors/Model/Mirrors.pm33
-rw-r--r--lib/MGA/Mirrors/View/TT.pm43
-rw-r--r--lib/MGA/Mirrors/View/TTBlock.pm40
-rw-r--r--root/html/includes/footer.tt3
-rw-r--r--root/html/includes/header.tt10
-rw-r--r--root/html/includes/mirrorslist.tt40
-rw-r--r--root/html/includes/new/checkurl.tt1
-rw-r--r--root/html/includes/new/confirm.tt6
-rw-r--r--root/html/includes/new/host_information.tt19
-rw-r--r--root/html/includes/new/invalid_mirror.tt1
-rw-r--r--root/html/includes/new/invalid_uri.tt1
-rw-r--r--root/html/includes/new/mirror_exists.tt1
-rw-r--r--root/html/includes/new/overlap_hosts.tt1
-rw-r--r--root/html/includes/new/success.tt3
-rw-r--r--root/html/includes/new/unsupported_protocol.tt1
-rw-r--r--root/html/includes/new/url_form.tt5
-rw-r--r--root/html/pages/index.tt6
-rw-r--r--root/html/pages/new/index.tt5
-rw-r--r--root/static/style.css1
-rw-r--r--t/controller_New.t9
-rw-r--r--t/model_Mirrors.t7
-rw-r--r--t/view_TT.t7
26 files changed, 738 insertions, 1 deletions
diff --git a/lib/MGA/Mirrors.pm b/lib/MGA/Mirrors.pm
index 03de5c6..dce921a 100644
--- a/lib/MGA/Mirrors.pm
+++ b/lib/MGA/Mirrors.pm
@@ -16,6 +16,10 @@ use Catalyst qw/
-Debug
ConfigLoader
Static::Simple
+ Prototype
+ Session
+ Session::State::Cookie
+ Session::Store::FastMmap
/;
extends 'Catalyst';
@@ -36,6 +40,7 @@ __PACKAGE__->config(
name => 'MGA::Mirrors',
# Disable deprecated behavior needed by old applications
disable_component_resolution_regex_fallback => 1,
+ default_view => 'TT',
);
# Start the application
diff --git a/lib/MGA/Mirrors/Controller/New.pm b/lib/MGA/Mirrors/Controller/New.pm
new file mode 100644
index 0000000..fbb1fbe
--- /dev/null
+++ b/lib/MGA/Mirrors/Controller/New.pm
@@ -0,0 +1,125 @@
+package MGA::Mirrors::Controller::New;
+use Moose;
+use namespace::autoclean;
+use URI;
+
+BEGIN {extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+MGA::Mirrors::Controller::New - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+
+ if ($c->req->param('url')) {
+ my $uri = URI->new($c->req->param('url'));
+ $c->stash->{uri} = $uri;
+
+ if (!($uri->can('host') && $uri->can('scheme'))) {
+ $c->stash->{subtemplate} = 'new/invalid_uri.tt';
+ return;
+ }
+
+ if (!$c->model('Mirrors')->get_protocol_info($uri->scheme)) {
+ $c->stash->{subtemplate} = 'new/unsupported_protocol.tt';
+ return;
+ }
+
+ my $urls = $c->model('Mirrors')->find_urls(
+ { protocol => $uri->scheme,
+ hostname => $uri->host, });
+ if (@{$urls || []}) {
+ $c->stash->{exists_url} = $urls;
+ $c->stash->{subtemplate} = 'new/mirror_exists.tt';
+ return;
+ }
+
+ if (!$c->model('Mirrors')->mirror_validity($uri)) {
+ $c->stash->{subtemplate} = 'new/invalid_mirror.tt';
+ return;
+ }
+
+ if (my @overlap_hosts = $c->model('Mirrors')->find_host_ip_overlap($uri->host)) {
+ $c->stash->{overlap_hosts} = \@overlap_hosts;
+ $c->stash->{subtemplate} = 'new/overlap_hosts.tt';
+ return;
+ }
+
+ my @ips = $c->model('Mirrors')->host_ips($uri->host);
+
+ $c->stash->{location} = $c->model('Mirrors')->locate_ips(@ips);
+
+ 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)) {
+ $c->session->{hostinfo}{$_} = $c->req->param($_);
+ }
+ } else {
+ $c->stash->{subtemplate} = 'new/host_information.tt';
+ return;
+ }
+
+ $c->session->{new_uri} = $uri;
+ $c->stash->{template} = 'new/confirm.tt';
+ }
+}
+
+sub confirm :Path :Args(1) {
+ my ( $self, $c ) = @_;
+ $c->stash->{current_view} = 'TTBlock';
+ if ($c->session->{new_uri} && $c->req->param('confirm')) {
+ my $uri = URI->new($c->session->{new_uri});
+ my $mirror = $c->model('Mirrors')->find_mirrors(
+ { hostname => $uri->host, });
+ if (!@{$mirror || []}) {
+ if ($c->session->{hostinfo}) {
+ $c->model->add_or_update_host(
+ $uri->host,
+ city => $c->session->{hostinfo}{city},
+ country => $c->session->{hostinfo}{country},
+ );
+ } else {
+ return;
+ }
+ }
+ if ($c->model('Mirrors')->add_or_update_url($uri)) {
+ $c->session->{hostinfo} = undef;
+ $c->session->{new_uri} = undef;
+ $c->stash->{template} = 'new/success.tt';
+ $c->model('Mirrors')->db->commit;
+ return;
+ }
+ }
+}
+
+=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/Root.pm b/lib/MGA/Mirrors/Controller/Root.pm
index 5decc3a..bbc4d4d 100644
--- a/lib/MGA/Mirrors/Controller/Root.pm
+++ b/lib/MGA/Mirrors/Controller/Root.pm
@@ -30,7 +30,7 @@ sub index :Path :Args(0) {
my ( $self, $c ) = @_;
# Hello World
- $c->response->body( $c->welcome_message );
+ #$c->response->body( $c->welcome_message );
}
=head2 default
diff --git a/lib/MGA/Mirrors/DB.pm b/lib/MGA/Mirrors/DB.pm
new file mode 100644
index 0000000..78b6862
--- /dev/null
+++ b/lib/MGA/Mirrors/DB.pm
@@ -0,0 +1,364 @@
+package MGA::Mirrors::DB;
+
+# $Id$
+
+use strict;
+use warnings;
+use Config::IniFiles;
+use URI;
+use DBI;
+use File::Temp qw(tempfile);
+use Net::DNS;
+
+sub configfile { '/etc/mga-mirror.ini' }
+
+sub new {
+ my ($class) = @_;
+
+ my $conf = (-f './mga-mirror.ini')
+ ? Config::IniFiles->new(-file => './mga-mirror.ini')
+ : Config::IniFiles->new(-file => configfile())
+ or return;
+
+ my $db = DBI->connect(
+ 'dbi:Pg:' . $conf->val('db', 'pgconn', ''),
+ $conf->val('db', 'user') || undef,
+ $conf->val('db', 'password') || undef,
+ {
+ AutoCommit => 0,
+ PrintError => 1,
+ }
+ ) or return;
+
+ bless {
+ db => $db,
+ conf => $conf,
+ }, $class;
+}
+
+sub host_ips {
+ my ($self, $hostname) = @_;
+
+ my $resolver = Net::DNS::Resolver->new;
+ my @addresses;
+ foreach my $type (qw'A AAAA') {
+ my $packet = $resolver->search($hostname, $type) or next;
+ foreach ($packet->answer) {
+ $_->type eq $type or next;
+ push(@addresses, $_->address);
+ }
+ }
+ @addresses;
+}
+
+sub db { $_[0]->{db} }
+
+sub locate_ips {
+ my ($self, @ips) = @_;
+
+ my $find = $self->db->prepare(q{
+ select countries.* from geoip
+ join countries on geoip.code = countries.code
+ where ipmin <= $1 and ipmax >= $1
+ });
+
+ foreach (@ips) {
+ $find->execute($_);
+ my $res = $find->fetchrow_hashref;
+ if ($res) {
+ $find->finish;
+ return $res;
+ }
+ }
+
+ return;
+}
+
+sub country_list {
+ my ($self) = @_;
+ my $list = $self->db->prepare(q{
+ select * from countries order by name
+ });
+ $list->execute;
+ return $list->fetchall_arrayref({});
+}
+
+sub mirror_validity {
+ my ($self, $uri) = @_;
+ my $listf = $self->db->prepare(q{
+ select * from global_files
+ });
+ $listf->execute;
+ while (my $res = $listf->fetchrow_hashref) {
+ my $furi = URI->new($uri . $res->{relpath});
+ $self->_check_url($furi) or return;
+ }
+
+ 1;
+}
+
+sub check_distributions {
+ my ($self) = @_;
+
+ my $uneeded_check = $self->db->prepare(q{
+ select * from mirrors_distributions where
+ lastcheck > now() - '6 hours'::interval
+ });
+ $uneeded_check->execute();
+ my $uch = $uneeded_check->fetchall_hashref([ qw(urlskey distributionkey) ]);
+
+ my $listd = $self->db->prepare(q{
+ select * from urls, distributions
+ where urls.valid = true
+ });
+
+ my $addstatus = $self->db->prepare(q{
+ insert into mirrors_distributions (urlskey, distributionkey, exists)
+ values (?,?,?)
+ });
+
+ my $updstatus = $self->db->prepare(q{
+ update mirrors_distributions set lastcheck = now(), exists = ?
+ where urlskey = ? and distributionkey = ?
+ });
+
+ my %urls_status = ();
+
+ my $updurl = $self->db->prepare(q{
+ update urls set lastcheck = now(), valid = ?
+ where key = ?
+ });
+
+ $listd->execute();
+ while (my $res = $listd->fetchrow_hashref) {
+ $uch->{$res->{key}}{$res->{dkey}} and next;
+ my $url = $self->fmt_url($res);
+ if (!exists($urls_status{$res->{key}})) {
+ my $ok = $self->mirror_validity($url);
+ $updurl->execute($ok ? 1 : 0, $res->{key});
+ $urls_status{$res->{key}} = $ok;
+ }
+ $urls_status{$res->{key}} or next;
+ my $furi = URI->new(join('/', $url, $res->{relpath}, $res->{relfile}));
+ my $exists = $self->_check_url($furi);
+ if ($updstatus->execute($exists, $res->{key}, $res->{dkey}) == 0) {
+ $addstatus->execute($res->{key}, $res->{dkey}, $exists);
+ }
+ $self->db->commit;
+ }
+}
+
+sub _check_url {
+ my ($self, $furi) = @_;
+ 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 eq 'rsync' ? "rsync --timeout 4 -q " . $furi->as_string . " $filename" : '';
+ my $ok = (system($cmd) == 0);
+ unlink($filename);
+ return $ok
+}
+
+sub get_protocol_info {
+ my ($self, $protocol) = @_;
+ my $get = $self->db->prepare(q{
+ select * from protocol where name = ?
+ });
+ $get->execute($protocol);
+ my $res = $get->fetchrow_hashref;
+ $get->finish;
+ $res;
+}
+
+sub find_mirrors {
+ my ($self, $filters, $key) = @_;
+
+ my $query = q{
+ select * from hosts
+ left join countries on countries.code = hosts.country
+ where hosts.hostname in (select hostname from urls %s)
+ %s
+ };
+
+ my (@mvals, @uvals);
+ my (@mw, @uw);
+ if (keys %{ $filters || {}}) {
+ foreach (keys %$filters) {
+ $filters->{$_} or next;
+ if (my $field = {
+ hostname => 'hosts.hostname',
+ country => 'countries.code',
+ continent => 'countries.contienent_code',
+ }->{$_}) {
+ push(@mw, sprintf('%s = ?', $field));
+ push(@mvals, $filters->{$_});
+ }
+ if (my $field = {
+ protocol => 'protocol',
+ }->{$_}) {
+ push(@uw, sprintf('%s = ?', $field));
+ push(@uvals, $filters->{$_});
+ }
+ }
+ }
+ my $list = $self->db->prepare(sprintf(
+ $query,
+ (@uw ? 'where ' . join(' and ', @uw) : ''),
+ (@mw ? 'and ' . join(' and ', @mw) : ''),
+ ));
+ $list->execute(@uvals, @mvals);
+ return $list->fetchall_arrayref({});
+}
+
+sub _find_urls {
+ my ($self, $filters, $key) = @_;
+
+ my $query = q{
+ select urls.* from urls join
+ hosts on hosts.hostname = urls.hostname
+ };
+ my @vals;
+ if (keys %{ $filters || {} }) {
+ $query .= ' where ';
+ my @w;
+ foreach (keys %$filters) {
+ my $field = {
+ hostname => 'hosts.hostname',
+ protocol => 'urls.protocol',
+ }->{$_} or next;
+
+ push(@w, sprintf('%s = ?', $field));
+ push(@vals, $filters->{$_});
+ }
+ $query .= join(' and ', @w);
+ }
+ my $list = $self->db->prepare($query);
+ $list->execute(@vals);
+ return $list->fetchall_arrayref({});
+}
+
+sub find_host_ip_overlap {
+ my ($self, $hostname) = @_;
+
+ my @addresses = $self->host_ips($hostname);
+
+ my $list = $self->db->prepare(q{
+ select * from ips where ip = any(?)
+ and hostname != ?
+ });
+ $list->execute(\@addresses, $hostname);
+ my $res = $list->fetchall_hashref('hostname');
+ return keys %{ $res };
+}
+
+sub add_or_update_host {
+ my ($self, $hostname, %info) = @_;
+
+ my (@fields, @vals);
+ while (my ($field, $val) = each(%info)) {
+ push(@fields, $field);
+ push(@vals, $val);
+ }
+ if (keys %info) {
+ my $upd = $self->db->prepare(sprintf(q{
+ update hosts set %s where hostname = ?
+ }, join(', ', map { "$_ = ?" } @fields)));
+ if ($upd->execute(@vals, $hostname) == 0) {
+ my $add = $self->db->prepare(sprintf(q{
+ insert into hosts (%s) values (%s)
+ }, join(', ', (@fields, 'hostname')),
+ join(',', ('?') x (scalar(@fields)+1))
+ ));
+ $add->execute(@vals, $hostname) or do {
+ $self->db->rollback;
+ return;
+ };
+ }
+ }
+
+ $self->update_host_ips($hostname);
+
+ 1;
+}
+
+sub add_or_update_url {
+ my ($self, $uri) = @_;
+ if (!ref $uri) {
+ $uri = URI->new($uri);
+ }
+
+ my $update = $self->db->prepare(q{
+ update urls set path = ?, port = ?
+ where hostname = ? and protocol = ?
+ });
+
+ if ($update->execute(
+ $uri->path, $uri->port == $uri->default_port ? undef : $uri->port,
+ $uri->host, $uri->scheme
+ ) == 0) {
+ my $add = $self->db->prepare(q{
+ insert into urls (path, port, hostname, protocol)
+ values (?,?,?,?)
+ });
+ $add->execute($uri->path, $uri->port == $uri->default_port ? undef : $uri->port,
+ $uri->host, $uri->scheme) or do {
+ $self->db->rollback;
+ return;
+ }
+ }
+
+ 1;
+}
+
+sub update_host_ips {
+ my ($self, $hostname) = @_;
+
+ my @addresses = $self->host_ips($hostname);
+ my $delete = $self->db->prepare(
+ q{delete from ips where hostname = ?
+ and ip != any(?)
+ }
+ );
+
+ $delete->execute($hostname, [ @addresses ]);
+
+ my $getip = $self->db->prepare(q{
+ select 1 from ips where hostname = ? and ip = ?
+ });
+ my $addip = $self->db->prepare(q{
+ insert into ips (hostname, ip) values (?,?)
+ });
+ foreach (@addresses) {
+ if ($getip->execute($hostname, $_) == 0) {
+ $addip->execute($hostname, $_);
+ }
+ $getip->finish;
+ }
+
+ 1;
+}
+
+sub find_urls {
+ my ($self, $filters, $key) = @_;
+ return [
+ map { $_->{url} = $self->fmt_url($_); $_ }
+ @{ $self->_find_urls($filters) || []}]
+}
+
+sub fmt_url {
+ my ($self, $dburl) = @_;
+
+ my $uri = URI->new(
+ sprintf('%s://%s%s',
+ $dburl->{protocol},
+ $dburl->{hostname},
+ $dburl->{path} || '/',
+ )
+ );
+ $uri->port($dburl->{port});
+
+ return $uri->as_string;
+}
+
+1;
diff --git a/lib/MGA/Mirrors/Model/Mirrors.pm b/lib/MGA/Mirrors/Model/Mirrors.pm
new file mode 100644
index 0000000..c1e1852
--- /dev/null
+++ b/lib/MGA/Mirrors/Model/Mirrors.pm
@@ -0,0 +1,33 @@
+package MGA::Mirrors::Model::Mirrors;
+use Moose;
+use namespace::autoclean;
+
+extends 'MGA::Mirrors::DB', 'Catalyst::Model';
+
+=head1 NAME
+
+MGA::Mirrors::Model::Mirrors - Catalyst Model
+
+=head1 DESCRIPTION
+
+Catalyst Model.
+
+=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
+
+sub new {
+ my ($class) = @_;
+ return bless(MGA::Mirrors::DB->new(), $class);
+}
+
+__PACKAGE__->meta->make_immutable(inline_constructor => 0);
+
+1;
diff --git a/lib/MGA/Mirrors/View/TT.pm b/lib/MGA/Mirrors/View/TT.pm
new file mode 100644
index 0000000..f5e2a50
--- /dev/null
+++ b/lib/MGA/Mirrors/View/TT.pm
@@ -0,0 +1,43 @@
+package MGA::Mirrors::View::TT;
+
+use strict;
+use warnings;
+use MGA::Mirrors;
+
+use base 'Catalyst::View::TT';
+
+__PACKAGE__->config(
+ TEMPLATE_EXTENSION => '.tt',
+ render_die => 1,
+ INCLUDE_PATH => [
+ MGA::Mirrors->path_to( 'root', 'html', 'includes' ),
+ MGA::Mirrors->path_to( 'root', 'html', 'pages' ),
+ ],
+ PRE_PROCESS => 'header.tt',
+ POST_PROCESS => 'footer.tt',
+);
+
+=head1 NAME
+
+MGA::Mirrors::View::TT - TT View for MGA::Mirrors
+
+=head1 DESCRIPTION
+
+TT View for MGA::Mirrors.
+
+=head1 SEE ALSO
+
+L<MGA::Mirrors>
+
+=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
+
+1;
diff --git a/lib/MGA/Mirrors/View/TTBlock.pm b/lib/MGA/Mirrors/View/TTBlock.pm
new file mode 100644
index 0000000..468a063
--- /dev/null
+++ b/lib/MGA/Mirrors/View/TTBlock.pm
@@ -0,0 +1,40 @@
+package MGA::Mirrors::View::TTBlock;
+
+use strict;
+use warnings;
+use MGA::Mirrors;
+
+use base 'Catalyst::View::TT';
+
+__PACKAGE__->config(
+ TEMPLATE_EXTENSION => '.tt',
+ render_die => 1,
+ INCLUDE_PATH => [
+ MGA::Mirrors->path_to( 'root', 'html', 'includes' ),
+ ],
+);
+
+=head1 NAME
+
+MGA::Mirrors::View::TT - TT View for MGA::Mirrors
+
+=head1 DESCRIPTION
+
+TT View for MGA::Mirrors.
+
+=head1 SEE ALSO
+
+L<MGA::Mirrors>
+
+=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
+
+1;
diff --git a/root/html/includes/footer.tt b/root/html/includes/footer.tt
new file mode 100644
index 0000000..2a1c949
--- /dev/null
+++ b/root/html/includes/footer.tt
@@ -0,0 +1,3 @@
+<!-- $Id$ -->
+</body>
+</html>
diff --git a/root/html/includes/header.tt b/root/html/includes/header.tt
new file mode 100644
index 0000000..5319e52
--- /dev/null
+++ b/root/html/includes/header.tt
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<!-- $Id$ -->
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+[% c.prototype.define_javascript_functions %]
+</head>
+
+<body>
diff --git a/root/html/includes/mirrorslist.tt b/root/html/includes/mirrorslist.tt
new file mode 100644
index 0000000..fd855f5
--- /dev/null
+++ b/root/html/includes/mirrorslist.tt
@@ -0,0 +1,40 @@
+<form action="[% c.uri_for() %]">
+<select name="country">
+<option value="">--</option>
+[% FOREACH country = c.model('Mirrors').country_list %]
+<option value="[% country.code %]" [% "selected=select" IF country.code == c.req.param('country') %]>[% country.name | html %]</option>
+[% END %]
+</select>
+<input type="submit">
+</form>
+
+[% db = c.model('Mirrors') %]
+[% mirrorslist = db.find_mirrors({
+ 'protocol' => c.req.param('protocol'),
+ 'country' => c.req.param('country'),
+}) %]
+[% FOREACH item = mirrorslist %]
+[% IF loop.first %]
+<table border=1>
+<tr><th>Server name</th><th>Location</th><th>Urls</th><tr>
+[% END %]
+<tr>
+[% urls = db.find_urls({ "hostname" => item.hostname }) %]
+<td>
+[% item.hostname | html %]
+</td>
+<td>
+[% IF item.name %]
+[% item.continent | html %], [% item.name | html %]
+[% END %]
+</td>
+<td>
+[% FOREACH u = urls %]
+<a href="[% u.url %]">[% u.protocol %]</a>
+[% END %]
+</td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
diff --git a/root/html/includes/new/checkurl.tt b/root/html/includes/new/checkurl.tt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/root/html/includes/new/checkurl.tt
@@ -0,0 +1 @@
+
diff --git a/root/html/includes/new/confirm.tt b/root/html/includes/new/confirm.tt
new file mode 100644
index 0000000..b3eb3f3
--- /dev/null
+++ b/root/html/includes/new/confirm.tt
@@ -0,0 +1,6 @@
+<div id="foo">
+[% c.prototype.form_remote_tag({ url => c.uri_for('confirm'), update => 'foo' } ) %]
+<input type="hidden" name="confirm" value="1">
+<input type="submit">
+</form>
+</div>
diff --git a/root/html/includes/new/host_information.tt b/root/html/includes/new/host_information.tt
new file mode 100644
index 0000000..78c04c2
--- /dev/null
+++ b/root/html/includes/new/host_information.tt
@@ -0,0 +1,19 @@
+Enter Info for [% uri.host %]
+
+<form action="[% c.uri_for() %]" method="POST">
+<input type="hidden" name="url" value="[% uri | html %]">
+<input type="hidden" name="hostinfo" value="1">
+
+[% location.name %] / [% location.continent %]<br>
+
+Country:
+<select name="country">
+<option value="">--</option>
+[% FOREACH country = c.model('Mirrors').country_list %]
+<option value="[% country.code %]" [% "selected=select" IF country.code == location.code %]>[% country.name | html %]</option>
+[% END %]
+</select>
+
+City: <input type="text" name="city">
+<input type="submit">
+</form>
diff --git a/root/html/includes/new/invalid_mirror.tt b/root/html/includes/new/invalid_mirror.tt
new file mode 100644
index 0000000..f22d0f3
--- /dev/null
+++ b/root/html/includes/new/invalid_mirror.tt
@@ -0,0 +1 @@
+This url seems to not contain a valid mirror.
diff --git a/root/html/includes/new/invalid_uri.tt b/root/html/includes/new/invalid_uri.tt
new file mode 100644
index 0000000..87ced3c
--- /dev/null
+++ b/root/html/includes/new/invalid_uri.tt
@@ -0,0 +1 @@
+[% uri %] is not a valid url.
diff --git a/root/html/includes/new/mirror_exists.tt b/root/html/includes/new/mirror_exists.tt
new file mode 100644
index 0000000..f891993
--- /dev/null
+++ b/root/html/includes/new/mirror_exists.tt
@@ -0,0 +1 @@
+<p>The url [% uri | html %] has same server and same protocol than [% exists_url.0.url %].</p>
diff --git a/root/html/includes/new/overlap_hosts.tt b/root/html/includes/new/overlap_hosts.tt
new file mode 100644
index 0000000..3d60603
--- /dev/null
+++ b/root/html/includes/new/overlap_hosts.tt
@@ -0,0 +1 @@
+This server seems to be the same than [% overlap_hosts.join(', ') | html %].
diff --git a/root/html/includes/new/success.tt b/root/html/includes/new/success.tt
new file mode 100644
index 0000000..56d757d
--- /dev/null
+++ b/root/html/includes/new/success.tt
@@ -0,0 +1,3 @@
+Host added
+
+[% INCLUDE 'new/url_form.tt' %]
diff --git a/root/html/includes/new/unsupported_protocol.tt b/root/html/includes/new/unsupported_protocol.tt
new file mode 100644
index 0000000..7552c82
--- /dev/null
+++ b/root/html/includes/new/unsupported_protocol.tt
@@ -0,0 +1 @@
+Unsuported protocol
diff --git a/root/html/includes/new/url_form.tt b/root/html/includes/new/url_form.tt
new file mode 100644
index 0000000..f8840b9
--- /dev/null
+++ b/root/html/includes/new/url_form.tt
@@ -0,0 +1,5 @@
+<form action="[% c.uri_for() %]" method="POST">
+Enter the url to the top level mirror tree:<br>
+<input type="text" name="url" size=40 value="[% c.req.param('url') | html %]">
+<input type=submit>
+</form>
diff --git a/root/html/pages/index.tt b/root/html/pages/index.tt
new file mode 100644
index 0000000..f1f0251
--- /dev/null
+++ b/root/html/pages/index.tt
@@ -0,0 +1,6 @@
+<!-- $Id$ -->
+<p>Mageia mirrors database</p>
+
+<p>Mirror not found ? <a href="[% c.uri_for('/new') %]">register it</a></p>
+
+[% INCLUDE 'mirrorslist.tt' %]
diff --git a/root/html/pages/new/index.tt b/root/html/pages/new/index.tt
new file mode 100644
index 0000000..0ef4cfd
--- /dev/null
+++ b/root/html/pages/new/index.tt
@@ -0,0 +1,5 @@
+[% INCLUDE 'new/url_form.tt' %]
+
+[% IF subtemplate %]
+[% INCLUDE $subtemplate %]
+[% END %]
diff --git a/root/static/style.css b/root/static/style.css
new file mode 100644
index 0000000..cfa1da3
--- /dev/null
+++ b/root/static/style.css
@@ -0,0 +1 @@
+// $Id$
diff --git a/t/controller_New.t b/t/controller_New.t
new file mode 100644
index 0000000..64ecee3
--- /dev/null
+++ b/t/controller_New.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::New' }
+
+ok( request('/new')->is_success, 'Request should succeed' );
+done_testing();
diff --git a/t/model_Mirrors.t b/t/model_Mirrors.t
new file mode 100644
index 0000000..e09d845
--- /dev/null
+++ b/t/model_Mirrors.t
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'MGA::Mirrors::Model::Mirrors' }
+
+done_testing();
diff --git a/t/view_TT.t b/t/view_TT.t
new file mode 100644
index 0000000..34c2283
--- /dev/null
+++ b/t/view_TT.t
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'MGA::Mirrors::View::TT' }
+
+done_testing();