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   882 use 5.008_004;
  50         178  
4              
5 50     50   276 use strict;
  50         104  
  50         1442  
6 50     50   318 use warnings FATAL => 'all';
  50         126  
  50         2180  
7              
8 50     50   336 use Fcntl qw( :DEFAULT :flock :seek );
  50         115  
  50         23904  
9              
10 50     50   412 use constant DEBUG => 0;
  50         118  
  50         3682  
11              
12 50     50   342 use base 'DBM::Deep::Storage';
  50         104  
  50         24230  
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 793 my $class = shift;
38 388         731 my ($args) = @_;
39              
40 388         3053 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         2001 foreach my $param ( keys %$self ) {
58 4656 100       8958 next unless exists $args->{$param};
59 453         986 $self->{$param} = $args->{$param};
60             }
61              
62 388 100 100     1388 if ( $self->{fh} && !$self->{file_offset} ) {
63 4         16 $self->{file_offset} = tell( $self->{fh} );
64             }
65              
66 388 100       1567 $self->open unless $self->{fh};
67              
68 387         1504 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 685 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         642 my $flags = O_CREAT | O_BINARY;
87              
88 398 50 66     10666 if ( !-e $self->{file} || -w _ ) {
89 398         1213 $flags |= O_RDWR;
90             }
91             else {
92 0         0 $flags |= O_RDONLY;
93             }
94              
95 398         659 my $fh;
96 398 100       17130 sysopen( $fh, $self->{file}, $flags )
97             or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
98 397         1806 $self->{fh} = $fh;
99              
100             # Even though we use O_BINARY, better be safe than sorry.
101 397         1199 binmode $fh;
102              
103 397 50       1158 if ($self->{autoflush}) {
104 397         1657 my $old = select $fh;
105 397         1218 $|=1;
106 397         1336 select $old;
107             }
108              
109 397         946 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 692 my $self = shift;
122              
123 415 100       1108 if ( $self->{fh} ) {
124 393         4512 close $self->{fh};
125 393         1988 $self->{fh} = undef;
126             }
127              
128 415         931 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 4453 my $self = shift;
159              
160 2931 100       6729 unless ( defined $self->{inode} ) {
161 376         5019 my @stats = stat($self->{fh});
162 376         1409 $self->{inode} = $stats[1];
163 376         1030 $self->{end} = $stats[7];
164             }
165              
166 2931         5616 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 25966     25966 1 40659 my $self = shift;
183 25966         35642 my $loc = shift;
184              
185 25966         69516 local ($,,$\);
186              
187 25966         41742 my $fh = $self->{fh};
188 25966 50       51751 if ( defined $loc ) {
189 25966         251072 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
190             }
191              
192 25966         51998 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 25966 50       299960 print( $fh @_ ) or die "Internal Error (print_at($loc)): $!\n";
199              
200 25966         138617 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 110382     110382 1 178763 my $self = shift;
217 110382         198374 my ($loc, $size) = @_;
218              
219 110382         172735 my $fh = $self->{fh};
220 110382 100       212243 if ( defined $loc ) {
221 109863         1324036 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
222             }
223              
224 110382         234141 if ( DEBUG ) {
225             my $caller = join ':', (caller)[0,2];
226             warn "($caller) read_at( " . (defined $loc ? $loc : '') . ", $size )\n";
227             }
228              
229 110382         175489 my $buffer;
230 110382         948538 read( $fh, $buffer, $size);
231              
232 110382         583812 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   699 my $self = shift;
243 380 50       853 return unless $self;
244              
245 380         1084 $self->close;
246              
247 380         4105 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 5469     5469 1 8654 my $self = shift;
260 5469         10153 my ($size) = @_;
261              
262             #XXX Do I need to reset $self->{end} here? I need a testcase
263 5469         9216 my $loc = $self->{end};
264 5469         8131 $self->{end} += $size;
265              
266 5469         10685 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         7 my ($temp_filename) = @_;
287              
288 2         41 my @stats = stat( $self->{fh} );
289 2         12 my $perms = $stats[2] & 07777;
290 2         3 my $uid = $stats[4];
291 2         4 my $gid = $stats[5];
292 2         84 chown( $uid, $gid, $temp_filename );
293 2         39 chmod( $perms, $temp_filename );
294             }
295              
296             sub flush {
297 5595     5595 1 8937 my $self = shift;
298              
299             # Flush the filehandle
300 5595         21304 my $old_fh = select $self->{fh};
301 5595         16393 my $old_af = $|; $| = 1; $| = $old_af;
  5595         12948  
  5595         9343  
302 5595         16527 select $old_fh;
303              
304 5595         13209 return 1;
305             }
306              
307             sub is_writable {
308 2073     2073 1 3348 my $self = shift;
309              
310 2073         3441 my $fh = $self->{fh};
311 2073 50       4468 return unless defined $fh;
312 2073 50       5671 return unless defined fileno $fh;
313 2073         7660 local $\ = ''; # just in case
314 50     50   458 no warnings; # temporarily disable warnings
  50         103  
  50         23410  
315 2073         5796 local $^W; # temporarily disable warnings
316 2073         14120 return print $fh '';
317             }
318              
319             sub lock_exclusive {
320 5813     5813 1 8880 my $self = shift;
321 5813         9120 my ($obj) = @_;
322 5813         12362 return $self->_lock( $obj, LOCK_EX );
323             }
324              
325             sub lock_shared {
326 4616     4616 1 6877 my $self = shift;
327 4616         7690 my ($obj) = @_;
328 4616         9791 return $self->_lock( $obj, LOCK_SH );
329             }
330              
331             sub _lock {
332 10429     10429   16300 my $self = shift;
333 10429         18038 my ($obj, $type) = @_;
334              
335 10429 50       24176 $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     49062 if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
340 0         0 $type = LOCK_EX;
341             }
342              
343 10429 100       24330 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       21969 if ($self->{locking}) {
349 10425 100       21572 if (!$self->{locked}) {
350 5508         62549 flock($self->{fh}, $type);
351              
352             # refresh end counter in case file has changed size
353 5508         75488 my @stats = stat($self->{fh});
354 5508         17005 $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     30353 if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
359 12         48 $self->close;
360 12         45 $self->open;
361              
362             #XXX This needs work
363 12         66 $obj->{engine}->setup( $obj );
364              
365 12         139 flock($self->{fh}, $type); # re-lock
366              
367             # This may not be necessary after re-opening
368 12         160 $self->{end} = (stat($self->{fh}))[7]; # re-end
369             }
370             }
371 10425         18067 $self->{locked}++;
372              
373 10425         32143 return 1;
374             }
375              
376 3         9 return;
377             }
378              
379             sub unlock {
380 10430     10430 1 15814 my $self = shift;
381              
382 10430 100       23761 if (!defined($self->{fh})) { return; }
  6         14  
383              
384 10424 100 100     39201 if ($self->{locking} && $self->{locked} > 0) {
385 10418         16389 $self->{locked}--;
386              
387 10418 100       20593 if (!$self->{locked}) {
388 5501         74750 flock($self->{fh}, LOCK_UN);
389 5501         25313 return 1;
390             }
391              
392 4917         11189 return;
393             }
394              
395 6         15 return;
396             }
397              
398             1;
399             __END__