App::CPAN::Testers::Remailer


App-CPAN-Testers-Remailer documentation Contained in the App-CPAN-Testers-Remailer distribution.

Index


Code Index:


App-CPAN-Testers-Remailer documentation Contained in the App-CPAN-Testers-Remailer distribution.

# Copyright (c) 2008 by David Golden. All rights reserved.
# Licensed under Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License was distributed with this file or you may obtain a 
# copy of the License from http://www.apache.org/licenses/LICENSE-2.0

package App::CPAN::Testers::Remailer;
use 5.006;
use strict;
use warnings;

our $VERSION = '0.01';
$VERSION = eval $VERSION; ## no critic

use Safe;
use Email::Simple;
use Email::Address;
use File::Basename qw/basename/;
use LWP::Simple;
use Net::DNS qw/mx/;
use Getopt::Long;
use POE qw( 
  Component::Client::NNTP::Tail
  Component::Client::SMTP
);

#--------------------------------------------------------------------------#
# FIXED PARAMETERS
#--------------------------------------------------------------------------#

my $nntpserver  = "nntp.perl.org";
my $group       = "perl.cpan.testers";

#--------------------------------------------------------------------------#
# MAIN APPLICATION CODE
#--------------------------------------------------------------------------#

sub run {
  my ($class) = shift;

  $0 = basename($0);

  #--------------------------------------------------------------------------#
  # Parameter handling
  #--------------------------------------------------------------------------#

  my ($author,@grades,$help);
  my $mirror      = "http://cpan.pair.com/";
  my $smtp        = [ mx('perl.org') ]->[0]->exchange; # maybe change to your ISP's server
  my $checksum_path;

  GetOptions( 
    'author=s'  => \$author,
    'grade=s'   => \@grades,
    'smtp=s'    => \$smtp,
    'mirror=s'  => \$mirror,
    'help'      => \$help
  );

  die << "END_USAGE" if $help || ! ( $author && @grades );
Usage: $0 OPTIONS

Options:
  --author=AUTHORID     CPAN author ID (required)

  --grade=GRADE         PASS, FAIL, UNKNOWN or NA (required, multiple ok)
  
  --smtp=SERVER         SMTP relay server; defaults to mx.perl.org
  
  --mirror=MIRROR       CPAN mirror; defaults to http://cpan.pair.com/

  --help                this usage info
END_USAGE

  $author = uc $author; # DWIM
  if ($author =~ /^(([a-z])[a-z])[a-z]+$/i) {
    $checksum_path="authors/id/$2/$1/$author/CHECKSUMS";
  }
  else {
    die "$0: '$author' doesn't seem to be a proper CPAN author ID\n";
  }

  for my $g ( @grades ) {
    $g = uc $g; # DWIM
    die "$0: '$g' is not a valid grade (PASS, FAIL, UNKNOWN or NA)\n"
      unless $g =~ /^(?:PASS|FAIL|UNKNOWN|NA)$/;
  }

  # make sure mirror ends with slash
  $mirror =~ s{/$}{};
  $mirror .= "/";

  #--------------------------------------------------------------------------#
  # Launch POE sessions
  #--------------------------------------------------------------------------#

  POE::Component::Client::NNTP::Tail->spawn(
    NNTPServer  => $nntpserver,
    Group       => $group,
  );

  POE::Session->create(
    package_states => [
      $class => [qw(_start refresh_dist_list new_header got_article smtp_err)]
    ],
    heap => {
      nntpserver => $nntpserver,
      group   => $group,
      author  => $author,
      grades  => \@grades,
      smtp    => $smtp,
      mirror  => $mirror,
      checksum_path => $checksum_path,
    },
  );

  POE::Kernel->run;
  return;
}

#--------------------------------------------------------------------------#
# EVENT HANDLERS
#--------------------------------------------------------------------------#

sub _start {
  $_[KERNEL]->call( $_[SESSION], 'refresh_dist_list' );
  $_[KERNEL]->post( $group => 'register' );
  print "$0: startup completed; now monitoring for reports...\n";
  return;
}

# get $author CHECKSUMS file and put dist list in heap  
sub refresh_dist_list {
  my ($heap) = $_[HEAP];
  my $checksum_path = $heap->{checksum_path};
  my $mirror = $heap->{mirror};
  my $url = "${mirror}${checksum_path}";
  my $file = get($url);
  die "$0: error getting $url\n" unless defined $file;
  $file =~ s/\015?\012/\n/;
  my $safe = Safe->new;
  my $checksums = $safe->reval($file);
  if ( ref $checksums eq 'HASH' ) {
    # clear dist list
    $_[HEAP]->{dists} = {};
    for my $f ( keys %$checksums ) {
      # use the .meta key so we don't worry about tarball suffixes
      next unless $f =~ /.meta$/;
      $f =~ s/.meta$//;
      $_[HEAP]->{dists}{$f} = 1;
    }
  }
  else {
    die "$0: Couldn't get distributions by $heap->{author} from $mirror\n";
  }
  # refresh in 12 hours
  $_[KERNEL]->delay( 'refresh_dist_list' => 3600 * 12 );
  return;
}

sub new_header {
  my ($kernel, $heap, $article_id, $lines) = @_[KERNEL, HEAP, ARG0, ARG1];
  my $article = Email::Simple->new( join "\015\012", @$lines );
  my $subject = $article->header('Subject');
  my ($grade, $dist) = split " ", $subject;
  if ( $heap->{dists}{$dist} && grep { $grade eq $_ } @{$heap->{grades}} ) {
    $kernel->post( $group => 'get_article' => $article_id );
  }
  return;
}

sub got_article {
  my ($kernel, $heap, $article_id, $lines) = @_[KERNEL, HEAP, ARG0, ARG1];
  my $article = Email::Simple->new( join "\015\012", @$lines );
  my $subject = $article->header('Subject');
  my ($from) = Email::Address->parse( $article->header('From') ) 
    or die "$0: parse error '" . $article->header('From') . "'\n";
  my $sender = $from->address;
  
  print "$0: from $sender\: $subject\n";
  POE::Component::Client::SMTP->send(
    From          => $sender,
    To            => "$heap->{author}\@cpan.org",
    Body          => $article->as_string,
    Server        => $heap->{smtp},
    Context       => $article_id,
    SMTP_Failure  => 'smtp_err',
  );

  return;
}

my %failed;
sub smtp_err {
  my ($article_id, $errors) = @_[ARG0, ARG1];
  if ( $errors->{SMTP_Server_Error} ) {
    warn "$0: SMTP error sending report $article_id\: $errors->{SMTP_Server_Error}\n";
  }
  elsif ( $errors->{Timeout} ) {
    warn "$0: Timeout sending report $article_id\n";
  }
  elsif ( $errors->{Configure} ) {
    warn "$0: Could not authenticate to SMTP server\n";
  }
  elsif ( $errors->{'POE::Wheel::SocketFactory'} ) {
    my ($operation, $errnum, $errstr) = @{ $errors->{'POE::Wheel::SocketFactory'} };
    warn "$0: Error during '$operation' for $article_id\: $errstr\n";
  }
  else {
    warn "$0: Internal error sending report $article_id\n"
  }
  if ( ! $failed{$article_id}++ ) {
    warn "$0: will try again to send report $article_id\n";
    $_[KERNEL]->post( $group => 'get_article' => $article_id );
  }
  else {
    warn "$0: will not try again for report $article_id\n";
  }
  return;
}

1;

__END__