File Coverage

blib/lib/XBase/Base.pm
Criterion Covered Total %
statement 141 189 74.6
branch 73 104 70.1
condition 12 18 66.6
subroutine 21 29 72.4
pod 10 23 43.4
total 257 363 70.8


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             XBase::Base - Base input output module for XBase suite
5              
6             =cut
7              
8             package XBase::Base;
9              
10 11     11   50 use strict;
  11         17  
  11         316  
11 11     11   15132 use IO::File;
  11         142526  
  11         1563  
12 11     11   86 use Fcntl qw( O_RDWR O_RDONLY O_BINARY );
  11         23  
  11         40303  
13              
14             $XBase::Base::VERSION = '1.02';
15              
16             # Sets the debug level
17             $XBase::Base::DEBUG = 0;
18             sub DEBUG () {
19 0     0 0 0 $XBase::Base::DEBUG
20             }
21              
22             my $SEEK_VIA_READ = 0;
23              
24             # Holds the text of the global error, if there was one
25             $XBase::Base::errstr = '';
26             # Fetch the error message
27             sub errstr () {
28 2 100   2 0 37 ( ref $_[0] ? $_[0]->{'errstr'} : $XBase::Base::errstr );
29             }
30              
31             # Set errstr and print error on STDERR if there is debug level
32             sub Error (@) {
33 262     262 0 372 my $self = shift;
34 262 100       1001 ( ref $self ? $self->{'errstr'} : $XBase::Base::errstr ) = join '', @_;
35             }
36             # Null the errstr
37             sub NullError {
38 256     256 0 644 shift->Error('');
39             }
40              
41              
42             # Build the object in the memory, open the file
43             sub new {
44 47     47 1 9643 __PACKAGE__->NullError();
45 47         73 my $class = shift;
46 47         126 my $new = bless {}, $class;
47 47 100 100     313 if (@_ and not $new->open(@_)) { return; }
  4         55  
48 43         413 return $new;
49             }
50             # Open the specified file. Use the read_header to load the header data
51             sub open {
52 44     44 1 116 __PACKAGE__->NullError();
53 44         79 my $self = shift;
54 44         62 my %options;
55 44 100       204 if (scalar(@_) % 2) { $options{'name'} = shift; }
  25         88  
56 44 100       269 $self->{'openoptions'} = { %options, @_ } unless defined $self->{'openoptions'};
57 44         223 %options = (%options, @_);
58 44 50       154 if (defined $self->{'fh'}) { $self->close(); }
  0         0  
59              
60 44         93 my $external_fh = 0;
61 44         303 my $fh = new IO::File;
62 44         1694 my $rw;
63            
64 44 100       192 if ($options{'name'} eq '-') {
65 1 50       5 if (defined $options{'fh'}) {
66 1         2 $fh = $options{'fh'};
67 1         5 $external_fh = 1;
68             } else {
69 0         0 $fh->fdopen(fileno(STDIN), 'r');
70 0         0 $self->{'stream'} = 1;
71 0         0 SEEK_VIA_READ(1);
72             }
73 1         1 $rw = 0;
74             } else {
75 43         74 my $ok = 1;
76 43 50       122 if (not $options{'readonly'}) {
77 43 100       177 if ($fh->open($options{'name'}, O_RDWR|O_BINARY)) {
78 39         2558 $rw = 1;
79             } else {
80 4         96 $ok = 0;
81             }
82             }
83 43 100       146 if (not $ok) {
84 4 50       21 if ($fh->open($options{'name'}, O_RDONLY|O_BINARY)) {
85 0         0 $rw = 0; $ok = 1;
  0         0  
86             } else {
87 4         76 $ok = 0;
88             }
89             }
90 43 100       307 if (not $ok) {
91 4         69 __PACKAGE__->Error("Error opening file $options{'name'}: $!\n");
92 4         25 return;
93             }
94             }
95              
96 40 100       114 $self->{'tell'} = 0 if $SEEK_VIA_READ;
97 40         197 $fh->autoflush();
98              
99 40 100       1679 binmode($fh) unless $external_fh;
100 40         88 @{$self}{ qw( fh filename rw ) } = ($fh, $options{'name'}, $rw);
  40         147  
101             ## $self->locksh();
102              
103             # read_header should be defined in the derived class
104 40         178 $self->read_header(@_);
105             }
106             # Close the file
107             sub close {
108 11     11 1 19 my $self = shift;
109 11         41 $self->NullError();
110 11 50       32 if (not defined $self->{'fh'}) {
111 0         0 $self->Error("Can't close file that is not opened\n");
112 0         0 return;
113             }
114 11         57 $self->{'fh'}->close();
115 11         319 delete $self->{'fh'};
116 11         23 1;
117             }
118             # Read from the filehandle
119             sub read {
120 1747     1747 0 1994 my $self = shift;
121 1747 50       7577 my $fh = $self->{'fh'} or return;
122 1747         4418 my $result = $fh->read(@_);
123 1747 100 66     27595 if (defined $result and defined $self->{'tell'}) {
124 18         27 $self->{'tell'} += $result;
125             }
126 1747         3136 $result;
127             }
128             # Tell the position
129             sub tell {
130 81     81 0 109 my $self = shift;
131 81 100       215 if (defined $self->{'tell'}) {
132 10         31 return $self->{'tell'};
133             }
134 71         336 return $self->{'fh'}->tell();
135             }
136             # Drop (unlink) the file
137             sub drop {
138 2     2 1 4 my $self = shift;
139 2         8 $self->NullError();
140 2 50       6 if (defined $self->{'filename'}) {
141 2         79 my $filename = $self->{'filename'};
142 2 50       14 $self->close() if defined $self->{'fh'};
143 2 50       164 if (not unlink $filename) {
144 0         0 $self->Error("Error unlinking file $filename: $!\n");
145 0         0 return;
146             }
147             }
148 2         6 1;
149             }
150              
151             # Create new file
152             sub create_file {
153 5     5 1 9 my $self = shift;
154 5         9 my ($filename, $perms) = @_;
155 5 50       15 if (not defined $filename) {
156 0         0 __PACKAGE__->Error("Name has to be specified when creating new file\n");
157 0         0 return;
158             }
159 5 50       82 if (-f $filename) {
160 0         0 __PACKAGE__->Error("File $filename already exists\n");
161 0         0 return;
162             }
163              
164 5 100       18 $perms = 0644 unless defined $perms;
165 5         33 my $fh = new IO::File;
166 5 50       192 $fh->open($filename, 'w+', $perms) or return;
167 5         585 binmode($fh);
168 5         9 @{$self}{ qw( fh filename rw ) } = ($fh, $filename, 1);
  5         48  
169 5         24 return $self;
170             }
171              
172              
173             # Compute the offset of the record
174             sub get_record_offset {
175 1646     1646 0 2155 my ($self, $num) = @_;
176 1646         2722 my ($header_len, $record_len) = ($self->{'header_len'},
177             $self->{'record_len'});
178 1646 50 33     6078 unless (defined $header_len and defined $record_len) {
179 0         0 $self->Error("Header and record lengths not known in get_record_offset\n");
180 0         0 return;
181             }
182 1646 50       2986 unless (defined $num) {
183 0         0 $self->Error("Number of the record must be specified in get_record_offset\n");
184 0         0 return;
185             }
186 1646         4537 return $header_len + $num * $record_len;
187             }
188              
189              
190             # Seek to start of the record
191             sub seek_to_record {
192 0     0 1 0 my ($self, $num) = @_;
193 0 0       0 defined (my $offset = $self->get_record_offset($num)) or return;
194 0         0 $self->seek_to($offset);
195             }
196             # Seek to absolute position
197             sub seek_to_seek {
198 1676     1676 0 2123 my ($self, $offset) = @_;
199 1676 50       3196 unless (defined $self->{'fh'}) {
200 0         0 $self->Error("Cannot seek on unopened file\n");
201 0         0 return;
202             }
203 1676 50       4902 unless ($self->{'fh'}->seek($offset, 0)) {
204 0         0 $self->Error("Seek error (file $self->{'filename'}, offset $offset): $!\n");
205 0         0 return;
206             }
207 1676         22096 1;
208             }
209             sub seek_to_read {
210 3     3 0 4 my ($self, $offset) = @_;
211 3 50       11 unless (defined $self->{'fh'}) {
212 0         0 $self->Error("Cannot seek on unopened file\n");
213 0         0 return;
214             }
215 3         4 my $tell = $self->tell();
216 3 50       9 if ($offset < $tell) {
217 0         0 $self->Error("Cannot seek backwards without using seek ($offset < $tell)\n");
218 0         0 return;
219             }
220 3 100       7 if ($offset > $tell) {
221 1         1 my $undef;
222 1         5 $self->read($undef, $offset - $tell);
223 1         8 $tell = $self->tell();
224             }
225 3 50       7 if ($tell != $offset) {
226 0         0 $self->Error("Some error occured during read-seek: $!\n");
227 0         0 return;
228             }
229 3         9 1;
230             }
231             sub SEEK_VIA_READ {
232 13     13 0 472 local $^W = 0;
233 13 100       129 if ($_[0]) {
234 1         24 *seek_to = \&seek_to_read; $SEEK_VIA_READ = 1;
  1         6  
235             } else {
236 12         48 *seek_to = \&seek_to_seek; $SEEK_VIA_READ = 0;
  12         41  
237             }
238             }
239             SEEK_VIA_READ(0);
240              
241             # Read the record of given number. The second parameter is the length of
242             # the record to read. It can be undefined, meaning read the whole record,
243             # and it can be negative, meaning at most the length
244             sub read_record {
245 1604     1604 1 2190 my ($self, $num, $in_length) = @_;
246 1604 50       2925 if (not defined $num) {
247 0         0 $self->Error("Number of the record must be defined when reading it\n");
248 0         0 return;
249             }
250 1604 100 100     3828 if ($self->last_record > 0 and $num > $self->last_record) {
251 2         11 $self->Error("Can't read record $num, there is not so many of them\n");
252 2         12 return;
253             }
254 1602 100       3478 if (not defined $in_length) {
255 1588         2294 $in_length = $self->{'record_len'};
256             }
257 1602 100       2822 if ($in_length < 0) {
258 14         22 $in_length = -$self->{'record_len'};
259             }
260              
261 1602 50       2945 defined (my $offset = $self->get_record_offset($num)) or return;
262 1602         3299 $self->read_from($offset, $in_length);
263             }
264             sub read_from {
265 1602     1602 1 1928 my ($self, $offset, $in_length) = @_;
266 1602 50       3654 unless (defined $offset) {
267 0         0 $self->Error("Offset to read from must be specified\n");
268 0         0 return;
269             }
270 1602 50       2774 $self->seek_to($offset) or return;
271 1602         1855 my $length = $in_length;
272 1602 100       2825 $length = -$length if $length < 0;
273 1602         2132 my $buffer;
274 1602         3123 my $read = $self->read($buffer, $length);
275 1602 50 66     7918 if (not defined $read or ($in_length > 0 and $read != $in_length)) {
      33        
276 0         0 $self->Error("Error reading $in_length bytes from $self->{'filename'}\n");
277 0         0 return;
278             }
279 1602         6351 $buffer;
280             }
281              
282             # Write the given record
283             sub write_record {
284 44     44 1 69 my ($self, $num) = (shift, shift);
285 44 50       134 defined (my $offset = $self->get_record_offset($num)) or return;
286 44 50       140 defined $self->write_to($offset, @_) or return;
287 44 100       159 $num == 0 ? '0E0' : $num;
288             }
289             # Write data directly to offset
290             sub write_to {
291 77     77 1 116 my ($self, $offset) = (shift, shift);
292 77 50       173 if (not $self->{'rw'}) {
293 0         0 $self->Error("The file $self->{'filename'} is not writable\n");
294 0         0 return;
295             }
296 77 50       201 $self->seek_to($offset) or return;
297 77         257 local ($,, $\) = ('', '');
298             $self->{'fh'}->print(@_) or
299 77 50       272 do { $self->Error("Error writing to offset $offset in file $self->{'filename'}: $!\n");
  0         0  
300 0         0 return;
301             };
302 77 100       1448 $offset == 0 ? '0E0' : $offset;
303             }
304              
305              
306 0     0 0   sub locksh { _locksh(shift->{'fh'}) }
307 0     0 0   sub lockex { _lockex(shift->{'fh'}) }
308 0     0 0   sub unlock { _unlock(shift->{'fh'}) }
309              
310 0     0     sub _locksh { flock(shift, 1); }
311 0     0     sub _lockex { flock(shift, 2); }
312 0     0     sub _unlock { flock(shift, 8); }
313              
314              
315             1;
316              
317             __END__