File Coverage

blib/lib/Cache/Meh.pm
Criterion Covered Total %
statement 77 81 95.0
branch 22 32 68.7
condition 2 3 66.6
subroutine 15 15 100.0
pod 6 6 100.0
total 122 137 89.0


line stmt bran cond sub pod time code
1 3     3   70663 use strict;
  3         7  
  3         104  
2 3     3   13 use warnings;
  3         6  
  3         134  
3             package Cache::Meh;
4             $Cache::Meh::VERSION = '0.03';
5 3     3   14 use Carp qw(confess);
  3         8  
  3         225  
6 3     3   3173 use Storable qw(nstore retrieve);
  3         10936  
  3         222  
7 3     3   2580 use File::Spec::Functions qw(tmpdir catfile);
  3         2466  
  3         213  
8 3     3   3687 use File::Temp qw(tempfile);
  3         76165  
  3         215  
9 3     3   3253 use File::Copy qw(move);
  3         7536  
  3         2450  
10              
11             # ABSTRACT: A cache of indifferent quality
12              
13              
14             sub filename {
15 6     6 1 307 my ($self, $f) = @_;
16              
17 6 100       17 if ( defined $f ) {
18 2         12 $self->{filename} = $f;
19             }
20              
21 6         37 return $self->{filename};
22             }
23              
24              
25             sub validity {
26 3     3 1 7 my $self = shift;
27 3         7 my $validity = shift;
28              
29 3 100       11 if ( defined $validity ) {
30 2 50       6 if ( $validity > 0 ) {
31 2         6 $self->{validity} = int($validity);
32             }
33             else {
34 0         0 confess "$validity is not a positive integer\n";
35             }
36             }
37              
38 3         11 return $self->{validity};
39             }
40              
41              
42             sub lookup {
43 1     1 1 1 my $self = shift;
44 1         2 my $coderef = shift;
45              
46 1 50       4 if ( ref($coderef) ne "CODE" ) {
47 0         0 return $self->{lookup};
48             }
49             else {
50 1         2 $self->{lookup} = $coderef;
51             }
52              
53 1         2 return $self->{lookup};
54             }
55              
56              
57             sub new {
58 2     2 1 1302 my $class = shift;
59 2         13 my %args = @_;
60              
61 2         5 my $self = {};
62              
63 2         5 bless $self, $class;
64              
65 2 50       9 confess "You must give a filename" unless exists $args{filename};
66              
67 2         12 $self->filename($args{filename});
68              
69 2         9 $self->{'~~~~cache'} = $self->_load();
70              
71 2 50       128 if ( exists $args{validity} ) {
72 2         10 $self->validity($args{validity});
73             }
74             else {
75 0         0 $self->validity(300);
76             }
77              
78 2 100       11 $self->lookup($args{lookup}) if exists $args{lookup};
79              
80 2         7 return $self;
81             }
82              
83             sub _load {
84 2     2   3 my $self = shift;
85              
86 2         13 my $fname = catfile(tmpdir(), $self->filename());
87              
88 2 100       75 if ( -e $fname ) {
89 1 50       19 if ( -r $fname ) {
90 1         6 return retrieve($fname);
91             }
92             else {
93 0         0 confess "$fname exists but is not readable.\n";
94             }
95             }
96              
97 1         4 return {};
98             }
99              
100             # This method stores the new cache file into a temporary file, then renames the
101             # tempfile to the cache state file name, which should help protect against
102             # new file write failures, leaving at least *some* state that will persist. I
103             # guess you could call this "atomic" but there are still a ton of race
104             # conditions in the IO layer which could bite you in the rear-end.
105              
106             sub _store {
107 2     2   5 my $self = shift;
108              
109 2         43 my ($fh, $filename) = tempfile();
110              
111 2 50       1324 nstore($self->{'~~~~cache'}, $filename) or
112             confess "Couldn't store cache in $filename: $!\n";
113              
114             # Unix doesn't care if the filehandle is still open, but Windows
115             # will not allow a move unless there are no open handles to the
116             # tempfile.
117 2 50       562 close $fh or confess "Couldn't close filehandle for $filename: $!\n";
118              
119 2         13 my $fname = catfile(tmpdir(), $self->filename());
120 2 50       16 move($filename, $fname) or
121             confess "Couldn't rename $filename to $fname: $!\n";
122              
123 2         381 return 1;
124             }
125              
126              
127             sub get {
128 2     2 1 2000194 my ($self, $key) = @_;
129              
130 2 100       20 if ( exists $self->{'~~~~cache'}->{$key} ) {
131 1         12 my $i = $self->{'~~~~cache'}->{$key}->{'insert_time'} + $self->validity;
132 1 50       18 return $self->{'~~~~cache'}->{$key}->{'value'} if ( time < $i ) ;
133             }
134              
135 2 100 66     21 if ( exists $self->{lookup} && ref($self->{lookup}) eq 'CODE' ) {
136 1         4 my $value = $self->{lookup}->($key);
137 1         26 $self->set( $key, $value );
138 1         3 return $value;
139             }
140              
141 1 50       8 if ( exists $self->{'~~~~cache'}->{$key} ) {
142 1         8 delete $self->{'~~~~cache'}->{$key};
143 1         7 $self->_store();
144             }
145              
146 1         6 return undef;
147             }
148              
149              
150             sub set {
151 1     1 1 25 my ($self, $key, $value) = @_;
152              
153 1         10 $self->{'~~~~cache'}->{$key}->{'value'} = $value;
154 1         6 $self->{'~~~~cache'}->{$key}->{'insert_time'} = time;
155              
156 1         3 $self->_store();
157              
158 1         2 return $self;
159             }
160              
161             1;
162              
163             __END__