package CPAN::Indexer::Mirror;

=pod

=head1 NAME

CPAN::Indexer::Mirror - Creates the mirror.yml and mirror.json files

=head1 SYNOPSIS

  use CPAN::Indexer::Mirror ();
  
  CPAN::Indexer::Mirror->new(
      root => '/cpan/root/directory',
  )->run;

=head1 DESCRIPTION

This module is used to implement a small piece of functionality inside the
CPAN/PAUSE indexer which generates F<mirror.yml> and F<mirror.json>.

These files are used to allow CPAN clients (via the L<Mirror::YAML> or
L<Mirror::JSON> modules) to implement mirror validation and automated
selection.

=head1 METHODS

Anyone who needs to know more detail than the SYNOPSIS should read the
(fairly straight forward) code.

=cut

use 5.006;
use strict;
use File::Spec              ();
use File::Remove            ();
use YAML::Tiny              ();
use JSON                    ();
use URI                     ();
use URI::http               ();
use IO::AtomicFile          ();
use Parse::CPAN::MirroredBy ();

use vars qw{$VERSION};
BEGIN {
	$VERSION = '0.05';
}





#####################################################################
# Constructor and Accessor Methods

sub new {
	my $class = shift;
	my $self  = bless { @_ }, $class;

	# Apply defaults
	$self->{name} ||= 'Comprehensive Perl Archive Network';
	$self->{master}  ||= 'http://www.cpan.org/';

	return $self;
}

sub root {
	$_[0]->{root};
}

sub name {
	$_[0]->{name};
}

sub master {
	$_[0]->{master};
}

sub timestamp {
	$_[0]->{timestamp} || $_[0]->now;
}

sub mirrored_by {
	File::Spec->catfile( $_[0]->root, 'MIRRORED.BY' );
}

sub mirror_yml {
	File::Spec->catfile( $_[0]->root, 'mirror.yml' );
}

sub mirror_json {
	File::Spec->catfile( $_[0]->root, 'mirror.json' );
}





#####################################################################
# Process Methods

sub run {
	my $self = ref $_[0] ? shift : shift->new(@_);

	# Always randomise the mirror order, to protect against
	# weak programmers on the other end scanning them in
	# sequential order.
	my @mirrors = sort { rand() <=> rand() }
                      $self->parser->parse_file( $self->mirrored_by );

	# Generate the data structure for the files
	my $data    = {
		version   => '1.0',
		name      => $self->name,
		master    => $self->master,
		timestamp => $self->timestamp,
		mirrors   => \@mirrors,
	};

	# Write the mirror.yml and mirror.json file.
	# Make sure the closes (and thus commits) are as close together
	# as we can possibly get them, minimising race conditions.
	SCOPE: {
		local $!;
		my $yaml_file = $self->mirror_yml;
		my $json_file = $self->mirror_json;
		my $yaml_fh   = IO::AtomicFile->open($yaml_file, "w")     or die "open: $!";
		my $json_fh   = IO::AtomicFile->open($json_file, "w")     or die "open: $!";
		$yaml_fh->print( YAML::Tiny::Dump($data) )           or die "print: $!";
		$json_fh->print(  JSON->new->pretty->encode($data) ) or die "print: $!";
		$yaml_fh->close                                      or die "close: $!";
		$json_fh->close                                      or die "close: $!";
	}

	return 1;
}

sub parser {
	my $parser = Parse::CPAN::MirroredBy->new;
	$parser->add_map(  sub { $_[0]->{dst_http} } );
	$parser->add_grep( sub {
		defined $_[0]
		and
		$_[0] =~ /\/$/
	} );
	$parser->add_map( sub { URI->new( $_[0], 'http' )->canonical->as_string } );
	return $parser;
}

sub now {
	my @t = gmtime time;
	return sprintf( "%04u-%02u-%02uT%02u:%02u:%02uZ",
		$t[5] + 1900,
		$t[4] + 1,
		$t[3],
		$t[2],
		$t[1],
		$t[0],
	);
}

1;

=pod

=head1 SUPPORT

Bugs should be reported via the CPAN bug tracker at

L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CPAN-Indexer-Mirror>

=head1 AUTHOR

Adam Kennedy E<lt>adamk@cpan.orgE<gt>

=head1 SEE ALSO

L<Parse::CPAN::Authors>, L<Parse::CPAN::Packages>,
L<Parse::CPAN::Modlist>, L<Parse::CPAN::Meta>,
L<Parse::CPAN::MirroredBy>

=head1 COPYRIGHT

Copyright 2008 Adam Kennedy.

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the
LICENSE file included with this module.

=cut