1 package DBM::Deep::File;
8 our $VERSION = q(0.99_03);
10 use Fcntl qw( :DEFAULT :flock :seek );
27 filter_store_key => undef,
28 filter_store_value => undef,
29 filter_fetch_key => undef,
30 filter_fetch_value => undef,
32 # These are values that are not expected to be passed in through
33 # $args. They are here for documentation purposes.
35 transaction_offset => 0,
36 transaction_audit => undef,
40 # Grab the parameters we want to use
41 foreach my $param ( keys %$self ) {
42 next unless exists $args->{$param};
43 $self->{$param} = $args->{$param};
46 if ( $self->{fh} && !$self->{file_offset} ) {
47 $self->{file_offset} = tell( $self->{fh} );
50 $self->open unless $self->{fh};
52 if ( $self->{audit_file} && !$self->{audit_fh} ) {
53 my $flags = O_WRONLY | O_APPEND | O_CREAT;
56 sysopen( $fh, $self->{audit_file}, $flags )
57 or die "Cannot open audit file '$self->{audit_file}' for read/write: $!";
59 # Set the audit_fh to autoflush
64 $self->{audit_fh} = $fh;
74 unless ( $self->{base_db_obj} ) {
75 $self->{base_db_obj} = shift;
76 Scalar::Util::weaken( $self->{base_db_obj} );
85 # Adding O_BINARY should remove the need for the binmode below. However,
86 # I'm not going to remove it because I don't have the Win32 chops to be
87 # absolutely certain everything will be ok.
88 my $flags = O_RDWR | O_CREAT | O_BINARY;
91 sysopen( $fh, $self->{file}, $flags )
92 or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
95 # Even though we use O_BINARY, better be safe than sorry.
98 if ($self->{autoflush}) {
121 unless ( defined $self->{inode} ) {
122 my @stats = stat($self->{fh});
123 $self->{inode} = $stats[1];
124 $self->{end} = $stats[7];
136 my $fh = $self->{fh};
137 if ( defined $loc ) {
138 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
148 my ($loc, $size) = @_;
149 print join(":",map{$_||''}caller) . " - read_at(@{[$loc || 'undef']}, $size)\n" if $::DEBUG;
153 my $fh = $self->{fh};
154 if ( defined $loc ) {
155 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
159 read( $fh, $buffer, $size);
164 sub increment_pointer {
168 if ( defined $size ) {
169 seek( $self->{fh}, $size, SEEK_CUR );
188 #XXX Do I need to reset $self->{end} here? I need a testcase
189 my $loc = $self->{end};
190 $self->{end} += $size;
196 # If db locking is set, flock() the db file. If called multiple
197 # times before unlock(), then the same number of unlocks() must
198 # be called before the lock is released.
202 my ($obj, $type) = @_;
204 #XXX This may not always be the correct thing to do
205 $obj = $self->{base_db_obj} unless defined $obj;
207 $type = LOCK_EX unless defined $type;
209 if (!defined($self->{fh})) { return; }
211 if ($self->{locking}) {
212 if (!$self->{locked}) {
213 flock($self->{fh}, $type);
215 # refresh end counter in case file has changed size
216 my @stats = stat($self->{fh});
217 $self->{end} = $stats[7];
219 # double-check file inode, in case another process
220 # has optimize()d our file while we were waiting.
221 if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
226 $obj->{engine}->setup_fh( $obj );
228 flock($self->{fh}, $type); # re-lock
230 # This may not be necessary after re-opening
231 $self->{end} = (stat($self->{fh}))[7]; # re-end
243 # If db locking is set, unlock the db file. See note in lock()
244 # regarding calling lock() multiple times.
249 if (!defined($self->{fh})) { return; }
251 if ($self->{locking} && $self->{locked} > 0) {
253 if (!$self->{locked}) { flock($self->{fh}, LOCK_UN); }
264 # Flush the filehandle
265 my $old_fh = select $self->{fh};
266 my $old_af = $|; $| = 1; $| = $old_af;
272 sub set_transaction_offset {
274 $self->{transaction_offset} = shift;
281 if ( my $afh = $self->{audit_fh} ) {
282 flock( $afh, LOCK_EX );
284 if ( $string =~ /^#/ ) {
285 print( $afh "$string " . localtime(time) . "\n" );
288 print( $afh "$string # " . localtime(time) . "\n" );
291 flock( $afh, LOCK_UN );
294 if ( $self->{transaction_audit} ) {
295 push @{$self->{transaction_audit}}, $string;
301 sub begin_transaction {
304 my $fh = $self->{fh};
308 my $buffer = $self->read_at( $self->{transaction_offset}, 4 );
309 my ($next, @trans) = unpack( 'C C C C C C C C C C C C C C C C', $buffer );
311 $self->{transaction_id} = ++$next;
313 die if $trans[-1] != 0;
315 for ( my $i = 0; $i <= $#trans; $i++ ) {
316 next if $trans[$i] != 0;
322 $self->{transaction_offset},
323 pack( 'C C C C C C C C C C C C C C C C', $next, @trans),
328 $self->{transaction_audit} = [];
330 return $self->{transaction_id};
333 sub end_transaction {
336 my $fh = $self->{fh};
340 my $buffer = $self->read_at( $self->{transaction_offset}, 4 );
341 my ($next, @trans) = unpack( 'C C C C C C C C C C C C C C C C', $buffer );
343 @trans = grep { $_ != $self->{transaction_id} } @trans;
346 $self->{transaction_offset},
347 pack( 'C C C C C C C C C C C C C C C C', $next, @trans),
350 #XXX Need to free the space used by the current transaction
354 $self->{transaction_id} = 0;
355 $self->{transaction_audit} = undef;
357 # $self->{base_db_obj}->optimize;
358 # $self->{inode} = undef;
364 sub current_transactions {
367 my $fh = $self->{fh};
371 my $buffer = $self->read_at( $self->{transaction_offset}, 4 );
372 my ($next, @trans) = unpack( 'C C C C C C C C C C C C C C C C', $buffer );
376 return grep { $_ && $_ != $self->{transaction_id} } @trans;
379 sub transaction_id { return $_[0]->{transaction_id} }
381 sub commit_transaction {
384 my @audit = @{$self->{transaction_audit}};
386 $self->end_transaction;
389 my $db = $self->{base_db_obj};
392 warn "$_: $@\n" if $@;