File Coverage

blib/lib/Email/Fingerprint/App/EliminateDups.pm
Criterion Covered Total %
statement 36 104 34.6
branch 0 26 0.0
condition 0 2 0.0
subroutine 12 19 63.1
pod 7 7 100.0
total 55 158 34.8


line stmt bran cond sub pod time code
1             package Email::Fingerprint::App::EliminateDups;
2              
3 1     1   1446 use warnings;
  1         2  
  1         35  
4 1     1   5 use strict;
  1         2  
  1         34  
5              
6 1     1   5 use Class::Std;
  1         2  
  1         7  
7              
8 1     1   94 use Carp qw( croak );
  1         2  
  1         55  
9 1     1   6 use File::Basename;
  1         2  
  1         102  
10 1     1   2173 use Getopt::Long;
  1         14102  
  1         7  
11              
12 1     1   191 use Email::Fingerprint;
  1         1  
  1         17  
13 1     1   966 use Email::Fingerprint::Cache;
  1         4  
  1         871  
14              
15             =head1 NAME
16              
17             Email::Fingerprint::App::EliminateDups - Implements eliminate-dups functionality
18              
19             =head1 VERSION
20              
21             Version 0.26
22              
23             =cut
24              
25             our $VERSION = '0.26';
26              
27             =head1 SYNOPSIS
28              
29             See the manpage for C. This module is not intended to be
30             used except by that script.
31              
32             =cut
33              
34             # Attributes
35              
36             my %dbname : ATTR( :get ); # Fingerprint DB name
37             my %cache : ATTR( :get ); # Actual fingerprint DB
38              
39             my %dump : ATTR( :get, :default<0> ); # Dump cache contents
40             my %help : ATTR( :get, :default<0> ); # Print usage
41             my %no_check : ATTR( :get, :default<0> ); # Only purge
42             my %no_purge : ATTR( :get, :default<0> ); # Only check
43             my %strict : ATTR( :get, :default<0> ); # Include body
44              
45             =head1 METHODS
46              
47             =head2 new
48              
49             $app = new Email::Fingerprint::App::EliminateDups;
50              
51             Create a new object. Takes no options.
52              
53             =head2 BUILD
54              
55             Internal helper method, not called by external users.
56              
57             =cut
58             sub BUILD {
59 0     0 1   my ($self, $obj_ID, $arg_ref) = @_;
60              
61 0           $self->_init;
62             }
63              
64             =head2 run
65              
66             $app->run(@ARGV);
67              
68             Run the eliminate-dups application.
69              
70             =cut
71              
72             sub run {
73 0     0 1   my $self = shift;
74              
75 0           $self->_process_options(@_);
76 0           $self->open_cache;
77 0           $self->dump_cache; # No-op if --dump wasn't specified
78 0           $self->check_fingerprint; # No-op if --no-check option was specified
79 0           $self->purge_cache; # No-op if --no-purge option was specified
80 0           $self->close_cache;
81              
82             # Success
83 0           exit 0;
84             }
85              
86             =head2 open_cache
87              
88             Initialize, open and lock the cache.
89              
90             =cut
91              
92             sub open_cache {
93 0     0 1   my $self = shift;
94 0           my $cache = $self->get_cache;
95 0   0       my $dbname = $self->get_dbname || '';
96              
97 0 0         return $cache if $cache;
98              
99             # Initialize the cache
100 0           $cache = new Email::Fingerprint::Cache({
101             file => $dbname,
102             });
103              
104             # Validate
105 0 0         if ( not $cache ) {
106 0           $self->_exit_retry( "Couldn't initialize cache \"$dbname\"" );
107             }
108              
109             # Lock it
110 0 0         if ( not $cache->lock( block => 1 ) ) {
111 0           $self->_exit_retry( "Couldn't lock \"$dbname\": $!" );
112             }
113              
114             # Open it
115 0 0         if ( not $cache->open ) {
116 0           $cache->unlock;
117 0           $self->_exit_retry( "Couldn't open \"$dbname\": $!" );
118             }
119              
120 0           $cache{ ident $self } = $cache;
121 0           return $cache;
122             }
123              
124             =head2 close_cache
125              
126             Close and unlock the cache.
127              
128             =cut
129              
130             sub close_cache {
131 0     0 1   my $self = shift;
132 0           my $cache = delete $cache{ ident $self };
133              
134 0 0         if ($cache) {
135 0           $cache->unlock;
136 0           $cache->close;
137             }
138              
139 0           1;
140             }
141              
142             =head2 dump_cache
143              
144             Conditionally dump the cache contents and exit.
145              
146             =cut
147              
148             sub dump_cache {
149 0     0 1   my $self = shift;
150              
151 0 0         return unless $self->get_dump;
152 0 0         return unless $self->get_cache;
153              
154             # Dump the contents of the hashfile in a human readable format
155 0           $self->get_cache->dump;
156              
157 0           $self->close_cache;
158 0           exit 0;
159             }
160              
161             =head2 check_fingerprint
162              
163             Conditionally check the fingerprint of the message on STDIN.
164              
165             =cut
166              
167             sub check_fingerprint {
168 0     0 1   my $self = shift;
169              
170 0 0         return if $self->get_no_check;
171              
172 0           my $checksum = new Email::Fingerprint({
173             input => \*STDIN,
174             checksum => "Digest::MD5",
175             strict_checking => $self->get_strict,
176             });
177              
178 0           my $fingerprint = $checksum->checksum;
179              
180             # If there's a match, suppress it with exit code 99.
181 0 0         if (defined $self->get_cache->get_hash->{$fingerprint})
182             {
183             # Fingerprint matches. Tell qmail to stop current delivery.
184 0           $self->close_cache;
185 0           exit 99;
186             }
187              
188             # Record the fingerprint
189 0           $self->get_cache->get_hash->{$fingerprint} = time;
190             }
191              
192             =head2 purge_cache
193              
194             Purge the cache of old entries.
195              
196             =cut
197              
198             sub purge_cache {
199 0     0 1   my $self = shift;
200            
201 0 0         return if $self->get_no_purge;
202              
203 0           $self->get_cache->purge;
204             }
205              
206             =head2 _process_options
207              
208             Process command-line options.
209              
210             =cut
211              
212             sub _process_options :PRIVATE {
213 0         0 my ( $self, @args ) = @_;
214              
215             # Fool Getopt::Long. Sigh.
216 0         0 local @ARGV = @args;
217              
218 0         0 $self->_init;
219              
220 0 0       0 $self->_die_usage if not GetOptions(
221             "dump" => \$dump{ident $self},
222             "no-purge" => \$no_purge{ident $self},
223             "no-check" => \$no_check{ident $self},
224             "strict" => \$strict{ident $self},
225             "help" => \$help{ident $self},
226             );
227              
228             # Respond to calls for help
229 0 0       0 $self->_die_usage if $self->get_help;
230              
231             # Set the filename. If omitted, a default is used.
232 0 0       0 $dbname{ident $self} = shift @ARGV if @ARGV;
233 1     1   8 }
  1         2  
  1         5  
234              
235             =head2 _init
236              
237             Basic initializer. Called from C and also from
238             C<_process_options>.
239              
240             =cut
241              
242             sub _init :PRIVATE {
243 0         0 my $self = shift;
244 0         0 my $obj_ID = ident $self;
245              
246 0         0 $dbname{$obj_ID} = '.maildups';
247 0         0 $self->close_cache; # A no-op if we don't have a cache yet
248              
249 0         0 $dump{$obj_ID} = 0;
250 0         0 $help{$obj_ID} = 0;
251 0         0 $no_purge{$obj_ID} = 0;
252 0         0 $no_check{$obj_ID} = 0;
253 0         0 $strict{$obj_ID} = 0;
254 1     1   257 }
  1         2  
  1         11  
255              
256             =head2 die_usage
257              
258             Exit with a usage message.
259              
260             =cut
261              
262             sub _die_usage :PRIVATE {
263 0         0 my $self = shift;
264 0         0 my $progname = basename $0;
265              
266 0         0 $self->_exit_retry(
267             "usage:\t$progname [--strict] [--no-purge] [hashfile]\n"
268             . "\t$progname [--dump] [hashfile]\n"
269             . "\t$progname [--no-check] [hashfile]"
270             );
271 1     1   221 }
  1         2  
  1         5  
272              
273             =head2 _exit_retry
274              
275             Exit with qmail's "temporary error" status code. This forces qmail to
276             abort delivery attempts and try again later.
277              
278             =cut
279              
280             sub _exit_retry :PRIVATE {
281 0           my ( $self, $message ) = @_;
282              
283 0           warn "$message\n";
284 0           exit 111;
285 1     1   218 }
  1         2  
  1         4  
286              
287             =head1 AUTHOR
288              
289             Len Budney, C<< >>
290              
291             =head1 BUGS
292              
293             Please report any bugs or feature requests to
294             C, or through the web interface at
295             L.
296             I will be notified, and then you'll automatically be notified of progress on
297             your bug as I make changes.
298              
299             =head1 SUPPORT
300              
301             You can find documentation for this module with the perldoc command.
302              
303             perldoc Email::Fingerprint
304              
305             You can also look for information at:
306              
307             =over 4
308              
309             =item * AnnoCPAN: Annotated CPAN documentation
310              
311             L
312              
313             =item * CPAN Ratings
314              
315             L
316              
317             =item * RT: CPAN's request tracker
318              
319             L
320              
321             =item * Search CPAN
322              
323             L
324              
325             =back
326              
327             =head1 SEE ALSO
328              
329             See B for options governing the parsing of email headers.
330              
331             =head1 ACKNOWLEDGEMENTS
332              
333             Email::Fingerprint is based on the C script by Peter Samuel
334             and available at L.
335              
336             =head1 COPYRIGHT & LICENSE
337              
338             Copyright 2006-2011 Len Budney, all rights reserved.
339              
340             This program is free software; you can redistribute it and/or modify it
341             under the same terms as Perl itself.
342              
343             =cut
344              
345             1; # End of Email::Fingerprint