File Coverage

blib/lib/DBM/Deep/Storage/File.pm
Criterion Covered Total %
statement 139 144 96.5
branch 37 48 77.0
condition 12 15 80.0
subroutine 22 23 95.6
pod 14 14 100.0
total 224 244 91.8


line stmt bran cond sub pod time code
1             package DBM::Deep::Storage::File;
2              
3 50     50   928 use 5.008_004;
  50         178  
4              
5 50     50   308 use strict;
  50         106  
  50         1432  
6 50     50   282 use warnings FATAL => 'all';
  50         105  
  50         2049  
7              
8 50     50   346 use Fcntl qw( :DEFAULT :flock :seek );
  50         97  
  50         25181  
9              
10 50     50   418 use constant DEBUG => 0;
  50         111  
  50         3734  
11              
12 50     50   334 use base 'DBM::Deep::Storage';
  50         130  
  50         23184  
13              
14             =head1 NAME
15              
16             DBM::Deep::Storage::File - mediate low-level interaction with storage mechanism
17              
18             =head1 PURPOSE
19              
20             This is an internal-use-only object for L. It mediates the low-level
21             interaction with the storage mechanism.
22              
23             Currently, the only storage mechanism supported is the file system.
24              
25             =head1 OVERVIEW
26              
27             This class provides an abstraction to the storage mechanism so that the Engine
28             (the only class that uses this class) doesn't have to worry about that.
29              
30             =head1 METHODS
31              
32             =head2 new( \%args )
33              
34             =cut
35              
36             sub new {
37 388     388 1 689 my $class = shift;
38 388         662 my ($args) = @_;
39              
40 388         2702 my $self = bless {
41             autobless => 1,
42             autoflush => 1,
43             end => 0,
44             fh => undef,
45             file => undef,
46             file_offset => 0,
47             locking => 1,
48             locked => 0,
49             #XXX Migrate this to the engine, where it really belongs.
50             filter_store_key => undef,
51             filter_store_value => undef,
52             filter_fetch_key => undef,
53             filter_fetch_value => undef,
54             }, $class;
55              
56             # Grab the parameters we want to use
57 388         1832 foreach my $param ( keys %$self ) {
58 4656 100       8168 next unless exists $args->{$param};
59 453         1032 $self->{$param} = $args->{$param};
60             }
61              
62 388 100 100     1334 if ( $self->{fh} && !$self->{file_offset} ) {
63 4         21 $self->{file_offset} = tell( $self->{fh} );
64             }
65              
66 388 100       1382 $self->open unless $self->{fh};
67              
68 387         1322 return $self;
69             }
70              
71             =head2 open()
72              
73             This method opens the filehandle for the filename in C< file >.
74              
75             There is no return value.
76              
77             =cut
78              
79             # TODO: What happens if we ->open when we already have a $fh?
80             sub open {
81 398     398 1 634 my $self = shift;
82              
83             # Adding O_BINARY should remove the need for the binmode below. However,
84             # I'm not going to remove it because I don't have the Win32 chops to be
85             # absolutely certain everything will be ok.
86 398         580 my $flags = O_CREAT | O_BINARY;
87              
88 398 50 66     9205 if ( !-e $self->{file} || -w _ ) {
89 398         1141 $flags |= O_RDWR;
90             }
91             else {
92 0         0 $flags |= O_RDONLY;
93             }
94              
95 398         596 my $fh;
96 398 100       15776 sysopen( $fh, $self->{file}, $flags )
97             or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
98 397         1650 $self->{fh} = $fh;
99              
100             # Even though we use O_BINARY, better be safe than sorry.
101 397         1094 binmode $fh;
102              
103 397 50       1075 if ($self->{autoflush}) {
104 397         1525 my $old = select $fh;
105 397         1174 $|=1;
106 397         1049 select $old;
107             }
108              
109 397         871 return 1;
110             }
111              
112             =head2 close()
113              
114             If the filehandle is opened, this will close it.
115              
116             There is no return value.
117              
118             =cut
119              
120             sub close {
121 415     415 1 637 my $self = shift;
122              
123 415 100       1118 if ( $self->{fh} ) {
124 393         4889 close $self->{fh};
125 393         2030 $self->{fh} = undef;
126             }
127              
128 415         856 return 1;
129             }
130              
131             =head2 size()
132              
133             This will return the size of the DB. If file_offset is set, this will take that into account.
134              
135             B: This function isn't used internally anywhere.
136              
137             =cut
138              
139             sub size {
140 0     0 1 0 my $self = shift;
141              
142 0 0       0 return 0 unless $self->{fh};
143 0         0 return( (-s $self->{fh}) - $self->{file_offset} );
144             }
145              
146             =head2 set_inode()
147              
148             This will set the inode value of the underlying file object.
149              
150             This is only needed to handle some obscure Win32 bugs. It really shouldn't be
151             needed outside this object.
152              
153             There is no return value.
154              
155             =cut
156              
157             sub set_inode {
158 2931     2931 1 4248 my $self = shift;
159              
160 2931 100       6305 unless ( defined $self->{inode} ) {
161 376         4212 my @stats = stat($self->{fh});
162 376         1216 $self->{inode} = $stats[1];
163 376         917 $self->{end} = $stats[7];
164             }
165              
166 2931         5189 return 1;
167             }
168              
169             =head2 print_at( $offset, @data )
170              
171             This takes an optional offset and some data to print.
172              
173             C< $offset >, if defined, will be used to seek into the file. If file_offset is
174             set, it will be used as the zero location. If it is undefined, no seeking will
175             occur. Then, C< @data > will be printed to the current location.
176              
177             There is no return value.
178              
179             =cut
180              
181             sub print_at {
182 25972     25972 1 37726 my $self = shift;
183 25972         33304 my $loc = shift;
184              
185 25972         69591 local ($,,$\);
186              
187 25972         41677 my $fh = $self->{fh};
188 25972 50       49438 if ( defined $loc ) {
189 25972         238579 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
190             }
191              
192 25972         48752 if ( DEBUG ) {
193             my $caller = join ':', (caller)[0,2];
194             my $len = length( join '', @_ );
195             warn "($caller) print_at( " . (defined $loc ? $loc : '') . ", $len )\n";
196             }
197              
198 25972 50       316214 print( $fh @_ ) or die "Internal Error (print_at($loc)): $!\n";
199              
200 25972         137185 return 1;
201             }
202              
203             =head2 read_at( $offset, $length )
204              
205             This takes an optional offset and a length.
206              
207             C< $offset >, if defined, will be used to seek into the file. If file_offset is
208             set, it will be used as the zero location. If it is undefined, no seeking will
209             occur. Then, C< $length > bytes will be read from the current location.
210              
211             The data read will be returned.
212              
213             =cut
214              
215             sub read_at {
216 110383     110383 1 160361 my $self = shift;
217 110383         183612 my ($loc, $size) = @_;
218              
219 110383         162591 my $fh = $self->{fh};
220 110383 100       207126 if ( defined $loc ) {
221 109864         1230431 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
222             }
223              
224 110383         216262 if ( DEBUG ) {
225             my $caller = join ':', (caller)[0,2];
226             warn "($caller) read_at( " . (defined $loc ? $loc : '') . ", $size )\n";
227             }
228              
229 110383         155009 my $buffer;
230 110383         999146 read( $fh, $buffer, $size);
231              
232 110383         590824 return $buffer;
233             }
234              
235             =head2 DESTROY
236              
237             When the ::Storage::File object goes out of scope, it will be closed.
238              
239             =cut
240              
241             sub DESTROY {
242 380     380   685 my $self = shift;
243 380 50       905 return unless $self;
244              
245 380         1036 $self->close;
246              
247 380         4653 return;
248             }
249              
250             =head2 request_space( $size )
251              
252             This takes a size and adds that much space to the DBM.
253              
254             This returns the offset for the new location.
255              
256             =cut
257              
258             sub request_space {
259 5473     5473 1 8331 my $self = shift;
260 5473         10292 my ($size) = @_;
261              
262             #XXX Do I need to reset $self->{end} here? I need a testcase
263 5473         9299 my $loc = $self->{end};
264 5473         8495 $self->{end} += $size;
265              
266 5473         9920 return $loc;
267             }
268              
269             =head2 copy_stats( $target_filename )
270              
271             This will take the stats for the current filehandle and apply them to
272             C< $target_filename >. The stats copied are:
273              
274             =over 4
275              
276             =item * Onwer UID and GID
277              
278             =item * Permissions
279              
280             =back
281              
282             =cut
283              
284             sub copy_stats {
285 2     2 1 4 my $self = shift;
286 2         5 my ($temp_filename) = @_;
287              
288 2         33 my @stats = stat( $self->{fh} );
289 2         8 my $perms = $stats[2] & 07777;
290 2         4 my $uid = $stats[4];
291 2         4 my $gid = $stats[5];
292 2         78 chown( $uid, $gid, $temp_filename );
293 2         37 chmod( $perms, $temp_filename );
294             }
295              
296             sub flush {
297 5595     5595 1 8075 my $self = shift;
298              
299             # Flush the filehandle
300 5595         19936 my $old_fh = select $self->{fh};
301 5595         15905 my $old_af = $|; $| = 1; $| = $old_af;
  5595         9703  
  5595         8995  
302 5595         14557 select $old_fh;
303              
304 5595         11780 return 1;
305             }
306              
307             sub is_writable {
308 2073     2073 1 3265 my $self = shift;
309              
310 2073         3222 my $fh = $self->{fh};
311 2073 50       4277 return unless defined $fh;
312 2073 50       5023 return unless defined fileno $fh;
313 2073         7203 local $\ = ''; # just in case
314 50     50   442 no warnings; # temporarily disable warnings
  50         146  
  50         23831  
315 2073         5587 local $^W; # temporarily disable warnings
316 2073         13359 return print $fh '';
317             }
318              
319             sub lock_exclusive {
320 5813     5813 1 8337 my $self = shift;
321 5813         8864 my ($obj) = @_;
322 5813         11798 return $self->_lock( $obj, LOCK_EX );
323             }
324              
325             sub lock_shared {
326 4616     4616 1 6619 my $self = shift;
327 4616         6901 my ($obj) = @_;
328 4616         9153 return $self->_lock( $obj, LOCK_SH );
329             }
330              
331             sub _lock {
332 10429     10429   14661 my $self = shift;
333 10429         17935 my ($obj, $type) = @_;
334              
335 10429 50       20626 $type = LOCK_EX unless defined $type;
336              
337             #XXX This is a temporary fix for Win32 and autovivification. It
338             # needs to improve somehow. -RobK, 2008-03-09
339 10429 50 33     44086 if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
340 0         0 $type = LOCK_EX;
341             }
342              
343 10429 100       24195 if (!defined($self->{fh})) { return; }
  1         7  
344              
345             #XXX This either needs to allow for upgrading a shared lock to an
346             # exclusive lock or something else with autovivification.
347             # -RobK, 2008-03-09
348 10428 100       21286 if ($self->{locking}) {
349 10425 100       20828 if (!$self->{locked}) {
350 5508         47326 flock($self->{fh}, $type);
351              
352             # refresh end counter in case file has changed size
353 5508         66825 my @stats = stat($self->{fh});
354 5508         15501 $self->{end} = $stats[7];
355              
356             # double-check file inode, in case another process
357             # has optimize()d our file while we were waiting.
358 5508 100 100     27150 if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
359 12         42 $self->close;
360 12         38 $self->open;
361              
362             #XXX This needs work
363 12         61 $obj->{engine}->setup( $obj );
364              
365 12         110 flock($self->{fh}, $type); # re-lock
366              
367             # This may not be necessary after re-opening
368 12         138 $self->{end} = (stat($self->{fh}))[7]; # re-end
369             }
370             }
371 10425         16235 $self->{locked}++;
372              
373 10425         30562 return 1;
374             }
375              
376 3         64 return;
377             }
378              
379             sub unlock {
380 10430     10430 1 14557 my $self = shift;
381              
382 10430 100       22128 if (!defined($self->{fh})) { return; }
  6         12  
383              
384 10424 100 100     36822 if ($self->{locking} && $self->{locked} > 0) {
385 10418         15267 $self->{locked}--;
386              
387 10418 100       19660 if (!$self->{locked}) {
388 5501         57337 flock($self->{fh}, LOCK_UN);
389 5501         23333 return 1;
390             }
391              
392 4917         10324 return;
393             }
394              
395 6         17 return;
396             }
397              
398             1;
399             __END__