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   916 use 5.008_004;
  50         192  
4              
5 50     50   291 use strict;
  50         139  
  50         1348  
6 50     50   279 use warnings FATAL => 'all';
  50         116  
  50         2161  
7              
8 50     50   344 use Fcntl qw( :DEFAULT :flock :seek );
  50         99  
  50         24942  
9              
10 50     50   429 use constant DEBUG => 0;
  50         119  
  50         4258  
11              
12 50     50   377 use base 'DBM::Deep::Storage';
  50         129  
  50         23576  
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 775 my $class = shift;
38 388         660 my ($args) = @_;
39              
40 388         2650 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         1901 foreach my $param ( keys %$self ) {
58 4656 100       8380 next unless exists $args->{$param};
59 453         979 $self->{$param} = $args->{$param};
60             }
61              
62 388 100 100     1398 if ( $self->{fh} && !$self->{file_offset} ) {
63 4         18 $self->{file_offset} = tell( $self->{fh} );
64             }
65              
66 388 100       1371 $self->open unless $self->{fh};
67              
68 387         1353 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 623 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         631 my $flags = O_CREAT | O_BINARY;
87              
88 398 50 66     8825 if ( !-e $self->{file} || -w _ ) {
89 398         1136 $flags |= O_RDWR;
90             }
91             else {
92 0         0 $flags |= O_RDONLY;
93             }
94              
95 398         608 my $fh;
96 398 100       15192 sysopen( $fh, $self->{file}, $flags )
97             or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
98 397         1698 $self->{fh} = $fh;
99              
100             # Even though we use O_BINARY, better be safe than sorry.
101 397         1105 binmode $fh;
102              
103 397 50       1077 if ($self->{autoflush}) {
104 397         1533 my $old = select $fh;
105 397         1291 $|=1;
106 397         1133 select $old;
107             }
108              
109 397         833 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 646 my $self = shift;
122              
123 415 100       1176 if ( $self->{fh} ) {
124 393         4848 close $self->{fh};
125 393         1964 $self->{fh} = undef;
126             }
127              
128 415         926 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 4433 my $self = shift;
159              
160 2931 100       6743 unless ( defined $self->{inode} ) {
161 376         4126 my @stats = stat($self->{fh});
162 376         1176 $self->{inode} = $stats[1];
163 376         852 $self->{end} = $stats[7];
164             }
165              
166 2931         5273 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 25969     25969 1 39421 my $self = shift;
183 25969         33590 my $loc = shift;
184              
185 25969         68573 local ($,,$\);
186              
187 25969         40008 my $fh = $self->{fh};
188 25969 50       51215 if ( defined $loc ) {
189 25969         239908 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
190             }
191              
192 25969         49506 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 25969 50       313059 print( $fh @_ ) or die "Internal Error (print_at($loc)): $!\n";
199              
200 25969         136253 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 161930 my $self = shift;
217 110383         193286 my ($loc, $size) = @_;
218              
219 110383         162313 my $fh = $self->{fh};
220 110383 100       216228 if ( defined $loc ) {
221 109864         1233103 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
222             }
223              
224 110383         215884 if ( DEBUG ) {
225             my $caller = join ':', (caller)[0,2];
226             warn "($caller) read_at( " . (defined $loc ? $loc : '') . ", $size )\n";
227             }
228              
229 110383         155612 my $buffer;
230 110383         991493 read( $fh, $buffer, $size);
231              
232 110383         582011 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   784 my $self = shift;
243 380 50       870 return unless $self;
244              
245 380         1023 $self->close;
246              
247 380         4677 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 5471     5471 1 8631 my $self = shift;
260 5471         9745 my ($size) = @_;
261              
262             #XXX Do I need to reset $self->{end} here? I need a testcase
263 5471         8855 my $loc = $self->{end};
264 5471         7858 $self->{end} += $size;
265              
266 5471         10487 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 7 my $self = shift;
286 2         5 my ($temp_filename) = @_;
287              
288 2         29 my @stats = stat( $self->{fh} );
289 2         14 my $perms = $stats[2] & 07777;
290 2         6 my $uid = $stats[4];
291 2         4 my $gid = $stats[5];
292 2         88 chown( $uid, $gid, $temp_filename );
293 2         38 chmod( $perms, $temp_filename );
294             }
295              
296             sub flush {
297 5595     5595 1 8371 my $self = shift;
298              
299             # Flush the filehandle
300 5595         18947 my $old_fh = select $self->{fh};
301 5595         14993 my $old_af = $|; $| = 1; $| = $old_af;
  5595         9702  
  5595         8361  
302 5595         15356 select $old_fh;
303              
304 5595         12165 return 1;
305             }
306              
307             sub is_writable {
308 2073     2073 1 3127 my $self = shift;
309              
310 2073         3757 my $fh = $self->{fh};
311 2073 50       4290 return unless defined $fh;
312 2073 50       5252 return unless defined fileno $fh;
313 2073         6973 local $\ = ''; # just in case
314 50     50   460 no warnings; # temporarily disable warnings
  50         183  
  50         23982  
315 2073         5787 local $^W; # temporarily disable warnings
316 2073         13212 return print $fh '';
317             }
318              
319             sub lock_exclusive {
320 5813     5813 1 8818 my $self = shift;
321 5813         8976 my ($obj) = @_;
322 5813         12076 return $self->_lock( $obj, LOCK_EX );
323             }
324              
325             sub lock_shared {
326 4616     4616 1 6716 my $self = shift;
327 4616         7147 my ($obj) = @_;
328 4616         9524 return $self->_lock( $obj, LOCK_SH );
329             }
330              
331             sub _lock {
332 10429     10429   14812 my $self = shift;
333 10429         16739 my ($obj, $type) = @_;
334              
335 10429 50       21446 $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     44382 if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
340 0         0 $type = LOCK_EX;
341             }
342              
343 10429 100       24730 if (!defined($self->{fh})) { return; }
  1         6  
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       25356 if ($self->{locking}) {
349 10425 100       20959 if (!$self->{locked}) {
350 5508         48368 flock($self->{fh}, $type);
351              
352             # refresh end counter in case file has changed size
353 5508         65845 my @stats = stat($self->{fh});
354 5508         15243 $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     27368 if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
359 12         42 $self->close;
360 12         42 $self->open;
361              
362             #XXX This needs work
363 12         61 $obj->{engine}->setup( $obj );
364              
365 12         130 flock($self->{fh}, $type); # re-lock
366              
367             # This may not be necessary after re-opening
368 12         132 $self->{end} = (stat($self->{fh}))[7]; # re-end
369             }
370             }
371 10425         16108 $self->{locked}++;
372              
373 10425         31261 return 1;
374             }
375              
376 3         8 return;
377             }
378              
379             sub unlock {
380 10430     10430 1 14864 my $self = shift;
381              
382 10430 100       22218 if (!defined($self->{fh})) { return; }
  6         14  
383              
384 10424 100 100     37713 if ($self->{locking} && $self->{locked} > 0) {
385 10418         15918 $self->{locked}--;
386              
387 10418 100       19969 if (!$self->{locked}) {
388 5501         57334 flock($self->{fh}, LOCK_UN);
389 5501         23573 return 1;
390             }
391              
392 4917         10388 return;
393             }
394              
395 6         15 return;
396             }
397              
398             1;
399             __END__