Moved almost all direct accesses to into ::File
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / File.pm
CommitLineData
460b1067 1package DBM::Deep::File;
2
3use 5.6.0;
4
5use strict;
6use warnings;
7
8use Fcntl qw( :DEFAULT :flock :seek );
9
10our $VERSION = '0.01';
11
12sub new {
13 my $class = shift;
14 my ($args) = @_;
15
16 my $self = bless {
359a01ac 17 audit_fh => undef,
18 audit_file => undef,
19 autobless => 1,
460b1067 20 autoflush => undef,
21 end => 0,
22 fh => undef,
23 file => undef,
24 file_offset => 0,
25 locking => undef,
26 locked => 0,
27 filter_store_key => undef,
28 filter_store_value => undef,
29 filter_fetch_key => undef,
30 filter_fetch_value => undef,
28394a1a 31
359a01ac 32 # These are values that are not expected to be passed in through
33 # $args. They are here for documentation purposes.
34 transaction_id => 0,
35 transaction_offset => 0,
36 base_db_obj => undef,
460b1067 37 }, $class;
38
39 # Grab the parameters we want to use
40 foreach my $param ( keys %$self ) {
41 next unless exists $args->{$param};
42 $self->{$param} = $args->{$param};
43 }
44
45 if ( $self->{fh} && !$self->{file_offset} ) {
46 $self->{file_offset} = tell( $self->{fh} );
47 }
48
49 $self->open unless $self->{fh};
50
359a01ac 51 if ( $self->{audit_file} && !$self->{audit_fh} ) {
52 my $flags = O_WRONLY | O_APPEND | O_CREAT;
53
54 my $fh;
55 sysopen( $fh, $self->{audit_file}, $flags )
56 or die "Cannot open audit file '$self->{audit_file}' for read/write: $!";
57
58 # Set the audit_fh to autoflush
59 my $old = select $fh;
60 $|=1;
61 select $old;
62
63 $self->{audit_fh} = $fh;
64 }
65
66
460b1067 67 return $self;
68}
69
359a01ac 70sub set_db {
71 unless ( $_[0]{base_db_obj} ) {
72 $_[0]{base_db_obj} = $_[1];
73 Scalar::Util::weaken( $_[0]{base_db_obj} );
74 }
75}
76
460b1067 77sub open {
78 my $self = shift;
79
80 # Adding O_BINARY does remove the need for the binmode below. However,
81 # I'm not going to remove it because I don't have the Win32 chops to be
82 # absolutely certain everything will be ok.
83 my $flags = O_RDWR | O_CREAT | O_BINARY;
84
85 my $fh;
86 sysopen( $fh, $self->{file}, $flags )
87 or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
88 $self->{fh} = $fh;
89
90 # Even though we use O_BINARY, better be safe than sorry.
91 binmode $fh;
92
93 if ($self->{autoflush}) {
94 my $old = select $fh;
95 $|=1;
96 select $old;
97 }
98
99 return 1;
100}
101
102sub close {
103 my $self = shift;
104
105 if ( $self->{fh} ) {
106 close $self->{fh};
107 $self->{fh} = undef;
108 }
109
110 return 1;
111}
112
7dcefff3 113sub set_inode {
114 my $self = shift;
115
116 unless ( $self->{inode} ) {
117 my @stats = stat($self->{fh});
118 $self->{inode} = $stats[1];
119 $self->{end} = $stats[7];
120 }
121
122 return 1;
123}
124
019404df 125sub print_at {
126 my $self = shift;
127 my $loc = shift;
128
129 local ($/,$\);
130
131 my $fh = $self->{fh};
7dcefff3 132 if ( defined $loc ) {
133 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
134 }
135
019404df 136 print( $fh @_ );
137
138 return 1;
139}
140
7dcefff3 141sub read_at {
142 my $self = shift;
143 my ($loc, $size) = @_;
144
145 local ($/,$\);
146
147 my $fh = $self->{fh};
148 if ( defined $loc ) {
149 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
150 }
151
152 my $buffer;
153 read( $fh, $buffer, $size);
154
155 return $buffer;
156}
157
158sub increment_pointer {
159 my $self = shift;
160 my ($size) = @_;
161
162 if ( defined $size ) {
163 seek( $self->{fh}, $size, SEEK_CUR );
164 }
165
166 return 1;
167}
168
460b1067 169sub DESTROY {
170 my $self = shift;
171 return unless $self;
172
173 $self->close;
174
175 return;
176}
177
019404df 178sub request_space {
179 my $self = shift;
180 my ($size) = @_;
181
7dcefff3 182 #XXX Do I need to reset $self->{end} here? I need a testcase
019404df 183 my $loc = $self->{end};
184 $self->{end} += $size;
185
186 return $loc;
187}
188
189#sub release_space {
190# my $self = shift;
191# my ($size, $loc) = @_;
192#
193# local($/,$\);
194#
195# my $next_loc = 0;
196#
197# my $fh = $self->{fh};
198# seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
199# print( $fh SIG_FREE
200# . pack($self->{long_pack}, $size )
201# . pack($self->{long_pack}, $next_loc )
202# );
203#
204# return;
205#}
206
15ba72cc 207##
208# If db locking is set, flock() the db file. If called multiple
209# times before unlock(), then the same number of unlocks() must
210# be called before the lock is released.
211##
212sub lock {
213 my $self = shift;
214 my ($obj, $type) = @_;
215 $type = LOCK_EX unless defined $type;
216
217 if (!defined($self->{fh})) { return; }
218
219 if ($self->{locking}) {
220 if (!$self->{locked}) {
221 flock($self->{fh}, $type);
222
223 # refresh end counter in case file has changed size
224 my @stats = stat($self->{fh});
225 $self->{end} = $stats[7];
226
227 # double-check file inode, in case another process
228 # has optimize()d our file while we were waiting.
229 if ($stats[1] != $self->{inode}) {
230 $self->close;
231 $self->open;
232
233 #XXX This needs work
234 $obj->{engine}->setup_fh( $obj );
235
236 flock($self->{fh}, $type); # re-lock
237
238 # This may not be necessary after re-opening
239 $self->{end} = (stat($self->{fh}))[7]; # re-end
240 }
241 }
242 $self->{locked}++;
243
244 return 1;
245 }
246
247 return;
248}
249
250##
251# If db locking is set, unlock the db file. See note in lock()
252# regarding calling lock() multiple times.
253##
254sub unlock {
255 my $self = shift;
256
257 if (!defined($self->{fh})) { return; }
258
259 if ($self->{locking} && $self->{locked} > 0) {
260 $self->{locked}--;
261 if (!$self->{locked}) { flock($self->{fh}, LOCK_UN); }
262
263 return 1;
264 }
265
266 return;
267}
268
269sub set_transaction_offset {
270 my $self = shift;
271 $self->{transaction_offset} = shift;
272}
273
aa83bc1e 274sub audit {
275 my $self = shift;
276
277 if ( my $afh = $self->{audit_fh} ) {
278 my ($string) = @_;
279
280 flock( $afh, LOCK_EX );
281
282 if ( $string =~ /^#/ ) {
283 print( $afh "$string " . localtime(time) . "\n" );
284 }
285 else {
286 print( $afh "$string # " . localtime(time) . "\n" );
287 }
288
289 flock( $afh, LOCK_UN );
290 }
291
292 return 1;
293}
294
28394a1a 295sub begin_transaction {
296 my $self = shift;
297
15ba72cc 298 my $fh = $self->{fh};
299
20b7f047 300 $self->lock;
301
aa83bc1e 302 seek( $fh, $self->{transaction_offset} + $self->{file_offset}, SEEK_SET );
20b7f047 303 my $buffer;
304 read( $fh, $buffer, 4 );
305 $buffer = unpack( 'N', $buffer );
306
307 for ( 1 .. 32 ) {
308 next if $buffer & (1 << ($_ - 1));
309 $self->{transaction_id} = $_;
c9b6d0d8 310 $buffer |= (1 << $_-1 );
20b7f047 311 last;
312 }
313
aa83bc1e 314 seek( $fh, $self->{transaction_offset} + $self->{file_offset}, SEEK_SET );
20b7f047 315 print( $fh pack( 'N', $buffer ) );
15ba72cc 316
20b7f047 317 $self->unlock;
318
319 return $self->{transaction_id};
28394a1a 320}
321
322sub end_transaction {
323 my $self = shift;
324
20b7f047 325 my $fh = $self->{fh};
326
327 $self->lock;
328
aa83bc1e 329 seek( $fh, $self->{transaction_offset} + $self->{file_offset}, SEEK_SET );
20b7f047 330 my $buffer;
331 read( $fh, $buffer, 4 );
332 $buffer = unpack( 'N', $buffer );
333
334 # Unset $self->{transaction_id} bit
335
aa83bc1e 336 seek( $fh, $self->{transaction_offset} + $self->{file_offset}, SEEK_SET );
20b7f047 337 print( $fh pack( 'N', $buffer ) );
338
339 $self->unlock;
15ba72cc 340
28394a1a 341 $self->{transaction_id} = 0;
342}
343
20b7f047 344sub current_transactions {
28394a1a 345 my $self = shift;
346
20b7f047 347 my $fh = $self->{fh};
348
349 $self->lock;
350
aa83bc1e 351 seek( $fh, $self->{transaction_offset} + $self->{file_offset}, SEEK_SET );
20b7f047 352 my $buffer;
353 read( $fh, $buffer, 4 );
354 $buffer = unpack( 'N', $buffer );
355
356 $self->unlock;
357
358 my @transactions;
359 for ( 1 .. 32 ) {
360 if ( $buffer & (1 << ($_ - 1)) ) {
361 push @transactions, $_;
362 }
363 }
364
c9b6d0d8 365 return grep { $_ != $self->{transaction_id} } @transactions;
28394a1a 366}
367
20b7f047 368sub transaction_id { return $_[0]->{transaction_id} }
369
28394a1a 370#sub commit {
371#}
372
460b1067 3731;
374__END__
375