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   31 use strict;
  11         12  
  11         229  
11 11     11   4399 use IO::File;
  11         69261  
  11         999  
12 11     11   51 use Fcntl qw( O_RDWR O_RDONLY O_BINARY );
  11         14  
  11         15480  
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 26 ( 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 234 my $self = shift;
34 262 100       672 ( ref $self ? $self->{'errstr'} : $XBase::Base::errstr ) = join '', @_;
35             }
36             # Null the errstr
37             sub NullError {
38 256     256 0 398 shift->Error('');
39             }
40              
41              
42             # Build the object in the memory, open the file
43             sub new {
44 47     47 1 3048 __PACKAGE__->NullError();
45 47         43 my $class = shift;
46 47         75 my $new = bless {}, $class;
47 47 100 100     203 if (@_ and not $new->open(@_)) { return; }
  4         36  
48 43         242 return $new;
49             }
50             # Open the specified file. Use the read_header to load the header data
51             sub open {
52 44     44 1 71 __PACKAGE__->NullError();
53 44         40 my $self = shift;
54 44         41 my %options;
55 44 100       108 if (scalar(@_) % 2) { $options{'name'} = shift; }
  25         45  
56 44 100       176 $self->{'openoptions'} = { %options, @_ } unless defined $self->{'openoptions'};
57 44         160 %options = (%options, @_);
58 44 50       92 if (defined $self->{'fh'}) { $self->close(); }
  0         0  
59              
60 44         41 my $external_fh = 0;
61 44         179 my $fh = new IO::File;
62 44         945 my $rw;
63            
64 44 100       92 if ($options{'name'} eq '-') {
65 1 50       3 if (defined $options{'fh'}) {
66 1         1 $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         38 my $ok = 1;
76 43 50       83 if (not $options{'readonly'}) {
77 43 100       123 if ($fh->open($options{'name'}, O_RDWR|O_BINARY)) {
78 39         1559 $rw = 1;
79             } else {
80 4         100 $ok = 0;
81             }
82             }
83 43 100       103 if (not $ok) {
84 4 50       13 if ($fh->open($options{'name'}, O_RDONLY|O_BINARY)) {
85 0         0 $rw = 0; $ok = 1;
  0         0  
86             } else {
87 4         54 $ok = 0;
88             }
89             }
90 43 100       84 if (not $ok) {
91 4         57 __PACKAGE__->Error("Error opening file $options{'name'}: $!\n");
92 4         21 return;
93             }
94             }
95              
96 40 100       70 $self->{'tell'} = 0 if $SEEK_VIA_READ;
97 40         130 $fh->autoflush();
98              
99 40 100       1245 binmode($fh) unless $external_fh;
100 40         60 @{$self}{ qw( fh filename rw ) } = ($fh, $options{'name'}, $rw);
  40         101  
101             ## $self->locksh();
102              
103             # read_header should be defined in the derived class
104 40         121 $self->read_header(@_);
105             }
106             # Close the file
107             sub close {
108 11     11 1 11 my $self = shift;
109 11         19 $self->NullError();
110 11 50       21 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         43 $self->{'fh'}->close();
115 11         161 delete $self->{'fh'};
116 11         15 1;
117             }
118             # Read from the filehandle
119             sub read {
120 1747     1747 0 1270 my $self = shift;
121 1747 50       2289 my $fh = $self->{'fh'} or return;
122 1747         2849 my $result = $fh->read(@_);
123 1747 100 66     14287 if (defined $result and defined $self->{'tell'}) {
124 18         16 $self->{'tell'} += $result;
125             }
126 1747         1667 $result;
127             }
128             # Tell the position
129             sub tell {
130 81     81 0 71 my $self = shift;
131 81 100       134 if (defined $self->{'tell'}) {
132 10         18 return $self->{'tell'};
133             }
134 71         175 return $self->{'fh'}->tell();
135             }
136             # Drop (unlink) the file
137             sub drop {
138 2     2 1 3 my $self = shift;
139 2         5 $self->NullError();
140 2 50       5 if (defined $self->{'filename'}) {
141 2         38 my $filename = $self->{'filename'};
142 2 50       9 $self->close() if defined $self->{'fh'};
143 2 50       109 if (not unlink $filename) {
144 0         0 $self->Error("Error unlinking file $filename: $!\n");
145 0         0 return;
146             }
147             }
148 2         5 1;
149             }
150              
151             # Create new file
152             sub create_file {
153 5     5 1 5 my $self = shift;
154 5         5 my ($filename, $perms) = @_;
155 5 50       11 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       33 if (-f $filename) {
160 0         0 __PACKAGE__->Error("File $filename already exists\n");
161 0         0 return;
162             }
163              
164 5 100       16 $perms = 0644 unless defined $perms;
165 5         23 my $fh = new IO::File;
166 5 50       129 $fh->open($filename, 'w+', $perms) or return;
167 5         417 binmode($fh);
168 5         7 @{$self}{ qw( fh filename rw ) } = ($fh, $filename, 1);
  5         32  
169 5         15 return $self;
170             }
171              
172              
173             # Compute the offset of the record
174             sub get_record_offset {
175 1646     1646 0 1305 my ($self, $num) = @_;
176             my ($header_len, $record_len) = ($self->{'header_len'},
177 1646         1584 $self->{'record_len'});
178 1646 50 33     4091 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       1835 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         2882 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 1158 my ($self, $offset) = @_;
199 1676 50       2003 unless (defined $self->{'fh'}) {
200 0         0 $self->Error("Cannot seek on unopened file\n");
201 0         0 return;
202             }
203 1676 50       2876 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         9446 1;
208             }
209             sub seek_to_read {
210 3     3 0 3 my ($self, $offset) = @_;
211 3 50       5 unless (defined $self->{'fh'}) {
212 0         0 $self->Error("Cannot seek on unopened file\n");
213 0         0 return;
214             }
215 3         3 my $tell = $self->tell();
216 3 50       6 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       6 if ($offset > $tell) {
221 1         1 my $undef;
222 1         3 $self->read($undef, $offset - $tell);
223 1         2 $tell = $self->tell();
224             }
225 3 50       8 if ($tell != $offset) {
226 0         0 $self->Error("Some error occured during read-seek: $!\n");
227 0         0 return;
228             }
229 3         6 1;
230             }
231             sub SEEK_VIA_READ {
232 13     13 0 148 local $^W = 0;
233 13 100       45 if ($_[0]) {
234 1         19 *seek_to = \&seek_to_read; $SEEK_VIA_READ = 1;
  1         3  
235             } else {
236 12         33 *seek_to = \&seek_to_seek; $SEEK_VIA_READ = 0;
  12         27  
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 1219 my ($self, $num, $in_length) = @_;
246 1604 50       1921 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     2186 if ($self->last_record > 0 and $num > $self->last_record) {
251 2         13 $self->Error("Can't read record $num, there is not so many of them\n");
252 2         8 return;
253             }
254 1602 100       2104 if (not defined $in_length) {
255 1588         1310 $in_length = $self->{'record_len'};
256             }
257 1602 100       2050 if ($in_length < 0) {
258 14         14 $in_length = -$self->{'record_len'};
259             }
260              
261 1602 50       1733 defined (my $offset = $self->get_record_offset($num)) or return;
262 1602         1951 $self->read_from($offset, $in_length);
263             }
264             sub read_from {
265 1602     1602 1 1210 my ($self, $offset, $in_length) = @_;
266 1602 50       1787 unless (defined $offset) {
267 0         0 $self->Error("Offset to read from must be specified\n");
268 0         0 return;
269             }
270 1602 50       1642 $self->seek_to($offset) or return;
271 1602         1239 my $length = $in_length;
272 1602 100       1921 $length = -$length if $length < 0;
273 1602         918 my $buffer;
274 1602         1817 my $read = $self->read($buffer, $length);
275 1602 50 66     5512 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         3766 $buffer;
280             }
281              
282             # Write the given record
283             sub write_record {
284 44     44 1 37 my ($self, $num) = (shift, shift);
285 44 50       73 defined (my $offset = $self->get_record_offset($num)) or return;
286 44 50       73 defined $self->write_to($offset, @_) or return;
287 44 100       99 $num == 0 ? '0E0' : $num;
288             }
289             # Write data directly to offset
290             sub write_to {
291 77     77 1 72 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       101 $self->seek_to($offset) or return;
297 77         194 local ($,, $\) = ('', '');
298             $self->{'fh'}->print(@_) or
299 77 50       152 do { $self->Error("Error writing to offset $offset in file $self->{'filename'}: $!\n");
  0         0  
300 0         0 return;
301             };
302 77 100       887 $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__