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   36 use strict;
  11         12  
  11         235  
11 11     11   4901 use IO::File;
  11         77830  
  11         1099  
12 11     11   63 use Fcntl qw( O_RDWR O_RDONLY O_BINARY );
  11         13  
  11         17195  
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 205 ( 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 252 my $self = shift;
34 262 100       700 ( ref $self ? $self->{'errstr'} : $XBase::Base::errstr ) = join '', @_;
35             }
36             # Null the errstr
37             sub NullError {
38 256     256 0 421 shift->Error('');
39             }
40              
41              
42             # Build the object in the memory, open the file
43             sub new {
44 47     47 1 6891 __PACKAGE__->NullError();
45 47         44 my $class = shift;
46 47         84 my $new = bless {}, $class;
47 47 100 100     234 if (@_ and not $new->open(@_)) { return; }
  4         46  
48 43         253 return $new;
49             }
50             # Open the specified file. Use the read_header to load the header data
51             sub open {
52 44     44 1 73 __PACKAGE__->NullError();
53 44         39 my $self = shift;
54 44         45 my %options;
55 44 100       117 if (scalar(@_) % 2) { $options{'name'} = shift; }
  25         48  
56 44 100       184 $self->{'openoptions'} = { %options, @_ } unless defined $self->{'openoptions'};
57 44         137 %options = (%options, @_);
58 44 50       91 if (defined $self->{'fh'}) { $self->close(); }
  0         0  
59              
60 44         42 my $external_fh = 0;
61 44         205 my $fh = new IO::File;
62 44         1017 my $rw;
63            
64 44 100       95 if ($options{'name'} eq '-') {
65 1 50       4 if (defined $options{'fh'}) {
66 1         2 $fh = $options{'fh'};
67 1         2 $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         40 my $ok = 1;
76 43 50       86 if (not $options{'readonly'}) {
77 43 100       129 if ($fh->open($options{'name'}, O_RDWR|O_BINARY)) {
78 39         1565 $rw = 1;
79             } else {
80 4         111 $ok = 0;
81             }
82             }
83 43 100       104 if (not $ok) {
84 4 50       11 if ($fh->open($options{'name'}, O_RDONLY|O_BINARY)) {
85 0         0 $rw = 0; $ok = 1;
  0         0  
86             } else {
87 4         46 $ok = 0;
88             }
89             }
90 43 100       91 if (not $ok) {
91 4         69 __PACKAGE__->Error("Error opening file $options{'name'}: $!\n");
92 4         21 return;
93             }
94             }
95              
96 40 100       71 $self->{'tell'} = 0 if $SEEK_VIA_READ;
97 40         152 $fh->autoflush();
98              
99 40 100       1252 binmode($fh) unless $external_fh;
100 40         67 @{$self}{ qw( fh filename rw ) } = ($fh, $options{'name'}, $rw);
  40         106  
101             ## $self->locksh();
102              
103             # read_header should be defined in the derived class
104 40         164 $self->read_header(@_);
105             }
106             # Close the file
107             sub close {
108 11     11 1 11 my $self = shift;
109 11         26 $self->NullError();
110 11 50       25 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         37 $self->{'fh'}->close();
115 11         162 delete $self->{'fh'};
116 11         15 1;
117             }
118             # Read from the filehandle
119             sub read {
120 1747     1747 0 1364 my $self = shift;
121 1747 50       2552 my $fh = $self->{'fh'} or return;
122 1747         2813 my $result = $fh->read(@_);
123 1747 100 66     14751 if (defined $result and defined $self->{'tell'}) {
124 18         15 $self->{'tell'} += $result;
125             }
126 1747         1708 $result;
127             }
128             # Tell the position
129             sub tell {
130 81     81 0 72 my $self = shift;
131 81 100       136 if (defined $self->{'tell'}) {
132 10         19 return $self->{'tell'};
133             }
134 71         185 return $self->{'fh'}->tell();
135             }
136             # Drop (unlink) the file
137             sub drop {
138 2     2 1 3 my $self = shift;
139 2         4 $self->NullError();
140 2 50       5 if (defined $self->{'filename'}) {
141 2         42 my $filename = $self->{'filename'};
142 2 50       8 $self->close() if defined $self->{'fh'};
143 2 50       111 if (not unlink $filename) {
144 0         0 $self->Error("Error unlinking file $filename: $!\n");
145 0         0 return;
146             }
147             }
148 2         4 1;
149             }
150              
151             # Create new file
152             sub create_file {
153 5     5 1 4 my $self = shift;
154 5         7 my ($filename, $perms) = @_;
155 5 50       10 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       36 if (-f $filename) {
160 0         0 __PACKAGE__->Error("File $filename already exists\n");
161 0         0 return;
162             }
163              
164 5 100       13 $perms = 0644 unless defined $perms;
165 5         26 my $fh = new IO::File;
166 5 50       132 $fh->open($filename, 'w+', $perms) or return;
167 5         414 binmode($fh);
168 5         7 @{$self}{ qw( fh filename rw ) } = ($fh, $filename, 1);
  5         31  
169 5         17 return $self;
170             }
171              
172              
173             # Compute the offset of the record
174             sub get_record_offset {
175 1646     1646 0 1245 my ($self, $num) = @_;
176             my ($header_len, $record_len) = ($self->{'header_len'},
177 1646         1531 $self->{'record_len'});
178 1646 50 33     4141 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       2064 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         2858 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 1187 my ($self, $offset) = @_;
199 1676 50       2119 unless (defined $self->{'fh'}) {
200 0         0 $self->Error("Cannot seek on unopened file\n");
201 0         0 return;
202             }
203 1676 50       3066 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         9631 1;
208             }
209             sub seek_to_read {
210 3     3 0 3 my ($self, $offset) = @_;
211 3 50       7 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       4 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       5 if ($offset > $tell) {
221 1         1 my $undef;
222 1         4 $self->read($undef, $offset - $tell);
223 1         2 $tell = $self->tell();
224             }
225 3 50       6 if ($tell != $offset) {
226 0         0 $self->Error("Some error occured during read-seek: $!\n");
227 0         0 return;
228             }
229 3         8 1;
230             }
231             sub SEEK_VIA_READ {
232 13     13 0 807 local $^W = 0;
233 13 100       61 if ($_[0]) {
234 1         20 *seek_to = \&seek_to_read; $SEEK_VIA_READ = 1;
  1         4  
235             } else {
236 12         36 *seek_to = \&seek_to_seek; $SEEK_VIA_READ = 0;
  12         31  
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 1182 my ($self, $num, $in_length) = @_;
246 1604 50       2105 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     2289 if ($self->last_record > 0 and $num > $self->last_record) {
251 2         17 $self->Error("Can't read record $num, there is not so many of them\n");
252 2         12 return;
253             }
254 1602 100       2160 if (not defined $in_length) {
255 1588         1323 $in_length = $self->{'record_len'};
256             }
257 1602 100       1853 if ($in_length < 0) {
258 14         16 $in_length = -$self->{'record_len'};
259             }
260              
261 1602 50       1790 defined (my $offset = $self->get_record_offset($num)) or return;
262 1602         1966 $self->read_from($offset, $in_length);
263             }
264             sub read_from {
265 1602     1602 1 1218 my ($self, $offset, $in_length) = @_;
266 1602 50       1830 unless (defined $offset) {
267 0         0 $self->Error("Offset to read from must be specified\n");
268 0         0 return;
269             }
270 1602 50       1837 $self->seek_to($offset) or return;
271 1602         1182 my $length = $in_length;
272 1602 100       1981 $length = -$length if $length < 0;
273 1602         971 my $buffer;
274 1602         1872 my $read = $self->read($buffer, $length);
275 1602 50 66     5573 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         3637 $buffer;
280             }
281              
282             # Write the given record
283             sub write_record {
284 44     44 1 46 my ($self, $num) = (shift, shift);
285 44 50       95 defined (my $offset = $self->get_record_offset($num)) or return;
286 44 50       91 defined $self->write_to($offset, @_) or return;
287 44 100       95 $num == 0 ? '0E0' : $num;
288             }
289             # Write data directly to offset
290             sub write_to {
291 77     77 1 71 my ($self, $offset) = (shift, shift);
292 77 50       113 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       111 $self->seek_to($offset) or return;
297 77         182 local ($,, $\) = ('', '');
298             $self->{'fh'}->print(@_) or
299 77 50       158 do { $self->Error("Error writing to offset $offset in file $self->{'filename'}: $!\n");
  0         0  
300 0         0 return;
301             };
302 77 100       950 $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__