File Coverage

blib/lib/Email/Fingerprint/Cache.pm
Criterion Covered Total %
statement 79 79 100.0
branch 20 20 100.0
condition 13 13 100.0
subroutine 19 19 100.0
pod 10 10 100.0
total 141 141 100.0


line stmt bran cond sub pod time code
1             package Email::Fingerprint::Cache;
2 4     4   1932 use Class::Std;
  4         7513  
  4         32  
3              
4 4     4   451 use warnings;
  3         4  
  3         95  
5 4     4   161 use strict;
  3         3  
  3         87  
6              
7 4     4   20 use Carp qw( croak cluck );
  4         7  
  4         184  
8 4     4   174 use Scalar::Util qw( reftype blessed );
  3         4  
  3         994  
9              
10             =head1 NAME
11              
12             Email::Fingerprint::Cache - Cache observed email fingerprints
13              
14             =head1 VERSION
15              
16             Version 0.48
17              
18             =cut
19              
20             our $VERSION = '0.48';
21              
22             =head1 SYNOPSIS
23              
24             use Email::Fingerprint::Cache;
25              
26             my %fingerprints; # To access cache contents
27              
28             # Create a cache
29             my $cache = new Email::Fingerprint::Cache(
30             backend => "AnyDBM",
31             hash => \%fingerprints,
32             file => $file, # Created if doesn't exist
33             ttl => 3600 * 24 * 7, # Purge records after one week
34             );
35              
36             # Prepare it for use
37             $cache->lock or die "Couldn't lock: $!"; # Waits for lock
38             $cache->open or die "Couldn't open: $!";
39              
40             # Work with fingerprints
41             for my (@message_fingerprints) {
42              
43             if ($fingerprints{$_}) {
44             print "Fingerprint found: $_\n";
45             next;
46             }
47              
48             my $now = time;
49             $fingerprints{$_} = $now;
50              
51             print "Fingerprint added: $_\n";
52             }
53              
54             # Get rid of old records
55             $cache->purge;
56              
57             # Print a listing of all fingerprints
58             $cache->dump;
59              
60             # Finish up
61             $cache->close;
62             $cache->unlock;
63              
64             =head1 ATTRIBUTES
65              
66             =cut
67              
68             my %hash :ATTR( :get ) = ();
69             my %ttl :ATTR( :name :default(604800) ) = ();
70             my %backend :ATTR( :init_arg :default('AnyDBM') ) = ();
71              
72             =head1 METHODS
73              
74             =head2 new
75              
76             my $fingerprint = new Email::Fingerprint::Cache(
77             file => $file, # Default: .maildups
78             backend => "AnyDBM", # Default: "AnyDBM"
79             ttl => $sec, # Default: 3600*24*7
80             hash => $ref, # Optional
81             );
82              
83             Returns a new Email::Fingerprint::Cache. The cache must still be opened
84             before it can be used.
85              
86             =head2 BUILD
87              
88             Internal helper method; never called directly by users.
89              
90             =cut
91              
92             sub BUILD {
93 16     16 1 55017 my ( $self, $ident, $args ) = @_;
94              
95             # Default hash is a fresh-n-tasty anonymous hash
96 15 100       62 $hash{$ident} = defined $args->{hash} ? $args->{hash} : {};
97              
98             # Backend will also need access to the hash
99 15         28 $args->{hash} = $hash{$ident};
100              
101             # Default backend is AnyDBM
102 16 100       47 my $backend = defined $args->{backend} ? $args->{backend} : 'AnyDBM';
103              
104             # Default cache file
105 16   100     63 $args->{file} ||= '.maildups';
106              
107             # Try accessing package as a subclass of Email::Fingerprint::Cache
108 16         103 my $package = __PACKAGE__ . "::" . $backend;
109 16     2   1340 eval "use $package"; ## no critic
  2         618  
  2         5  
  2         27  
110              
111             # Try accessing package using the given name exactly. If this fails,
112             # we try constructing a backend anyway, in case the module is already
113             # imported--or, e.g., defined in the script file itself.
114 16 100       49 if ($@) {
115 5         25 $package = $backend;
116 5         201 eval "use $package"; ## no critic
117             }
118              
119 16         27 undef $backend;
120              
121             # Perhaps the correct module was loaded by our caller;
122             # try instantiating the backend even if the above steps
123             # all failed.
124 16         29 eval {
125 16         112 $backend = $package->new({
126             file => $args->{file},
127             hash => $args->{hash},
128             });
129             };
130              
131             # It's a fatal error if the backend doesn't load
132 16 100 100     2902 croak "Can't load backend module" if $@ or not $backend;
133              
134 14         50 $backend{$ident} = $backend;
135             }
136              
137             =head2 set_file
138              
139             $file = $cache->set_file( 'foo' ) or die "Failed to set filename";
140             # now $file eq 'foo.db' or 'foo.dir', etc., depending on the backend;
141             # it is almost certainly NOT 'foo'.
142              
143             Sets the file to be used for the cache. Returns the actual filename
144             on success; false on failure.
145              
146             The actual filename will probably differ from the 'foo', because
147             the backend will usually add an extension or otherwise munge it.
148              
149             C has I effect while the cache file is locked or open!
150              
151             =cut
152              
153             sub set_file {
154 6     6 1 21 my ($self, $file) = @_;
155              
156             # Validation
157 6 100       10 return if $self->get_backend->is_locked;
158 5 100       25 return if $self->get_backend->is_open;
159              
160             # OK, there's no harm in changing the file attribute
161 4         14 $self->get_backend->set_file($file);
162              
163 4         31 1;
164             }
165              
166             =head2 get_backend
167              
168             Returns the backend object for this cache.
169              
170             =cut
171              
172             sub get_backend :PRIVATE() {
173 72     72 1 662 my $self = shift;
174 71         211 return $backend{ident $self};
175 4     4   165 }
  3         4  
  3         20  
176              
177             =head2 dump
178              
179             # Be a good citizen
180             $cache->lock;
181             $cache->open;
182              
183             $cache->dump;
184              
185             # Be a good neighbor
186             $cache->close;
187             $cache->unlock;
188              
189             Dump a human-readable version of the contents of the cache. Data is
190             printed in timestamp order.
191              
192             The cache I first be opened, and I first be locked.
193              
194             =cut
195              
196             sub dump {
197 2     2 1 17 my $self = shift;
198 2         11 my $hash = $self->get_hash;
199              
200 2         3867 for my $key ( sort { $hash->{$a} <=> $hash->{$b} } keys %$hash )
  55438         297047  
201             {
202 5000         120923 my $value = $hash->{$key};
203 5000         31262 print "$value\t", scalar gmtime $value, "\t$key\n";
204             }
205             }
206              
207             =head2 open
208              
209             $cache->open or die;
210              
211             Open the cache file, and tie it to a hash. This is delegated to the
212             backend.
213              
214             =cut
215              
216             sub open {
217 9     10 1 2223 my $self = shift;
218              
219 9         28 return $self->_delegate( "open", @_ );
220             }
221              
222             =head2 close
223              
224             $cache->close;
225              
226             Close the cache file and untie the hash.
227              
228             =cut
229              
230             sub close {
231 9     10 1 724 my $self = shift;
232              
233 9         25 return $self->_delegate( "close", @_ );
234             }
235              
236             =head2 lock
237              
238             $cache->lock or die; # returns immediately
239             $cache->lock( block => 1 ) or die; # Waits for a lock
240             $cache->lock( %opts ) or die; # Backend-specific options
241              
242             Lock the DB file to guarantee exclusive access.
243              
244             =cut
245              
246             sub lock {
247 6     7 1 264 my $self = shift;
248              
249 6         15 return $self->_delegate( "lock", @_ );
250             }
251              
252             =head2 unlock
253              
254             $cache->unlock or warn "Unlock failed";
255              
256             Unlock the DB file.
257              
258             =cut
259              
260             sub unlock {
261 19     20 1 46858 my $self = shift;
262 19         44 return $self->_delegate( "unlock", @_ );
263             }
264              
265             =head2 purge
266              
267             $cache->purge; # Use default TTL
268             $cache->purge( ttl => 3600 ); # Everything older than 1 hour
269              
270             Purge the cache of old entries. This reduces the risk of false positives
271             from things like reused message IDs, but increases the risk of false
272             negatives.
273              
274             The C option specifies the "time to live": cache entries older
275             than that will be purged. The default is one week. If the TTL is
276             zero, then (just as you'd expect) items one second or older will
277             be purged. If you specify a negative TTL, then the cache will be
278             emptied completely.
279              
280             =cut
281              
282             sub purge {
283 7     8 1 1074 my $self = shift;
284 7         18 my %opts = @_;
285              
286 7         27 my $hash = $self->get_hash;
287 7 100       54 my $ttl = defined $opts{ttl} ? $opts{ttl} : $self->get_ttl;
288 7         16 my $now = time;
289              
290 7         8098 for my $key ( keys %$hash )
291             {
292 9608   100     32264 my $timestamp = $hash->{$key} || 0; # Also clobbers bad data like undef
293 9608 100 100     61239 delete $hash->{$key} if ($now - $timestamp) > $ttl or $ttl < 0;
294             }
295              
296 7         1269 1;
297             }
298              
299             =head2 DESTROY
300              
301             Clean up the module. If the hash is still tied, we warn the user and call
302             C on C<$self>.
303              
304             =head2 DEMOLISH
305              
306             Internal helper method, never called directly by user.
307              
308             =cut
309              
310             sub DEMOLISH {
311 15     16 1 48938 my $self = shift;
312              
313 15         35 my $backend = $self->get_backend;
314              
315             # Failing to close() the cache is bad: data won't be
316             # committed to disk.
317 15 100 100     66 if ( $backend and $backend->is_open )
318             {
319 2         45 cluck "Cache DESTROY()ed before it was close()ed";
320 2         853 $self->close;
321             }
322              
323             # Failure to unlock() is rude, but we don't say anything.
324 15         40 $self->unlock;
325             }
326              
327             =head2 _delegate
328              
329             Delegate the specified method to the backend. Internal method.
330              
331             =cut
332              
333             sub _delegate :PRIVATE() {
334 43     43   70 my ($self, $method, @args) = @_;
335              
336 44         230 my $backend = $self->get_backend;
337 43 100       89 return unless $backend;
338              
339 41         141 return $backend->$method(@args);
340 3     4   1770 }
  4         163  
  3         10  
341              
342             =head1 AUTHOR
343              
344             Len Budney, C<< >>
345              
346             =head1 BUGS
347              
348             The C method assumes that Perl's C function returns
349             seconds since the UNIX epoch, 00:00:00 UTC, January 1, 1970. The
350             module will work on architectures with non-standard epochs, but the
351             automated tests will fail.
352              
353             Please report any bugs or feature requests to
354             C, or through the web interface at
355             L.
356             I will be notified, and then you'll automatically be notified of progress on
357             your bug as I make changes.
358              
359             =head1 SUPPORT
360              
361             You can find documentation for this module with the perldoc command.
362              
363             perldoc Email::Fingerprint::Cache
364              
365             You can also look for information at:
366              
367             =over 4
368              
369             =item * AnnoCPAN: Annotated CPAN documentation
370              
371             L
372              
373             =item * CPAN Ratings
374              
375             L
376              
377             =item * RT: CPAN's request tracker
378              
379             L
380              
381             =item * Search CPAN
382              
383             L
384              
385             =back
386              
387             =head1 ACKNOWLEDGEMENTS
388              
389             Email::Fingerprint::Cache is based on caching code in the
390             C script by Peter Samuel and available at
391             L.
392              
393             =head1 COPYRIGHT & LICENSE
394              
395             Copyright 2006-2011 Len Budney, all rights reserved.
396              
397             This program is free software; you can redistribute it and/or modify it
398             under the same terms as Perl itself.
399              
400             =cut
401              
402             1;