File Coverage

blib/lib/CPAN/Testers/Data/Uploads/Mailer.pm
Criterion Covered Total %
statement 118 127 92.9
branch 29 40 72.5
condition 6 8 75.0
subroutine 21 21 100.0
pod 2 2 100.0
total 176 198 88.8


line stmt bran cond sub pod time code
1             package CPAN::Testers::Data::Uploads::Mailer;
2              
3 8     8   298004 use warnings;
  8         20  
  8         311  
4 8     8   48 use strict;
  8         16  
  8         856  
5             $|++;
6              
7             our $VERSION = '0.05';
8              
9             #----------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             CPAN::Testers::Data::Uploads::Mailer - Verify CPAN uploads and mails reports
14              
15             =head1 SYNOPSIS
16              
17             my $mailer = CPAN::Testers::Data::Uploads::Mailer->new();
18             $mailer->process();
19              
20             =head1 DESCRIPTION
21              
22             Reads the uploads log, then generates and emails the bad uploads report to
23             the appropriate authors.
24              
25             =cut
26              
27             # -------------------------------------
28             # Library Modules
29              
30 8     8   8770 use Email::Simple;
  8         67463  
  8         274  
31 8     8   88 use File::Basename;
  8         16  
  8         2366  
32 8     8   54 use File::Path;
  8         17  
  8         547  
33 8     8   10992 use File::Slurp;
  8         171291  
  8         763  
34 8     8   10939 use Getopt::ArgvFile default=>1;
  8         60025  
  8         66  
35 8     8   87441 use Getopt::Long;
  8         176600  
  8         116  
36 8     8   16106 use IO::File;
  8         86333  
  8         1338  
37 8     8   12400 use Template;
  8         257048  
  8         407  
38 8     8   8640 use Time::Piece;
  8         114474  
  8         51  
39              
40             # -------------------------------------
41             # Variables
42              
43             # the following will have the emails mailed to them too
44             my @ADMINS = ('barbie@missbarbell.co.uk');
45             my %ADMINS = map {$_ => 1} @ADMINS;
46              
47             my %default = (
48             source => 'logs/uploads.log',
49             lastfile => 'logs/uploads-mailer.txt',
50             logfile => 'logs/uploads-mailer.log',
51             debug => 0, # if set to 1 will not send mails
52             test => 1, # if set to 1 will only send to @ADMINS
53             help => 0,
54             version => 0
55             );
56              
57             #my $HOW = 'blah';
58             my $HOW = '/usr/sbin/sendmail -bm';
59             my $HEAD = 'To: EMAIL
60             From: Barbie
61             Subject: SUBJECT
62             Date: DATE
63              
64             ';
65              
66             my $archive = qr/\b(rar|tgs|tbz|zip|tar|pm|gz|bz2|tz)$/i; # known archive formats posted
67             my $accept = qr/\.(?:(?:tar\.|t)(?:gz|bz2)|zip)$/i; # valid archives
68             my $ignore = qr/
69             \.(pl|sh) | # ignore scripts
70             \.(gif|png|jpg) | # ... images
71             \.(readme|meta|yml|json|changelog) | # ... package files
72             \.(asc|pdf|ppm|patch|pod|txt) $ # ... docs and patches
73             /xi;
74              
75              
76             #----------------------------------------------------------------------------
77             # The Application Programming Interface
78              
79             sub new {
80 9     9 1 22588 my $class = shift;
81              
82 9         26 my $self = {};
83 9         32 bless $self, $class;
84              
85 9         71 $self->_init_options(@_);
86 6         21 return $self;
87             }
88              
89             sub process {
90 2     2 1 12 my $self = shift;
91 2         7 my $source = $self->{options}{source};
92 2         10 my $lastid = $self->_last_id();
93 2         4 my $last_id = $lastid;
94              
95 2         7 $self->{mail}{layout} = 'mail-bad-uploads.eml';
96              
97 2 50       20 my $fh = IO::File->new($source) or die "Cannot open file [$source]: $!";
98 2         223 while(<$fh>) {
99 2154         2669 chomp;
100              
101             #... [1281307] subject=CPAN Upload: A/AP/APLA/update_db_schema.pl
102 2154         4109 my ($id,$path,$cpan,$dist) = m!\.\.\. \[(\d+)\] subject=CPAN Upload: (\w/\w{2}/(\w+)/(.*))!;
103 2154 100 100     7635 next unless($id && $id > $lastid);
104 260         310 $last_id = $id;
105              
106 260 50       427 next unless(defined $cpan); # must have a PAUSE id
107 260 100       3537 next if($dist =~ /$accept|$ignore/); # accepted valid archives or ignored extensions
108              
109 1 50       12 if($dist !~ /$archive/) { # not a known archive format
110 0         0 $self->{mail}{others} .= "$id,$path\n";
111 0         0 next;
112             }
113 1         7 $self->{mail}{authors}{$cpan.'@cpan.org'} = 1;
114 1         8 $self->{mail}{uploads} .= "$id,$path\n";
115             }
116              
117 2         12 $self->_send_mail();
118              
119 2         46 $self->_last_id($last_id);
120             }
121              
122             #----------------------------------------------------------------------------
123             # Private Methods
124              
125             sub _send_mail {
126 2     2   5 my $self = shift;
127              
128 2 100 66     17 return unless(defined $self->{mail}{authors} && keys %{$self->{mail}{authors}});
  1         9  
129              
130 1         5 my $DATE = _emaildate();
131 1         171 $DATE =~ s/\s+$//;
132              
133 1         8 my %tvars = (
134             date => $DATE,
135             uploads => $self->{mail}{uploads},
136             );
137              
138 1         3 my @recipients;
139 1 50       7 push @recipients, (keys %{$self->{mail}{authors}}) unless($self->{options}{test});
  0         0  
140 1         4 push @recipients, @ADMINS;
141              
142 1         3 for my $addr (@recipients) {
143 1         4 $tvars{email} = $addr;
144 1 50 50     13 $tvars{others} = $ADMINS{$addr} ? $self->{mail}{others}||'' : '';
145              
146 1         7 my $body = _create_mail($self->{mail}{layout},\%tvars);
147              
148 1         135 my $cmd = qq!| $HOW $addr!;
149              
150 1 50       8 if($self->{options}{debug}) {
151 1         8 $self->_log("$DATE: NULL: $addr");
152 1         81 $self->_log("$body");
153             } else {
154 0 0       0 if(my $fh = IO::File->new($cmd)) {
155 0         0 print $fh $body;
156 0         0 $fh->close;
157 0         0 $self->_log("$DATE: PASS: $addr");
158             } else {
159 0         0 $self->_log("$DATE: FAIL: $addr");
160             }
161             }
162             }
163             }
164              
165             sub _create_mail {
166 1     1   3 my $layout = shift;
167 1         3 my $tvars = shift;
168 1         2 my $body;
169              
170 1         9 my %config = ( # provide config info
171             RELATIVE => 1,
172             ABSOLUTE => 1,
173             INCLUDE_PATH => './templates',
174             INTERPOLATE => 0,
175             POST_CHOMP => 1,
176             TRIM => 1,
177             );
178              
179 1         25 my $parser = Template->new(\%config); # initialise parser
180 1 50       28786 $parser->process($layout,$tvars,\$body) # parse the template
181             or die $parser->error();
182              
183 1         229 return $body;
184             }
185              
186             # Format date and time into one that conforms to the RFCs.
187              
188             sub _emaildate {
189 1     1   12 my $t = localtime;
190 1         145 return $t->strftime("%a, %d %b %Y %H:%M:%S %z");
191             }
192              
193             sub _last_id {
194 4     4   10 my $self = shift;
195 4         7 my ( $id ) = @_;
196              
197 4 100       78 overwrite_file( $self->{options}{lastfile}, 0 ) unless -f $self->{options}{lastfile};
198              
199 4 100       219 if (defined $id) {
200 2         15 overwrite_file( $self->{options}{lastfile}, $id );
201             } else {
202 2         10 $id = read_file($self->{options}{lastfile});
203             }
204              
205 4         657 return $id;
206             }
207              
208             sub _log {
209 2     2   4 my $self = shift;
210 2         5 my $msg = shift;
211 2         6 my $logfile = $self->{options}{logfile};
212 2 50       6 return unless($logfile);
213              
214 2 50       17 my $fh = IO::File->new($logfile,'a+') or die "Cannot write to file [$logfile]: $!\n";
215 2         368 print $fh "$msg\n";
216 2         17 $fh->close;
217             }
218              
219             sub _defined_or {
220 63     63   89 my $self = shift;
221 63         131 while(@_) {
222 161         200 my $value = shift;
223 161 100       686 return $value if(defined $value);
224             }
225              
226 0         0 return;
227             }
228              
229             sub _init_options {
230 9     9   16 my $self = shift;
231 9         33 my %opts = @_;
232              
233 9         14 my %options;
234 9         57 GetOptions( \%options,
235             'source|s=s',
236             'logfile=s',
237             'lastfile=s',
238             'test|t!',
239             'debug|d!',
240             'help|h',
241             'version|v'
242             );
243              
244 9         4683 $self->{options} = ();
245 9         91 $self->{options}{$_} = $self->_defined_or($options{$_}, $opts{$_}, $default{$_}) for(keys %default);
246              
247 9 100       49 _help(1) if($self->{options}{help});
248 8 100       66 _help(0) if($self->{options}{version});
249              
250 7 100       185 unless(-f $self->{options}{source}) {
251 1         54 print "No uploads source log file [$self->{options}{source}] found\n\n";
252 1         4 _help(1);
253             }
254              
255 6         600 mkpath(dirname($self->{options}{lastfile}));
256 6         309 mkpath(dirname($self->{options}{logfile}));
257             }
258              
259             sub _help {
260 3     3   28 my $full = shift;
261              
262 3 100       10 if($full) {
263 2         90 print <
264              
265             Usage: $0 \\
266             [--logfile=] [--source=] [--lastfile=] \\
267             [--test] [--debug] [-h] [-v]
268              
269             --logfile log file from cpanstats-verify
270             --source results output file
271             --lastfile last NNTP ID mailed out
272             --test send mails to admin only
273             --debug do not send mails
274             -h this help screen
275             -v program version
276              
277             HERE
278              
279             }
280              
281 3         80 print "$0 v$VERSION\n";
282 3         12 exit(0);
283             }
284              
285             __END__