/usr/local/CPAN/MyCPAN-Indexer/MyCPAN/App/BackPAN/Indexer.pm
package MyCPAN::App::BackPAN::Indexer;
use strict;
use warnings;
no warnings 'uninitialized';
use vars qw($VERSION);
use Carp;
use Cwd qw(cwd);
use File::Basename;
use File::Path qw(mkpath);
use File::Spec::Functions qw(catfile);
use File::Temp qw(tempdir);
use Getopt::Std;
use Log::Log4perl;
$VERSION = '1.28';
$|++;
my $logger = Log::Log4perl->get_logger( 'backpan_indexer' );
#$SIG{__DIE__} = \&Carp::confess;
$SIG{INT} = sub { exit() };
__PACKAGE__->activate( @ARGV ) unless caller;
BEGIN {
my $cwd = cwd();
my $report_dir = catfile( $cwd, 'indexer_reports' );
my %Defaults = (
alarm => 15,
copy_bad_dists => 0,
dispatcher_class => 'MyCPAN::Indexer::Dispatcher::Parallel',
error_report_subdir => catfile( $report_dir, 'errors' ),
indexer_class => 'MyCPAN::Indexer',
indexer_id => 'Joe Example <joe@example.com>',
interface_class => 'MyCPAN::Indexer::Interface::Text',
log_file_watch_time => 30,
organize_dists => 0,
parallel_jobs => 1,
pause_id => 'MYCPAN',
queue_class => 'MyCPAN::Indexer::Queue',
report_dir => $report_dir,
reporter_class => 'MyCPAN::Indexer::Reporter::AsYAML',
retry_errors => 1,
success_report_subdir => catfile( $report_dir, 'success' ),
system_id => 'an unnamed system',
worker_class => 'MyCPAN::Indexer::Worker',
);
sub default_keys { keys %Defaults }
sub default { $Defaults{$_[1]} }
sub config_class { 'ConfigReader::Simple' }
sub init_config
{
my( $self, $file ) = @_;
eval "require " . $self->config_class . "; 1";
my $config = $self->config_class->new( defined $file ? $file : () );
foreach my $key ( $self->default_keys )
{
next if $config->exists( $key );
$config->set( $key, $self->default( $key ) );
}
$config;
}
}
sub adjust_config
{
my( $application ) = @_;
my $coordinator = $application->get_coordinator;
my $config = $coordinator->get_config;
my @argv = $application->{args};
# set the directories to index
unless( $config->exists( 'backpan_dir') )
{
# At the moment, you can only set string values, so we have to
# cheat a bit. This should really come in as a ConfigReader
# subclass
$config->set( 'backpan_dir', @argv ? join( ' ', @argv ) : cwd() );
}
if( $config->exists( 'report_dir' ) )
{
foreach my $subdir ( qw(success error) )
{
$config->set(
"${subdir}_report_subdir",
catfile( $config->get( 'report_dir' ), $subdir ),
);
}
}
# Adjust for some environment variables
my $log4perl_file =
$ENV{'MYCPAN_LOG4PERL_FILE'}
||
$coordinator->get_note( 'log4perl_file' )
;
$config->set( 'log4perl_file', $log4perl_file ) if $log4perl_file;
}
sub new
{
my( $class, @args ) = @_;
bless { args => [ @args ] }, $class;
}
sub get_coordinator { $_[0]->{coordinator} }
sub set_coordinator { $_[0]->{coordinator} = $_[1] }
sub process_options
{
my( $application ) = @_;
my $run_dir = dirname( $0 );
( my $script = basename( $0 ) ) =~ s/\.\w+$//;
local @ARGV = @{ $application->{args} };
getopts( 'cl:f:', \ my %Options );
# other things might want to use things from @ARGV, and
# we just removed the bits that we wanted.
$application->{args} = [ @ARGV ]; # XXX: yuck
$Options{f} ||= catfile( $run_dir, "$script.conf" );
$Options{l} ||= catfile( $run_dir, "$script.log4perl" );
$application->{options} = \%Options;
}
sub get_option { $_[0]->{options}{$_[1]} }
sub setup_coordinator
{
my( $application ) = @_;
require MyCPAN::Indexer::Coordinator;
my $coordinator = MyCPAN::Indexer::Coordinator->new;
$coordinator->set_application( $application );
$application->set_coordinator( $coordinator );
$coordinator->set_note( 'UUID', $application->get_uuid() );
$coordinator->set_note( 'tempdirs', [] );
$coordinator->set_note( 'log4perl_file', $application->get_option( 'l' ) );
$coordinator;
}
sub handle_config
{
my( $application ) = @_;
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Adjust config based on run parameters
my $config = $application->init_config( $application->get_option('f') );
$application->get_coordinator->set_config( $config );
$application->adjust_config;
if( $application->get_option('c') )
{
use Data::Dumper;
print STDERR Dumper( $config );
exit;
}
}
sub activate_steps
{
qw(
process_options
setup_coordinator
setup_environment
handle_config
setup_logging
setup_dirs
run_components
activate_end
);
}
sub activate
{
my( $class, @argv ) = @_;
use vars qw( %Options );
local %ENV = %ENV;
my $application = $class->new( @argv );
foreach my $step ( $application->activate_steps )
{
$application->$step();
}
$application;
}
sub run_components
{
my( $application ) = @_;
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Load classes and check that they do the right thing
my @components = $application->components;
my $coordinator = $application->get_coordinator;
my $config = $coordinator->get_config;
foreach my $tuple ( @components )
{
my( $directive, $default_class, $method ) = @$tuple;
my $class = $config->get( "${directive}_class" ) || $default_class;
eval "require $class; 1" or die "$@\n";
die "$directive [$class] does not implement $method()"
unless $class->can( $method );
$logger->debug( "Calling $class->$method()" );
my $component = $class->new;
$component->set_coordinator( $coordinator );
$component->$method();
my $set_method = "set_$directive";
$coordinator->$set_method( $component );
}
}
sub activate_end
{
my( $application ) = @_;
$application->cleanup;
$application->_exit;
}
sub setup_environment
{
my %pass_through = map { $_, 1 } qw(
DISPLAY USER HOME PWD TERM
), grep { /^(?:D|MY)CPAN_/ } keys %ENV;
foreach my $key ( keys %ENV )
{
delete $ENV{$key} unless exists $pass_through{$key}
}
$ENV{AUTOMATED_TESTING}++;
}
sub setup_logging
{
my( $self ) = @_;
my $config = $self->get_coordinator->get_config;
my $log_file = $config->get( 'log4perl_file' );
if( defined $log_file and -e $log_file )
{
Log::Log4perl->init_and_watch(
$log_file,
$self->get_coordinator->get_config->get( 'log_file_watch_time' )
);
}
else
{
Log::Log4perl->easy_init( $Log::Log4perl::ERROR );
}
}
sub components
{
(
[ qw( queue MyCPAN::Indexer::Queue get_queue ) ],
[ qw( dispatcher MyCPAN::Indexer::Dispatcher::Parallel get_dispatcher ) ],
[ qw( reporter MyCPAN::Indexer::Reporter::AsYAML get_reporter ) ],
[ qw( worker MyCPAN::Indexer::Worker get_task ) ],
[ qw( interface MyCPAN::Indexer::Interface::Curses do_interface ) ],
[ qw( reporter MyCPAN::Indexer::Reporter::AsYAML final_words ) ],
)
}
sub cleanup
{
my( $self ) = @_;
require File::Path;
my @dirs =
@{ $self->get_coordinator->get_note('tempdirs') },
$self->get_coordinator->get_config->temp_dir;
$logger->debug( "Dirs to remove are @dirs" );
eval {
no warnings;
File::Path::rmtree [@dirs];
};
print STDERR "$@\n" if $@;
$logger->error( "Couldn't cleanup: $@" ) if $@;
}
# I don't remember why I made an explicit exit. Was it to get
# out of a Tk app or something?
sub _exit
{
my( $self ) = @_;
$logger->info( "Exiting from ", __PACKAGE__ );
exit 0;
}
sub setup_dirs # XXX big ugly mess to clean up
{
my( $self ) = @_;
my $config = $self->get_coordinator->get_config;
# Okay, I've gone back and forth on this a couple of times. There is
# no default for temp_dir. I create it here so it's only set when I
# need it. It either comes from the user or on-demand creation. I then
# set it's value in the configuration.
my $temp_dir = $config->temp_dir || tempdir( DIR => cwd(), CLEANUP => 1 );
$logger->debug( "temp_dir is [$temp_dir] [" . $config->temp_dir . "]" );
$config->set( 'temp_dir', $temp_dir );
my $tempdirs = $self->get_coordinator->get_note( 'tempdirs' );
push @$tempdirs, $temp_dir;
$self->get_coordinator->set_note( 'tempdirs', $tempdirs );
mkpath( $temp_dir ) unless -d $temp_dir;
$logger->logdie( "temp_dir [$temp_dir] does not exist!" ) unless -d $temp_dir;
foreach my $key ( qw(report_dir success_report_subdir error_report_subdir) )
{
my $dir = $config->get( $key );
mkpath( $dir ) unless -d $dir;
$logger->logdie( "$key [$dir] does not exist!" ) unless -d $dir;
}
if( $config->retry_errors )
{
my $glob = catfile( $config->get( 'error_report_subdir' ), "*.yml" );
$glob =~ s/( +)/(\\$1)/g;
unlink glob( $glob );
}
}
sub get_uuid
{
require Data::UUID;
my $ug = Data::UUID->new;
my $uuid = $ug->create;
$ug->to_string( $uuid );
}
1;