File Coverage

blib/lib/File/Cache/Persistent.pm
Criterion Covered Total %
statement 65 70 92.8
branch 22 26 84.6
condition 10 12 83.3
subroutine 9 11 81.8
pod 3 5 60.0
total 109 124 87.9


line stmt bran cond sub pod time code
1             package File::Cache::Persistent;
2              
3 1     1   580 use strict;
  1         2  
  1         38  
4              
5 1     1   5 use vars qw($VERSION $CACHE $TIME_CACHE $NO_FILE $NOT_MODIFIED $FILE $PROLONG $TIMEOUT);
  1         2  
  1         955  
6             $VERSION = 0.3;
7              
8             $NO_FILE = 1;
9             $NOT_MODIFIED = $NO_FILE << 1;
10             $FILE = $NOT_MODIFIED << 1;
11             $PROLONG = $FILE << 1;
12             $TIMEOUT = $PROLONG << 1;
13             $CACHE = $TIMEOUT << 1;
14             $TIME_CACHE = $CACHE << 1;
15              
16             sub new {
17 4     4 1 25 my ($class, %args) = @_;
18              
19 4   100     56 my $this = {
      100        
      100        
20             prefix => $args{prefix} || undef,
21             timeout => $args{timeout} || 0,
22             reader => $args{reader} || undef,
23             reader_error => undef,
24             data => {},
25             status => undef,
26             };
27 4         10 bless $this, $class;
28              
29 4         12 return $this;
30             }
31              
32             sub get {
33 18     18 1 61 my ($this, $path) = @_;
34              
35 18 100       71 $path = $this->{prefix} . '/' . $path if $this->{prefix};
36              
37 18         17 my $data;
38 18         34 my $havecache = defined $this->{data}{$path};
39              
40 18         25 $this->{status} = undef;
41 18 100       41 unless ($this->{timeout}) {
42             # Time caching mode is off.
43              
44 10 100       150 unless (-e $path) {
45             # Did not find the file, make an attempt to use cache.
46 1 50       4 unless ($havecache) {
47             # Nope, failed completely.
48 0         0 die "Neither file '$path' nor its cache exists\n";
49             }
50             else {
51             # OK, using cache but no file still exists.
52 1         2 $data = $this->{data}{$path}[0];
53 1         3 $this->{status} = $CACHE + $NO_FILE;
54             }
55             }
56             else {
57             # There is a file. Before using it should test if there is a cached version of it.
58 9 100 66     33 if ($havecache && !$this->_is_modified($path)) {
59             # File was not modified, using cache.
60 2         5 $data = $this->{data}{$path}[0];
61 2         4 $this->{status} = $CACHE + $NOT_MODIFIED;
62             }
63             else {
64             # No cache found or file was modified. Reading it from disk and saving in cache.
65 7         18 $data = $this->_read_file($path);
66 7         12 $this->{status} = $FILE;
67             }
68             }
69             }
70             else {
71             # Time caching mode is on. No attempts to check whether the file was changed if cache is new enough.
72            
73 8 100       35 unless ($havecache) {
    100          
74             # No cache available. Read the file from disk.
75 3 50       37 die "Neither file '$path' nor its cache exists\n" unless -e $path;
76 3         8 $data = $this->_read_file($path);
77 3         5 $this->{status} = $FILE;
78             }
79             elsif (time - $this->{data}{$path}[3] <= $this->{timeout}) {
80             # Good cache. Using it.
81 2         5 $data = $this->{data}{$path}[0];
82 2         5 $this->{status} = $CACHE + $TIME_CACHE;
83             }
84             else {
85             # Cache is outdated.
86 3 100       82 if (-e $path) {
87             # There is a file.
88 2 100       14 if (!$this->_is_modified($path)) {
89             # No changes in file. Thus just prolongating cache time life.
90 1         4 $this->{data}{$path}[3] = time;
91 1         4 $data = $this->{data}{$path}[0];
92 1         5 $this->{status} = $CACHE + $TIME_CACHE + $NOT_MODIFIED + $PROLONG + $TIMEOUT;
93             }
94             else {
95             # Both cache expired and file changed. Reload.
96 1         6 $data = $this->_read_file($path);
97 1         4 $this->{status} = $FILE + $TIMEOUT;
98             }
99             }
100             else {
101             # No file. No panic but using outdated cached document.
102 1         3 $data = $this->{data}{$path}[0];
103 1         3 $this->{status} = $CACHE + $TIME_CACHE + $NO_FILE + $TIMEOUT;
104             }
105             }
106             }
107             # Too many elses? Do it yourself otherwise :-)
108              
109 18         57 return $data;
110             }
111              
112             sub remove {
113 1     1 0 2 my ($this, $path) = @_;
114              
115 1 50       20 $path = $this->{prefix} . '/' . $path if $this->{prefix};
116              
117 1         3 $this->{status} = undef;
118 1         4 delete $this->{data}{$path};
119             }
120              
121             sub status {
122 15     15 1 52 my $this = shift;
123            
124 15         75 return $this->{status};
125             }
126              
127             sub _timeout {
128 0     0   0 my ($this, $path) = @_;
129              
130 0         0 return $this->{data}{$path}[1] - (stat $path)[9];
131             }
132              
133             sub _is_modified {
134 4     4   9 my ($this, $path) = @_;
135              
136             return
137 4   66     89 $this->{data}{$path}[1] != (stat $path)[9] || # mtime
138             $this->{data}{$path}[2] != (stat _)[7]; # size
139             }
140              
141             sub _update_cache {
142 11     11   22 my ($this, $data, $path) = @_;
143              
144 11         182 $this->{data}{$path} = [
145             $data,
146             (stat $path)[9],
147             (stat _)[7],
148             time
149             ];
150             }
151              
152             sub _read_file {
153 11     11   16 my ($this, $path) = @_;
154              
155 11         11 my $data;
156 11 100       24 if (defined $this->{reader}) {
157 1         2 $this->{reader_error} = undef;
158 1         2 eval {
159 1         5 $data = $this->{reader}($path);
160             };
161 1 50       9 $this->{reader_error} = $@ if $@;
162             }
163             else {
164 10         29 local $/;
165 10         15 undef $/;
166 10         305 open my $file, '<', $path;
167 10         196 $data = <$file>;
168 10         120 close $file;
169             }
170              
171 11         28 $this->_update_cache($data, $path);
172            
173 11         32 return $data;
174             }
175              
176             sub reader_error {
177 0     0 0   my $this = shift;
178            
179 0           return $this->{reader_error};
180             }
181              
182             1;
183              
184             __END__