File Coverage

blib/lib/Mail/ThreadKiller.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Mail::ThreadKiller;
2 1     1   782 use strict;
  1         3  
  1         24  
3 1     1   4 use warnings;
  1         2  
  1         34  
4              
5 1     1   529 use DB_File;
  0            
  0            
6             use Fcntl ':flock';
7             use Carp;
8              
9             our $VERSION = '1.0.1';
10              
11             sub new
12             {
13             my ($class) = @_;
14             return bless {
15             db_file => undef,
16             lock_fd => undef,
17             lockfile => undef,
18             lock_fd => undef,
19             tied => undef,
20             }, $class;
21             }
22              
23             sub set_db_file
24             {
25             my ($self, $filename) = @_;
26             $self->{db_file} = $filename;
27             }
28              
29             sub open_db_file
30             {
31             my ($self, $filename) = @_;
32             if (defined($filename)) {
33             $self->{db_file} = $filename;
34             }
35              
36             croak("DB file not set") unless defined($self->{db_file});
37             my $lockfile = $self->{db_file} . '.lock';
38             my $fp;
39             open($fp, ">>$lockfile") or croak("Cannot open $lockfile: $!");
40             flock($fp, LOCK_EX) or croak("Cannot lock $lockfile: $!");
41             $self->{lock_fd} = $fp;
42             $self->{lockfile} = $lockfile;
43             if (!tie(%{$self->{tied}}, 'DB_File', $self->{db_file})) {
44             croak("Cannot tie to " . $self->{db_file} . ": $!");
45             }
46             return 1;
47             }
48              
49             sub close_db_file
50             {
51             my ($self) = @_;
52             if (defined($self->{tied})) {
53             untie(%{$self->{tied}});
54             $self->{tied} = undef;
55             }
56             if (defined($self->{lock_fd})) {
57             close($self->{lock_fd});
58             $self->{lock_fd} = undef;
59             }
60             if (defined($self->{lockfile})) {
61             unlink($self->{lockfile});
62             $self->{lockfile} = undef;
63             }
64             }
65              
66             sub DESTROY
67             {
68             my ($self) = @_;
69             $self->close_db_file();
70             }
71              
72             # Add a message-ID to the database
73             sub add_message_id {
74             my ($self, $msgid) = @_;
75             my $now = time();
76              
77             $self->{tied}->{$msgid} = $now;
78             return $now;
79             }
80              
81             # Are any IDs in the database?
82             sub any_ids_in_database {
83             my ($self, $msgid_line, $in_reply_to_line, $references_line) = @_;
84             if (defined($msgid_line) && ($msgid_line =~ /(<\S+>)/)) {
85             return $self->{tied}->{$1} if (exists($self->{tied}->{$1}));
86             }
87             if (defined($in_reply_to_line) && ($in_reply_to_line =~ /(<\S+>)/)) {
88             return $self->{tied}->{$1} if (exists($self->{tied}->{$1}));
89             }
90             if (defined($references_line)) {
91             my @ids = split(/\s+/, $references_line);
92             foreach my $id (@ids) {
93             return $self->{tied}->{$id} if (exists($self->{tied}->{$id}));
94             }
95             }
96             return 0;
97             }
98              
99             # Convenience function
100             sub kill_message
101             {
102             my ($self, $mail) = @_;
103             my $mid = $mail->header('Message-ID');
104             return 0 unless defined($mid);
105             return $self->add_message_id($mid);
106             }
107              
108             # Convenience function
109             sub should_kill_message {
110             my ($self, $mail) = @_;
111             # NOTE: We need to force scalar context, hence the crazy || '' code.
112             if ($self->any_ids_in_database(($mail->header('Message-ID') || ''),
113             ($mail->header('In-Reply-To') || ''),
114             ($mail->header('References') || ''))) {
115             $self->add_message_id($mail->header('Message-ID'));
116             return 1;
117             }
118             return 0;
119             }
120              
121             # Clean out anything in DB older than $days
122             sub clean_db {
123             my ($self, $days) = @_;
124             my ($k, $v);
125             my (@toKill);
126             my ($secs) = $days * 86400;
127             my ($now) = time();
128             while (($k, $v) = each(%{$self->{tied}})) {
129             push @toKill, $k if (($now - $v) > $secs);
130             }
131             my $num_cleaned = 0;
132             foreach $k (@toKill) {
133             $num_cleaned++;
134             delete $self->{tied}->{$k};
135             }
136             return $num_cleaned;
137             }
138              
139             1;
140              
141             __END__