line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Tie::FileLRUCache; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
77327
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
48
|
|
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
1606
|
use Class::ParmList qw (simple_parms parse_parms); |
|
1
|
|
|
|
|
3320
|
|
|
1
|
|
|
|
|
93
|
|
6
|
1
|
|
|
1
|
|
3147
|
use Digest::SHA1 qw(sha1_hex); |
|
1
|
|
|
|
|
4433
|
|
|
1
|
|
|
|
|
76
|
|
7
|
1
|
|
|
1
|
|
7
|
use Fcntl qw (:flock); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
145
|
|
8
|
1
|
|
|
1
|
|
5
|
use File::Spec; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21
|
|
9
|
1
|
|
|
1
|
|
1532
|
use Storable qw (nstore nfreeze retrieve); |
|
1
|
|
|
|
|
4817
|
|
|
1
|
|
|
|
|
85
|
|
10
|
1
|
|
|
1
|
|
9
|
use Symbol qw (gensym); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
54
|
|
11
|
|
|
|
|
|
|
|
12
|
1
|
|
|
1
|
|
6
|
use vars qw ($VERSION); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
47
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
BEGIN { |
15
|
1
|
|
|
1
|
|
5291
|
$VERSION = "1.05"; |
16
|
|
|
|
|
|
|
} |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
########################################################################### |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 NAME |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
Tie::FileLRUCache - A lightweight but robust filesystem based persistent LRU cache |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 CHANGES |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
1.05 2005.09.14 - Changes to pod tests to make them more CPANTS friendly. |
27
|
|
|
|
|
|
|
No functional changes. |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
1.04 2005.09.13 - Removed use of 'warnings' to fix compatibility with Perl 5.005. |
30
|
|
|
|
|
|
|
Fixed minor typographical errors in documentation. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
1.03 2005.09.10 - Changed build test to handle difference in treatment of hashes |
33
|
|
|
|
|
|
|
in scalar contect between 5.6.x and 5.8.x versions of Perl that |
34
|
|
|
|
|
|
|
caused a test failure under Perl 5.6.x. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
1.02 2005.09.08 - Added build tests. Major code cleanup. Improved platform portability. |
37
|
|
|
|
|
|
|
Added and documented 'cache_dir', 'keep_last' and 'number_of_entries' |
38
|
|
|
|
|
|
|
methods. Added Module::Build support. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
1.01 1999.12.09 - Added 'detainting' to cache management code. |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 SYNOPSIS |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head2 OBJECT INTERFACE |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
use Tie::FileLRUCache; |
47
|
|
|
|
|
|
|
my $cache = Tie::FileLRUCache->new({ -cache_dir => $directory, -keep_last => 100 }); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# Inserting value into LRU cache using '-key' |
50
|
|
|
|
|
|
|
$cache->update({ -key => $key, -value => $value }); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Inserting value into LRU cache using '-cache_key' |
54
|
|
|
|
|
|
|
my $cache_key = $cache->make_cache_key({ -key => $key }); |
55
|
|
|
|
|
|
|
$cache->update({ -cache_key => $cache_key, -value => $value }); |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# Checking LRU cache |
59
|
|
|
|
|
|
|
my ($in_cache,$value) = $cache->check({ -key => $key }); |
60
|
|
|
|
|
|
|
if ($in_cache) { |
61
|
|
|
|
|
|
|
return $value; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
# Not in cache - do something else |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# Checking LRU cache with speed up hack for objects, hashes, arrays etc used as keys |
67
|
|
|
|
|
|
|
my $cache_key = $cache->make_cache_key({ -key => $something }); |
68
|
|
|
|
|
|
|
my ($in_cache,$value) = $cache->check({ -cache_key => $cache_key }); |
69
|
|
|
|
|
|
|
if ($in_cache) { |
70
|
|
|
|
|
|
|
return $value; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
# Not in cache - do something else |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# Deleting a key and its value from the cache |
76
|
|
|
|
|
|
|
$cache->delete({ -key => $key }); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# Clearing LRU cache |
80
|
|
|
|
|
|
|
$cache->clear; |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 TIED INTERFACE |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
use Tie::FileLRUCache; |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
[$X =] tie %hash, 'Tie::FileLRUCache', $cache_dir, $keep_last_n; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Adding a key/value to the cache |
89
|
|
|
|
|
|
|
$hash{$key} = $value; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# Checking the cache |
92
|
|
|
|
|
|
|
if (not exists $hash{$key}) {; |
93
|
|
|
|
|
|
|
# No match |
94
|
|
|
|
|
|
|
. |
95
|
|
|
|
|
|
|
. |
96
|
|
|
|
|
|
|
. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
} else { |
99
|
|
|
|
|
|
|
my $value = $hash{$key}; |
100
|
|
|
|
|
|
|
. |
101
|
|
|
|
|
|
|
. |
102
|
|
|
|
|
|
|
. |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Removing a value from the cache; |
107
|
|
|
|
|
|
|
delete $hash{$key}; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Clearing the cache |
110
|
|
|
|
|
|
|
%hash = (); |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Note: Iteration over the cache (each, keys, values) is _NOT_ supported. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=cut |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=head1 DESCRIPTION |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Provides a lightweight persistent filesystem based LRU cache. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
It uses the 'last accessed' timestamp generated by the file system |
121
|
|
|
|
|
|
|
to determine the 'oldest' cache entry and discards the oldest |
122
|
|
|
|
|
|
|
cache entries when needed to stay under the -keep_last limit. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
If you store thing very fast (such that many entries receive the |
125
|
|
|
|
|
|
|
same time stamp), it is essentially a coin toss which entry |
126
|
|
|
|
|
|
|
within a single timestamped second gets purged from the cache |
127
|
|
|
|
|
|
|
to make room for new ones. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
It is not designed to handle huge numbers of cached items. It is probably |
130
|
|
|
|
|
|
|
unwise to set the 'keep_last' higher than around 100. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=cut |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head1 OBJECT METHODS |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=cut |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
####################################################################### |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=over 4 |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=item new({[ -cache_dir => $cache_directory] [, -keep_last => $keep_last_n ] }); |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
Creates and optionally initializes a Tie::FileLRUCache object: |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
Example: |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
my $cache = Tie::FileLRUCache->new({ |
149
|
|
|
|
|
|
|
-cache_dir => '/tmp/testing', |
150
|
|
|
|
|
|
|
-keep_last => 100, |
151
|
|
|
|
|
|
|
}); |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
The default cache size is 100 entries unless specified. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=back |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=cut |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
sub new { |
160
|
13
|
|
|
13
|
1
|
4692
|
my $proto = shift; |
161
|
13
|
|
|
|
|
168
|
my $package = __PACKAGE__; |
162
|
13
|
|
66
|
|
|
73
|
my $class = ref ($proto) || $proto || $package; |
163
|
13
|
|
|
|
|
27
|
my $self = bless {}, $class; |
164
|
|
|
|
|
|
|
|
165
|
13
|
|
|
|
|
125
|
my $parms = parse_parms({ -parms => \@_, |
166
|
|
|
|
|
|
|
-legal => [-cache_dir, -keep_last], |
167
|
|
|
|
|
|
|
-required => [], |
168
|
|
|
|
|
|
|
-defaults => { -keep_last => 100, |
169
|
|
|
|
|
|
|
-cache_dir => undef, |
170
|
|
|
|
|
|
|
}, |
171
|
|
|
|
|
|
|
}); |
172
|
13
|
100
|
|
|
|
1184
|
if (not defined $parms) { |
173
|
1
|
|
|
|
|
6
|
my $error_message = Class::ParmList->error; |
174
|
1
|
|
|
|
|
9
|
require Carp; |
175
|
1
|
|
|
|
|
147
|
Carp::croak ($package . "::new() - Parameter error '$error_message'\n"); |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Save settings |
179
|
12
|
|
|
|
|
50
|
my ($cache_dir,$keep_last) = $parms->get(-cache_dir,-keep_last); |
180
|
12
|
|
|
|
|
282
|
$self->cache_dir($cache_dir); |
181
|
12
|
|
|
|
|
25
|
$self->keep_last($keep_last); |
182
|
|
|
|
|
|
|
|
183
|
12
|
|
|
|
|
63
|
$self; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
####################################################################### |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=over 4 |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=item check({ -key => $key }); |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Reads the cache for the key. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Returns two values: $cache_hit (true if a hit was found, false if not) |
195
|
|
|
|
|
|
|
$value (the cached value, undef if no hit) |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
Examples: |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
my ($cache_hit,$value) = $cache->check({ -key => $key }); |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
my ($cache_hit,$value) = $cache->check({ -cache_key => $cache_key }); |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
The '-key' form is used when you just want to use a raw key. It can use |
204
|
|
|
|
|
|
|
blessed objects, hash refs, scalars, or array refs as keys. The more complex |
205
|
|
|
|
|
|
|
structures take a speed penalty for computing a canonical form. |
206
|
|
|
|
|
|
|
You can minimize this penalty by using the '-cache_key' form instead. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
The '-cache_key' form is used for performance reasons when using keys |
209
|
|
|
|
|
|
|
such as complex blessed objects or hashes as a key. The -cache_key |
210
|
|
|
|
|
|
|
is obtained with a call to 'make_cache_key'. It is legal to mix |
211
|
|
|
|
|
|
|
-cache_key and -key based calls - they are cross-compatible. |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=back |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=cut |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
sub check { |
218
|
48
|
|
|
48
|
1
|
554
|
my $self = shift; |
219
|
48
|
|
|
|
|
80
|
my $package = __PACKAGE__; |
220
|
|
|
|
|
|
|
|
221
|
48
|
100
|
|
|
|
122
|
if (not wantarray) { |
222
|
1
|
|
|
|
|
4
|
require Carp; |
223
|
1
|
|
|
|
|
75
|
Carp::croak ($package . "::check() - Called in a scalar context\n"); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
47
|
|
|
|
|
507
|
my $parms = parse_parms({ -parms => \@_, |
227
|
|
|
|
|
|
|
-legal => [-cache_key, -key], |
228
|
|
|
|
|
|
|
-required => [], |
229
|
|
|
|
|
|
|
-defaults => {}, |
230
|
|
|
|
|
|
|
}); |
231
|
46
|
100
|
|
|
|
8398
|
if (not defined $parms) { |
232
|
1
|
|
|
|
|
4
|
my $error_message = Class::ParmList->error; |
233
|
1
|
|
|
|
|
7
|
require Carp; |
234
|
1
|
|
|
|
|
97
|
Carp::croak ($package . "::check() - $error_message\n"); |
235
|
|
|
|
|
|
|
} |
236
|
45
|
|
|
|
|
7387
|
my ($key,$cache_key) = $parms->get(-key,-cache_key); |
237
|
45
|
100
|
100
|
|
|
1538
|
if (not (defined ($key) or defined ($cache_key))) { |
238
|
1
|
|
|
|
|
3
|
require Carp; |
239
|
1
|
|
|
|
|
79
|
Carp::croak ($package . "::check() - Called without either a -key or -cache_key\n"); |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
44
|
|
|
|
|
147
|
my $cache_dir = $self->cache_dir; |
243
|
44
|
100
|
|
|
|
117
|
unless (defined $cache_dir) { |
244
|
1
|
|
|
|
|
4
|
require Carp; |
245
|
1
|
|
|
|
|
80
|
Carp::croak ($package . "::check - No cache directory set.\n"); |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Ok. Set our lock on the cache |
249
|
43
|
|
|
|
|
115
|
$self->_lock_cache; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Generate the cache_key (done by making a cannonical |
252
|
|
|
|
|
|
|
# network order Storable string out of the key) if we |
253
|
|
|
|
|
|
|
# don't already have it |
254
|
43
|
100
|
|
|
|
163
|
unless (defined $cache_key) { |
255
|
5
|
|
|
|
|
25
|
$cache_key = $self->make_cache_key({ -key => $key }); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# Generate a unique cache file name by taking a SHA1 hash of $cache_key |
259
|
43
|
|
|
|
|
416
|
my $cache_hash = lc(sha1_hex($cache_key)); |
260
|
43
|
|
|
|
|
189
|
$cache_hash =~ s/\s//gs; |
261
|
43
|
|
|
|
|
459
|
my ($untainted_cache_hash) = $cache_hash =~ m/^([a-f0-9]+)$/s; |
262
|
43
|
|
|
|
|
674
|
my $cache_file = File::Spec->catfile($cache_dir, "cl_$untainted_cache_hash"); |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# Check if there is a cache entry for this key |
265
|
43
|
100
|
|
|
|
1580
|
unless (-e $cache_file) { |
266
|
22
|
|
|
|
|
86
|
$self->_unlock_cache; |
267
|
22
|
|
|
|
|
308
|
return (0,undef); |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# Yes. Get it. And update the last modified and last accessed dates. |
271
|
21
|
|
|
|
|
31
|
my $entry; |
272
|
21
|
|
|
|
|
51
|
eval { |
273
|
21
|
|
|
|
|
91
|
$entry = retrieve($cache_file); |
274
|
21
|
|
|
|
|
2073
|
my $now = time; |
275
|
21
|
|
|
|
|
1346
|
utime ($now, $now, $cache_file); |
276
|
|
|
|
|
|
|
}; |
277
|
21
|
50
|
|
|
|
69
|
if ($@) { |
278
|
0
|
|
|
|
|
0
|
my $error = $@; |
279
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
280
|
0
|
|
|
|
|
0
|
require Carp; |
281
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::check - Error while retrieving cache entry file '$cache_file': $error\n"); |
282
|
|
|
|
|
|
|
} |
283
|
21
|
50
|
|
|
|
51
|
unless (defined $entry) { |
284
|
0
|
|
|
|
|
0
|
my $error = $!; |
285
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
286
|
0
|
|
|
|
|
0
|
require Carp; |
287
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::update - Failed to retrieve cache entry file '$cache_file': $error\n"); |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# Release the lock. |
291
|
21
|
|
|
|
|
69
|
$self->_unlock_cache; |
292
|
|
|
|
|
|
|
|
293
|
21
|
|
|
|
|
56
|
my $cache_value = $entry->{'-value'}; |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# Give them their cupie doll |
296
|
21
|
|
|
|
|
320
|
return (1, $cache_value); |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
####################################################################### |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=over 4 |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=item make_cache_key({ -key => $key }); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
Generates a cache key by canonicalizing a passed |
306
|
|
|
|
|
|
|
key as a network ordered canonical Storable string. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
Example: |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
my $cache_key = $cache->make_cache_key({ -key => $key }); |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
=back |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=cut |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
sub make_cache_key { |
317
|
11
|
|
|
11
|
1
|
28
|
my $self = shift; |
318
|
11
|
|
|
|
|
21
|
my $package = __PACKAGE__; |
319
|
11
|
|
|
|
|
81
|
my $parms = parse_parms({ -parms => \@_, |
320
|
|
|
|
|
|
|
-legal => [], |
321
|
|
|
|
|
|
|
-required => ['-key'], |
322
|
|
|
|
|
|
|
-defaults => {}, |
323
|
|
|
|
|
|
|
}); |
324
|
11
|
100
|
|
|
|
933
|
unless (defined $parms) { |
325
|
1
|
|
|
|
|
8
|
my $error_message = Class::ParmList->error; |
326
|
1
|
|
|
|
|
9
|
require Carp; |
327
|
1
|
|
|
|
|
130
|
Carp::croak ($package . "::make_cache_key() - $error_message\n"); |
328
|
|
|
|
|
|
|
} |
329
|
10
|
|
|
|
|
37
|
my ($key) = $parms->get(-key); |
330
|
|
|
|
|
|
|
|
331
|
10
|
|
|
|
|
260
|
my $temp = $Storable::canonical; |
332
|
10
|
|
|
|
|
35
|
my $result = nfreeze(\$key); |
333
|
10
|
|
|
|
|
397
|
$Storable::canonical = $temp; |
334
|
|
|
|
|
|
|
|
335
|
10
|
50
|
|
|
|
25
|
if (not $result) { |
336
|
0
|
|
|
|
|
0
|
my $error = $!; |
337
|
0
|
|
|
|
|
0
|
require Carp; |
338
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::check() - Unable to serialize passed -key value: $error"); |
339
|
|
|
|
|
|
|
} |
340
|
10
|
|
|
|
|
54
|
return $result; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
####################################################################### |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=over 4 |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=item clear; |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
Completely clears the cache of all cache entries. |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=back |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=cut |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
sub clear { |
356
|
4
|
|
|
4
|
1
|
28
|
my $self = shift; |
357
|
4
|
|
|
|
|
9
|
my $package = __PACKAGE__; |
358
|
4
|
|
|
|
|
13
|
my $cache_dir = $self->cache_dir; |
359
|
|
|
|
|
|
|
|
360
|
4
|
100
|
|
|
|
15
|
unless (defined $cache_dir) { |
361
|
1
|
|
|
|
|
12
|
require Carp; |
362
|
1
|
|
|
|
|
163
|
Carp::croak ($package . "::clear - No cache directory set.\n"); |
363
|
|
|
|
|
|
|
} |
364
|
3
|
100
|
|
|
|
9
|
if ($cache_dir eq '') { |
365
|
1
|
|
|
|
|
5
|
require Carp; |
366
|
1
|
|
|
|
|
80
|
Carp::croak ($package . "::clear - Cannot use root directory as cache directory.\n"); |
367
|
|
|
|
|
|
|
} |
368
|
2
|
50
|
33
|
|
|
58
|
if ((-e $cache_dir) and (not -d _)) { |
369
|
0
|
|
|
|
|
0
|
require Carp; |
370
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::clear - '$cache_dir' already exists and is not a directory.\n"); |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
2
|
|
|
|
|
8
|
$self->_lock_cache; |
374
|
|
|
|
|
|
|
|
375
|
2
|
|
|
|
|
11
|
my $cache_dir_fh = gensym; |
376
|
2
|
50
|
|
|
|
135
|
if (not opendir ($cache_dir_fh, $cache_dir)) { |
377
|
0
|
|
|
|
|
0
|
my $error = $!; |
378
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
379
|
0
|
|
|
|
|
0
|
require Carp; |
380
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::clear - Failed to open directory '$cache_dir' for reading: $error\n"); |
381
|
|
|
|
|
|
|
} |
382
|
|
|
|
|
|
|
|
383
|
2
|
|
|
|
|
48
|
my @raw_directory_list = readdir($cache_dir_fh); |
384
|
2
|
50
|
|
|
|
38
|
unless (closedir ($cache_dir_fh)) { |
385
|
0
|
|
|
|
|
0
|
my $error = $!; |
386
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
387
|
0
|
|
|
|
|
0
|
require Carp; |
388
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::clear - Failed to close directory '$cache_dir': $error\n"); |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Untaint the filenames, convert them to absolute file paths and unlink them. |
392
|
2
|
|
|
|
|
41
|
my @raw_files_list = grep(/^(cacheline_[a-zA-Z0-9]{1,50}|cl_[a-zA-Z0-9]{1,50})$/s, @raw_directory_list); |
393
|
2
|
|
|
|
|
4
|
my @file_list = (); |
394
|
2
|
|
|
|
|
6
|
foreach my $item (@raw_files_list) { |
395
|
10
|
|
|
|
|
73
|
my ($filename) = $item =~ m/^(.*)$/s; |
396
|
10
|
|
|
|
|
138
|
my $file_path = File::Spec->catfile($cache_dir, $filename); |
397
|
10
|
50
|
|
|
|
5379
|
unless (unlink $file_path) { |
398
|
0
|
|
|
|
|
0
|
my $error = $!; |
399
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
400
|
0
|
|
|
|
|
0
|
require Carp; |
401
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::clear - Failed to unlink file '$file_path': $error"); |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
2
|
|
|
|
|
16
|
$self->_unlock_cache; |
406
|
|
|
|
|
|
|
|
407
|
2
|
|
|
|
|
26
|
return; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
####################################################################### |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=over 4 |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=item update({ [-key => $key,] [-cache_key => $cache_key, ], -value => $value [, -keep_last => $keep_last_n ] }); |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
Updates the Least Recently Used (LRU) cache for the specified |
417
|
|
|
|
|
|
|
key with the passed value. '-keep_last' is optional after the first access |
418
|
|
|
|
|
|
|
to a dataset. It will use the I 'keep_last' used |
419
|
|
|
|
|
|
|
if not specified. |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
It is legal to use ordinary scalars, hash references, or array references |
422
|
|
|
|
|
|
|
as keys as well as objects as -keys or -values. Basically, anything that |
423
|
|
|
|
|
|
|
Storable can reproducibly serialize can be used. |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
Examples: |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
$cache->update({ -key => $key, -value => $value }); |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
$cache->update({ -key => $key, -value => $value, -keep_last => 100}); |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
my $cache_key = $cache->make_cache_key({ -key => $key }); |
432
|
|
|
|
|
|
|
$cache->update({ -cache_key => $cache_key, -value => $value }); |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
my $cache_key = $cache->make_cache_key({ -key => $key }); |
435
|
|
|
|
|
|
|
$cache->update({ -cache_key => $cache_key, -value => $value, -keep_last => 50 }); |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
-cache_key is assumed to be a simple scalar value for use as a key. |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
-key can be pretty much anything Storable can successfully and reproducibly serialize. |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
One or the other I be passed. |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
=back |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=cut |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
sub update { |
448
|
21
|
|
|
21
|
1
|
6001245
|
my $self = shift; |
449
|
21
|
|
|
|
|
37
|
my $package = __PACKAGE__; |
450
|
|
|
|
|
|
|
|
451
|
21
|
|
|
|
|
242
|
my $parms = parse_parms({ -parms => \@_, |
452
|
|
|
|
|
|
|
-legal => ['-keep_last', '-key', '-cache_key'], |
453
|
|
|
|
|
|
|
-required => ['-value'], |
454
|
|
|
|
|
|
|
-defaults => {'-keep_last' => $self->keep_last}, |
455
|
|
|
|
|
|
|
}); |
456
|
21
|
100
|
|
|
|
3259
|
unless (defined $parms) { |
457
|
1
|
|
|
|
|
5
|
my $error_message = Class::ParmList->error; |
458
|
1
|
|
|
|
|
7
|
require Carp; |
459
|
1
|
|
|
|
|
110
|
Carp::croak ($package . "::update() - $error_message\n"); |
460
|
|
|
|
|
|
|
} |
461
|
20
|
|
|
|
|
138
|
my ($key,$cache_key,$value,$keep_last) = $parms->get('-key', '-cache_key', '-value', '-keep_last'); |
462
|
20
|
100
|
100
|
|
|
1792
|
unless (defined ($key) or defined ($cache_key)) { |
463
|
1
|
|
|
|
|
4
|
require Carp; |
464
|
1
|
|
|
|
|
106
|
Carp::croak ($package . "::update() - Called without either a -key or -cache_key. At least one of them must be passed.\n"); |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
19
|
|
|
|
|
241
|
my ($cache_dir) = $self->cache_dir; |
468
|
19
|
100
|
|
|
|
67
|
unless (defined $cache_dir) { |
469
|
1
|
|
|
|
|
4
|
require Carp; |
470
|
1
|
|
|
|
|
90
|
Carp::croak ($package . "::update - No cache directory set.\n"); |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
# Generate the cache_key (done by making a cannonical |
474
|
|
|
|
|
|
|
# network order Storable string out of the key) if we |
475
|
|
|
|
|
|
|
# don't already have one. |
476
|
18
|
100
|
|
|
|
60
|
unless (defined $cache_key) { |
477
|
2
|
|
|
|
|
9
|
$cache_key = $self->make_cache_key({ -key => $key }); |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# Generate a unique cache file |
481
|
|
|
|
|
|
|
# name by taking a SHA1 hash of |
482
|
|
|
|
|
|
|
# $cache_key and dumping it as hex |
483
|
18
|
|
|
|
|
203
|
my $cache_hash = lc(sha1_hex($cache_key)); |
484
|
18
|
|
|
|
|
2798
|
$cache_hash =~ s/\s//gs; |
485
|
18
|
|
|
|
|
165
|
my ($untainted_cache_hash) = $cache_hash =~ m/^([a-f0-9]+)$/s; |
486
|
18
|
|
|
|
|
382
|
my $cache_file = File::Spec->catfile($cache_dir, "cl_$untainted_cache_hash"); |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# Serialize the $value for storage |
489
|
18
|
|
|
|
|
81
|
my $entry = { -value => $value }; |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
# Set our lock on the cache directory |
492
|
18
|
|
|
|
|
1560
|
$self->_lock_cache; |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
########## |
495
|
|
|
|
|
|
|
# Store the cache entry. |
496
|
18
|
|
|
|
|
27
|
my $result; |
497
|
18
|
|
|
|
|
56
|
eval { $result = nstore($entry,$cache_file); }; |
|
18
|
|
|
|
|
115
|
|
498
|
18
|
50
|
|
|
|
7207
|
if ($@) { |
499
|
0
|
|
|
|
|
0
|
my $error = $@; |
500
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
501
|
0
|
|
|
|
|
0
|
require Carp; |
502
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::update - Error while saving cache entry file '$cache_file': $error"); |
503
|
|
|
|
|
|
|
} |
504
|
18
|
50
|
|
|
|
66
|
unless (defined $result) { |
505
|
0
|
|
|
|
|
0
|
my $error = $!; |
506
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
507
|
0
|
|
|
|
|
0
|
require Carp; |
508
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::update - Error while saving cache entry file '$cache_file': $error\n"); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
######################################## |
512
|
|
|
|
|
|
|
# Check if we need to purge old entries |
513
|
18
|
|
|
|
|
74
|
my $cache_dir_fh = gensym; |
514
|
18
|
50
|
|
|
|
1349
|
unless (opendir ($cache_dir_fh, $cache_dir)) { |
515
|
0
|
|
|
|
|
0
|
my $error = $!; |
516
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
517
|
0
|
|
|
|
|
0
|
require Carp; |
518
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::update - Failed to open directory '$cache_dir' for reading: $error\n"); |
519
|
|
|
|
|
|
|
} |
520
|
18
|
|
|
|
|
2149
|
my @raw_file_list = grep(/^(cacheline_[a-fA-F0-9]{1,50}|cl_[a-fA-F0-9]{1,50})$/s,readdir($cache_dir_fh)); |
521
|
18
|
50
|
|
|
|
427
|
unless (closedir ($cache_dir_fh)) { |
522
|
0
|
|
|
|
|
0
|
my $error = $!; |
523
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
524
|
0
|
|
|
|
|
0
|
require Carp; |
525
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::update - Failed to close directory '$cache_dir': $error\n"); |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
# Untainting the filenames and converting them to absolute file paths. |
529
|
18
|
|
|
|
|
44
|
my @file_list = (); |
530
|
18
|
|
|
|
|
56
|
foreach my $item (@raw_file_list) { |
531
|
58
|
|
|
|
|
222
|
my ($filename) = $item =~ m/^(.*)$/s; |
532
|
58
|
|
|
|
|
625
|
my $file_path = File::Spec->catfile($cache_dir, $filename); |
533
|
58
|
|
|
|
|
182
|
push (@file_list,$file_path); |
534
|
|
|
|
|
|
|
} |
535
|
18
|
|
|
|
|
55
|
my $n_files = $#file_list + 1; |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# No problems. All done. |
538
|
18
|
100
|
|
|
|
51
|
if ($n_files <= $keep_last) { |
539
|
14
|
|
|
|
|
57
|
$self->_unlock_cache; |
540
|
14
|
|
|
|
|
687
|
return; |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
# Too many entries. Delete the excess entries (usually only one) |
544
|
4
|
|
|
|
|
15
|
my %file_last_access = (); |
545
|
4
|
|
|
|
|
11
|
foreach my $file (@file_list) { |
546
|
24
|
|
|
|
|
493
|
my $last_accessed = (stat($file))[9]; |
547
|
24
|
|
|
|
|
89
|
$file_last_access{$file} = $last_accessed; |
548
|
|
|
|
|
|
|
} |
549
|
4
|
|
|
|
|
36
|
my @sorted_file_list = sort { $file_last_access{$b} <=> $file_last_access{$a} } @file_list; |
|
40
|
|
|
|
|
96
|
|
550
|
4
|
|
66
|
|
|
34
|
while (($n_files > $keep_last) and ($n_files > 0)) { |
551
|
4
|
|
|
|
|
7
|
$n_files--; |
552
|
4
|
|
|
|
|
9
|
my $pruned_file = $sorted_file_list[$n_files]; |
553
|
4
|
50
|
|
|
|
126818
|
unless (unlink $pruned_file) { |
554
|
0
|
|
|
|
|
0
|
my $error = $!; |
555
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
556
|
0
|
|
|
|
|
0
|
require Carp; |
557
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::update - Failed to unlink file '$pruned_file': $error"); |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
# Release our lock and return |
562
|
4
|
|
|
|
|
169
|
$self->_unlock_cache; |
563
|
4
|
|
|
|
|
111
|
return; |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
####################################################################### |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
=over 4 |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
=item delete({ -key => $key }); |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
Forces the deletion of a specific key from the cache. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Example: |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
$cache->delete({ -key => $key }); |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=back |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=cut |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
sub delete { |
583
|
8
|
|
|
8
|
1
|
86
|
my $self = shift; |
584
|
8
|
|
|
|
|
10
|
my $package = __PACKAGE__; |
585
|
|
|
|
|
|
|
|
586
|
8
|
|
|
|
|
60
|
my $parms = parse_parms({ -parms => \@_, |
587
|
|
|
|
|
|
|
-legal => [-key, -cache_key], |
588
|
|
|
|
|
|
|
-required => [], |
589
|
|
|
|
|
|
|
-defaults => {}, |
590
|
|
|
|
|
|
|
}); |
591
|
7
|
100
|
|
|
|
529
|
if (not defined $parms) { |
592
|
1
|
|
|
|
|
4
|
my $error_message = Class::ParmList->error; |
593
|
1
|
|
|
|
|
6
|
require Carp; |
594
|
1
|
|
|
|
|
97
|
Carp::croak ($package . "::delete() - $error_message\n"); |
595
|
|
|
|
|
|
|
} |
596
|
6
|
|
|
|
|
28
|
my ($key,$cache_key) = $parms->get(-key, -cache_key); |
597
|
6
|
100
|
100
|
|
|
149
|
if (not (defined ($key) or defined ($cache_key))) { |
598
|
1
|
|
|
|
|
4
|
require Carp; |
599
|
1
|
|
|
|
|
75
|
Carp::croak ($package . "::delete() - Called without either a -key or -cache_key\n"); |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
|
602
|
5
|
|
|
|
|
12
|
my $cache_dir = $self->cache_dir; |
603
|
5
|
100
|
|
|
|
13
|
unless (defined $cache_dir) { |
604
|
1
|
|
|
|
|
25
|
require Carp; |
605
|
1
|
|
|
|
|
82
|
Carp::croak ($package . "::delete - No cache directory set.\n"); |
606
|
|
|
|
|
|
|
} |
607
|
4
|
50
|
|
|
|
12
|
if ($cache_dir eq '') { |
608
|
0
|
|
|
|
|
0
|
require Carp; |
609
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::delete - Cannot use root directory as cache directory.\n"); |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# Generate the cache_key (done by making a cannonical |
613
|
|
|
|
|
|
|
# network order Storable string out of the key) if we |
614
|
|
|
|
|
|
|
# don't already have it |
615
|
4
|
100
|
|
|
|
11
|
if (not defined $cache_key) { |
616
|
2
|
|
|
|
|
8
|
$cache_key = $self->make_cache_key({ -key => $key }); |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
# Generate a unique cache file |
620
|
|
|
|
|
|
|
# name by taking a SHA1 hash of |
621
|
|
|
|
|
|
|
# $cache_key |
622
|
4
|
|
|
|
|
21
|
my $cache_hash = lc(sha1_hex($cache_key)); |
623
|
4
|
|
|
|
|
11
|
$cache_hash =~ s/\s//gs; |
624
|
4
|
|
|
|
|
19
|
my ($untainted_cache_hash) = $cache_hash =~ m/^([a-f0-9]+)$/s; |
625
|
4
|
|
|
|
|
52
|
my $cache_file = File::Spec->catfile($cache_dir, "cl_$untainted_cache_hash"); |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# Ok. Set our lock on the cache directory |
628
|
4
|
|
|
|
|
14
|
$self->_lock_cache; |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# If it is in the cache, remove it |
631
|
4
|
50
|
33
|
|
|
446
|
if ((-e $cache_file) and (not unlink $cache_file)) { |
632
|
0
|
|
|
|
|
0
|
my $error = $!; |
633
|
0
|
|
|
|
|
0
|
$self->_unlock_cache; |
634
|
0
|
|
|
|
|
0
|
require Carp; |
635
|
0
|
|
|
|
|
0
|
Carp::croak($package . "::delete - Failed to unlink file '$cache_file': $error"); |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
# Release our lock and return |
639
|
4
|
|
|
|
|
14
|
$self->_unlock_cache; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
####################################################################### |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=over 4 |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=item cache_dir([$cache_directory_path]); |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Get/Set accessor for the cache directory path. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
Ex. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
my $cache_directory = $cache->cache_dir; |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
$cache->cache_dir($cache_directory); |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
=back |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=cut |
659
|
|
|
|
|
|
|
|
660
|
163
|
|
|
163
|
1
|
12950
|
sub cache_dir { return shift->_property('cache_dir', @_); } |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
####################################################################### |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
=over 4 |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
=item keep_last([$keep_last_n]); |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
Get/Set accessor for the keep last N setting. |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
Ex. |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
my $n_last = $cache->keep_last; |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
$cache->keep_last(20); |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
=back |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=cut |
679
|
|
|
|
|
|
|
|
680
|
37
|
|
|
37
|
1
|
124
|
sub keep_last { return shift->_property('keep_last', @_); } |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
####################################################################### |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=over 4 |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=item number_of_entries; |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
Returns the current number of entries in the cache. |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=back |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
=cut |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
sub number_of_entries { |
696
|
2
|
|
|
2
|
1
|
2000337
|
my $self = shift; |
697
|
2
|
|
|
|
|
8
|
my $package = __PACKAGE__; |
698
|
|
|
|
|
|
|
|
699
|
2
|
|
|
|
|
23
|
my $cache_dir_fh = gensym; |
700
|
2
|
|
|
|
|
64
|
my $cache_dir = $self->cache_dir; |
701
|
2
|
50
|
|
|
|
15
|
unless (defined $cache_dir) { |
702
|
0
|
|
|
|
|
0
|
require Carp; |
703
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::delete - No cache directory set.\n"); |
704
|
|
|
|
|
|
|
} |
705
|
2
|
50
|
|
|
|
12
|
if ($cache_dir eq '') { |
706
|
0
|
|
|
|
|
0
|
require Carp; |
707
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::delete - Cannot use root directory as cache directory.\n"); |
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
|
710
|
2
|
50
|
|
|
|
156
|
unless (opendir ($cache_dir_fh, $cache_dir)) { |
711
|
0
|
|
|
|
|
0
|
my $error = $!; |
712
|
0
|
|
|
|
|
0
|
require Carp; |
713
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::update - Failed to open directory '$cache_dir' for reading: $error\n"); |
714
|
|
|
|
|
|
|
} |
715
|
2
|
|
|
|
|
171
|
my @raw_file_list = grep(/^(cacheline_[a-fA-F0-9]{1,50}|cl_[a-fA-F0-9]{1,50})$/s,readdir($cache_dir_fh)); |
716
|
2
|
50
|
|
|
|
134
|
unless (closedir ($cache_dir_fh)) { |
717
|
0
|
|
|
|
|
0
|
my $error = $!; |
718
|
0
|
|
|
|
|
0
|
require Carp; |
719
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::update - Failed to close directory '$cache_dir': $error\n"); |
720
|
|
|
|
|
|
|
} |
721
|
2
|
|
|
|
|
8
|
my $n_entries = $#raw_file_list + 1; |
722
|
2
|
|
|
|
|
21
|
return $n_entries; |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
####################################################################### |
726
|
|
|
|
|
|
|
# # |
727
|
|
|
|
|
|
|
# PRIVATE METHODS # |
728
|
|
|
|
|
|
|
# # |
729
|
|
|
|
|
|
|
# Internals. Documented for maintainance reasons only. # |
730
|
|
|
|
|
|
|
# Do not use these methods from outside this module. # |
731
|
|
|
|
|
|
|
# # |
732
|
|
|
|
|
|
|
####################################################################### |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
####################################################################### |
735
|
|
|
|
|
|
|
# _cache_lock_fh([$fh]); |
736
|
|
|
|
|
|
|
# |
737
|
|
|
|
|
|
|
# Get/Set accessor used to store a reference to the filehandle |
738
|
|
|
|
|
|
|
# used for locking. |
739
|
|
|
|
|
|
|
|
740
|
134
|
|
|
134
|
|
400
|
sub _cache_lock_fh { return shift->_property('_cache_lock_fh', @_); } |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
####################################################################### |
743
|
|
|
|
|
|
|
# _lock_cache; |
744
|
|
|
|
|
|
|
# |
745
|
|
|
|
|
|
|
# Obtains a lock on the 'cache.lock' file for this LRU cache. |
746
|
|
|
|
|
|
|
# |
747
|
|
|
|
|
|
|
# Example: |
748
|
|
|
|
|
|
|
# $self->_lock_cache; |
749
|
|
|
|
|
|
|
# |
750
|
|
|
|
|
|
|
# This will create the 'cache.lock' file if it does not already exist, |
751
|
|
|
|
|
|
|
# creating any intermediate directories as needed. |
752
|
|
|
|
|
|
|
# |
753
|
|
|
|
|
|
|
# It also writes the current PID to the lock file. |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
sub _lock_cache { |
756
|
67
|
|
|
67
|
|
114
|
my $self = shift; |
757
|
67
|
|
|
|
|
95
|
my $package = __PACKAGE__; |
758
|
|
|
|
|
|
|
|
759
|
67
|
|
|
|
|
171
|
my $cache_dir = $self->cache_dir; |
760
|
67
|
50
|
|
|
|
3238
|
if (not defined $cache_dir) { |
761
|
0
|
|
|
|
|
0
|
require Carp; |
762
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - No cache directory set.\n"); |
763
|
|
|
|
|
|
|
} |
764
|
67
|
50
|
|
|
|
5420
|
if ($cache_dir eq '') { |
765
|
0
|
|
|
|
|
0
|
require Carp; |
766
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - Cannot use root directory as cache directory.\n"); |
767
|
|
|
|
|
|
|
} |
768
|
67
|
50
|
33
|
|
|
7122
|
if ((-e $cache_dir) and (not -d _)) { |
769
|
0
|
|
|
|
|
0
|
require Carp; |
770
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - '$cache_dir' already exists and is not a directory.\n"); |
771
|
|
|
|
|
|
|
} |
772
|
67
|
50
|
|
|
|
1689
|
if (not -e $cache_dir) { |
773
|
0
|
|
|
|
|
0
|
eval { |
774
|
0
|
|
|
|
|
0
|
require File::Path; |
775
|
0
|
|
|
|
|
0
|
File::Path::mkpath ($cache_dir); |
776
|
|
|
|
|
|
|
}; |
777
|
0
|
0
|
|
|
|
0
|
if ($@) { |
778
|
0
|
|
|
|
|
0
|
my $error = $@; |
779
|
0
|
|
|
|
|
0
|
require Carp; |
780
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - unable to create directory '$cache_dir': $error"); |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
} |
783
|
67
|
50
|
33
|
|
|
2073
|
if (not ((-e $cache_dir) and (-d _))) { |
784
|
0
|
|
|
|
|
0
|
require Carp; |
785
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - Unable to create directory '$cache_dir'\n"); |
786
|
|
|
|
|
|
|
} |
787
|
67
|
|
|
|
|
1296
|
my $document_name = File::Spec->catfile($cache_dir,'.cache.lock'); |
788
|
67
|
|
|
|
|
375
|
my $cache_lock_fh = gensym; |
789
|
67
|
50
|
|
|
|
14474
|
unless (open ($cache_lock_fh,">>$document_name")) { |
790
|
0
|
|
|
|
|
0
|
my $error = $!; |
791
|
0
|
|
|
|
|
0
|
require Carp; |
792
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - Unable to open '$document_name': $error\n"); |
793
|
|
|
|
|
|
|
} |
794
|
67
|
|
|
|
|
154
|
my $lock_timeout = 100; |
795
|
67
|
|
|
|
|
686
|
while (not flock($cache_lock_fh, LOCK_EX()|LOCK_NB())) { |
796
|
0
|
|
|
|
|
0
|
$lock_timeout--; |
797
|
0
|
|
|
|
|
0
|
select (undef,undef,undef,0.1); |
798
|
0
|
0
|
|
|
|
0
|
if ($lock_timeout == 0) { |
799
|
0
|
|
|
|
|
0
|
my $error = $!; |
800
|
0
|
|
|
|
|
0
|
require Carp; |
801
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - Unable to get an exclusive lock on '$document_name': $error\n"); |
802
|
|
|
|
|
|
|
} |
803
|
|
|
|
|
|
|
} |
804
|
67
|
|
|
|
|
289
|
my $fh = select ($cache_lock_fh); |
805
|
67
|
|
|
|
|
9665
|
$|++; |
806
|
67
|
|
|
|
|
214
|
select ($fh); |
807
|
67
|
50
|
|
|
|
1063084
|
unless (truncate ($cache_lock_fh, 0)) { |
808
|
0
|
|
|
|
|
0
|
my $error = $!; |
809
|
0
|
|
|
|
|
0
|
require Carp; |
810
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - Unable to truncate '$document_name': $error\n"); |
811
|
|
|
|
|
|
|
} |
812
|
67
|
|
|
|
|
3356146
|
print $cache_lock_fh "$$\n"; |
813
|
67
|
|
|
|
|
354
|
$self->_cache_lock_fh($cache_lock_fh); |
814
|
|
|
|
|
|
|
|
815
|
67
|
|
|
|
|
520
|
return; |
816
|
|
|
|
|
|
|
} |
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
####################################################################### |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
# _unlock_cache; |
821
|
|
|
|
|
|
|
# |
822
|
|
|
|
|
|
|
# Release a lock on the 'cache.lock' file for this LRU cache. |
823
|
|
|
|
|
|
|
# |
824
|
|
|
|
|
|
|
# Example: |
825
|
|
|
|
|
|
|
# $self->_unlock_cache; |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
sub _unlock_cache { |
828
|
67
|
|
|
67
|
|
119
|
my $self = shift; |
829
|
67
|
|
|
|
|
128
|
my $package = __PACKAGE__; |
830
|
|
|
|
|
|
|
|
831
|
67
|
|
|
|
|
176
|
my $cache_lock_fh = $self->_cache_lock_fh; |
832
|
67
|
50
|
|
|
|
4013798
|
unless (truncate ($cache_lock_fh,0)) { |
833
|
0
|
|
|
|
|
0
|
my $error = $!; |
834
|
0
|
|
|
|
|
0
|
require Carp; |
835
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_lock_cache - Unable to truncate cache.lock file: $error\n"); |
836
|
|
|
|
|
|
|
} |
837
|
67
|
50
|
|
|
|
8148
|
unless (close ($cache_lock_fh)) { |
838
|
0
|
|
|
|
|
0
|
my $error = $!; |
839
|
0
|
|
|
|
|
0
|
require Carp; |
840
|
0
|
|
|
|
|
0
|
Carp::croak ($package . "::_unlock_cache - Error while closing cache.lock file: $error\n"); |
841
|
|
|
|
|
|
|
} |
842
|
67
|
|
|
|
|
224
|
return; |
843
|
|
|
|
|
|
|
} |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
#################################################################### |
846
|
|
|
|
|
|
|
# _property('property_name' => $property_value) |
847
|
|
|
|
|
|
|
# |
848
|
|
|
|
|
|
|
# get/set base accessor for property values |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
sub _property { |
851
|
334
|
|
|
334
|
|
472
|
my $self = shift; |
852
|
|
|
|
|
|
|
|
853
|
334
|
|
|
|
|
1285
|
my $property = shift; |
854
|
|
|
|
|
|
|
|
855
|
334
|
|
|
|
|
1779
|
my $package = __PACKAGE__; |
856
|
334
|
100
|
|
|
|
829
|
if (0 == @_) { |
|
|
50
|
|
|
|
|
|
857
|
229
|
|
|
|
|
537
|
my $output = $self->{$package}->{$property}; |
858
|
229
|
|
|
|
|
1878
|
return $output; |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
} elsif (1 == @_) { |
861
|
105
|
|
|
|
|
149
|
my $input = shift; |
862
|
105
|
|
|
|
|
316
|
$self->{$package}->{$property} = $input; |
863
|
105
|
|
|
|
|
1039
|
return; |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
} else { |
866
|
0
|
|
|
|
|
0
|
require Carp; |
867
|
0
|
|
|
|
|
0
|
Carp::croak("Bad calling parameters to ${package}::${property}()\n"); |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
#################################################################### |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
sub TIEHASH { |
874
|
4
|
|
|
4
|
|
5641
|
my $proto = shift; |
875
|
4
|
|
|
|
|
7
|
my $package = __PACKAGE__; |
876
|
4
|
|
33
|
|
|
42
|
my $class = ref ($proto) || $proto || $package; |
877
|
4
|
|
|
|
|
10
|
my $self = bless {}, $class; |
878
|
|
|
|
|
|
|
|
879
|
4
|
|
|
|
|
9
|
my ($cache_dir,$keep_last) = @_; |
880
|
|
|
|
|
|
|
|
881
|
4
|
100
|
|
|
|
38
|
$keep_last = 100 unless (defined $keep_last); |
882
|
4
|
100
|
66
|
|
|
21
|
unless (defined ($cache_dir) and ($cache_dir ne '')) { |
883
|
1
|
|
|
|
|
14
|
require Carp; |
884
|
1
|
|
|
|
|
684
|
Carp::croak($package . ": Missing required parameter (cache_dir)\n"); |
885
|
|
|
|
|
|
|
} |
886
|
3
|
|
|
|
|
9
|
$self->cache_dir($cache_dir); |
887
|
3
|
|
|
|
|
9
|
$self->keep_last($keep_last); |
888
|
3
|
|
|
|
|
11
|
return $self; |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
####################################################################### |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
sub STORE { |
894
|
9
|
|
|
9
|
|
6006297
|
my $self = shift; |
895
|
|
|
|
|
|
|
|
896
|
9
|
|
|
|
|
42
|
my ($key,$value) = @_; |
897
|
|
|
|
|
|
|
|
898
|
9
|
100
|
|
|
|
76
|
if (ref(\$key) eq 'SCALAR') { |
899
|
8
|
|
|
|
|
100
|
$self->update({ -cache_key => $key, -value => $value }); |
900
|
|
|
|
|
|
|
} else { |
901
|
1
|
|
|
|
|
6
|
$self->update({ -key => $key, -value => $value }); |
902
|
|
|
|
|
|
|
} |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
####################################################################### |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
sub FETCH { |
908
|
7
|
|
|
7
|
|
64
|
my $self = shift; |
909
|
|
|
|
|
|
|
|
910
|
7
|
|
|
|
|
19
|
my ($key) = @_; |
911
|
|
|
|
|
|
|
|
912
|
7
|
100
|
|
|
|
28
|
if (ref(\$key) eq 'SCALAR') { |
913
|
6
|
|
|
|
|
33
|
my ($cache_hit, $value) = $self->check({ -cache_key => $key }); |
914
|
6
|
|
|
|
|
44
|
return $value; |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
} else { |
917
|
1
|
|
|
|
|
22
|
my ($cache_hit,$value) = $self->check({ -key => $key }); |
918
|
1
|
|
|
|
|
10
|
return $value; |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
} |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
####################################################################### |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
sub DELETE { |
926
|
2
|
|
|
2
|
|
26
|
my $self = shift; |
927
|
|
|
|
|
|
|
|
928
|
2
|
|
|
|
|
5
|
my ($key) = @_; |
929
|
|
|
|
|
|
|
|
930
|
2
|
100
|
|
|
|
10
|
if (ref(\$key) eq 'SCALAR') { |
931
|
1
|
|
|
|
|
9
|
$self->delete({ -cache_key => $key }); |
932
|
|
|
|
|
|
|
} else { |
933
|
1
|
|
|
|
|
7
|
$self->delete({ -key => $key }); |
934
|
|
|
|
|
|
|
} |
935
|
|
|
|
|
|
|
} |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
####################################################################### |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
sub CLEAR { |
940
|
1
|
|
|
1
|
|
9
|
my $self = shift; |
941
|
|
|
|
|
|
|
|
942
|
1
|
|
|
|
|
6
|
$self->clear; |
943
|
|
|
|
|
|
|
} |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
####################################################################### |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
sub EXISTS { |
948
|
18
|
|
|
18
|
|
207
|
my $self = shift; |
949
|
|
|
|
|
|
|
|
950
|
18
|
|
|
|
|
39
|
my ($key) = @_; |
951
|
|
|
|
|
|
|
|
952
|
18
|
100
|
|
|
|
79
|
if (ref(\$key) eq 'SCALAR') { |
953
|
16
|
|
|
|
|
92
|
my ($cache_hit,$value) = $self->check({ -cache_key => $key }); |
954
|
16
|
|
|
|
|
92
|
return $cache_hit; |
955
|
|
|
|
|
|
|
} else { |
956
|
2
|
|
|
|
|
12
|
my ($cache_hit,$value) = $self->check({ -key => $key }); |
957
|
2
|
|
|
|
|
11
|
return $cache_hit; |
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
####################################################################### |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
# |
964
|
|
|
|
|
|
|
# Iteration over the cache is not supported |
965
|
|
|
|
|
|
|
# |
966
|
|
|
|
|
|
|
|
967
|
1
|
|
|
1
|
|
20
|
sub FIRSTKEY { undef; } |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
####################################################################### |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
# |
972
|
|
|
|
|
|
|
# Iteration over the cache is not supported |
973
|
|
|
|
|
|
|
# |
974
|
|
|
|
|
|
|
|
975
|
0
|
|
|
0
|
|
|
sub NEXTKEY { undef; } |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
####################################################################### |
978
|
|
|
|
|
|
|
# |
979
|
|
|
|
|
|
|
# We return the number of cache entries in a scalar context |
980
|
|
|
|
|
|
|
# |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
sub SCALAR { |
983
|
0
|
|
|
0
|
|
|
my $self = shift; |
984
|
|
|
|
|
|
|
|
985
|
0
|
|
|
|
|
|
return $self->number_of_entries; |
986
|
|
|
|
|
|
|
} |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
####################################################################### |
989
|
|
|
|
|
|
|
####################################################################### |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=head1 COPYRIGHT |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
Copyright 1999, Benjamin Franz () and |
994
|
|
|
|
|
|
|
FreeRun Technologies, Inc. (). All Rights Reserved. |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
=head1 VERSION |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
1.05 released 2005.09.14 |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=head1 LICENSE |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
This software may be copied or redistributed under the same terms as Perl itelf. |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
This means that you can, at your option, redistribute it and/or modify it under |
1005
|
|
|
|
|
|
|
either the terms the GNU Public License (GPL) version 1 or later, or under the |
1006
|
|
|
|
|
|
|
Perl Artistic License. |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=head1 DISCLAIMER |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS |
1013
|
|
|
|
|
|
|
OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE |
1014
|
|
|
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
1015
|
|
|
|
|
|
|
PARTICULAR PURPOSE. |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
Use of this software in any way or in any form, source or binary, |
1018
|
|
|
|
|
|
|
is not allowed in any country which prohibits disclaimers of any |
1019
|
|
|
|
|
|
|
implied warranties of merchantability or fitness for a particular |
1020
|
|
|
|
|
|
|
purpose or any disclaimers of a similar nature. |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
IN NO EVENT SHALL I BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, |
1023
|
|
|
|
|
|
|
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
1024
|
|
|
|
|
|
|
USE OF THIS SOFTWARE AND ITS DOCUMENTATION (INCLUDING, BUT NOT |
1025
|
|
|
|
|
|
|
LIMITED TO, LOST PROFITS) EVEN IF I HAVE BEEN ADVISED OF THE |
1026
|
|
|
|
|
|
|
POSSIBILITY OF SUCH DAMAGE |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=head1 AUTHOR |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
Benjamin Franz |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
=head1 TODO |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
Nothing. |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
=cut |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
1; |