File Coverage

blib/lib/VisionDB/Read.pm
Criterion Covered Total %
statement 228 236 96.6
branch 81 108 75.0
condition 4 8 50.0
subroutine 26 27 96.3
pod 10 10 100.0
total 349 389 89.7


line stmt bran cond sub pod time code
1             # This program is free software: you can redistribute it and/or modify
2             # it under the terms of the GNU General Public License as published by
3             # the Free Software Foundation, either version 2 of the License, or
4             # (at your option) any later version.
5             #
6             # This program is distributed in the hope that it will be useful,
7             # but WITHOUT ANY WARRANTY; without even the implied warranty of
8             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9             # GNU General Public License for more details.
10             #
11             # You should have received a copy of the GNU General Public License
12             # along with this file. If not, see .
13              
14             package VisionDB::Read;
15              
16 9     9   220403 use strict;
  9         21  
  9         357  
17 9     9   52 use warnings;
  9         21  
  9         266  
18 9     9   47 use Exporter;
  9         22  
  9         408  
19 9     9   68 use File::Basename;
  9         24  
  9         835  
20 9     9   58 use Fcntl qw(SEEK_SET SEEK_CUR);
  9         18  
  9         869  
21              
22             our ($VERSION, @ISA);
23             @ISA = qw( Exporter );
24             $VERSION = 0.04;
25              
26 9     9   26229 BEGIN {
27             }
28              
29 9     9   311 END {
30             }
31             #--------------------------------------#
32             # Open DB segment from basename and #
33             # segment number #
34             # in: $basename, $seg #
35             # out: $handle #
36             # die if error #
37             #--------------------------------------#
38              
39             sub _open_db_segment {
40              
41 51     51   144 my ($basename, $segment) = @_;
42              
43 51         86 my $fh;
44 51 100       394 my $fn = $basename.(($segment) ? ".d".sprintf("%02d", $segment) : '');
45              
46 51 50       3003 open($fh, "<$fn") || die "open error ($!)\n";
47 51   50     330 binmode $fh || die "cannot set binmode ($!)\n";
48 51         489 return $fh;
49             }
50              
51             #-------------------------------------#
52             # Destroy internal vars #
53             # in: - #
54             # out: - #
55             #-------------------------------------#
56             sub _free_vars {
57              
58 28     28   90 my $self = shift;
59              
60 28         59 undef $self->{_err};
61 28         50 undef $self->{_db_fname};
62 28         48 undef $self->{_db_fpath};
63 28         42 undef $self->{_fh};
64 28         73 undef $self->{_rec_tot};
65 28         43 undef $self->{_rec_cur};
66 28         44 undef $self->{_version};
67 28         76 undef $self->{_data_hlen};
68 28         43 undef $self->{_data_cur};
69 28         44 undef $self->{_seg_tot};
70 28         42 undef $self->{_seg_cur};
71 28         77 undef $self->{_seg_size};
72 28         44 undef $self->{_data_start};
73 28         41 undef $self->{_data_end};
74 28         1030 undef $self->{_data_keys};
75             }
76              
77             #--------------------------------------#
78             # Read next valid record and increment #
79             # ptr to the next one #
80             # in: $objref #
81             # out: $data_arrayref #
82             # die if error #
83             #--------------------------------------#
84             sub _read_next {
85              
86 59732     59732   90202 my $self = shift;
87              
88 59732 100       134287 $self->{_rec_cur} >= 0 || die "invalid negatve record number\n";
89              
90 59730         86657 my $fh = $self->{_fh};
91 59730         85359 my $dseg = $self->{_seg_cur};
92 59730         109517 my $dptr = $self->{_data_cur};
93 59730         80228 my $data;
94              
95 59730         68622 my $del = 0;
96 59730         66909 do {
97 60000         66005 my $buf;
98 60000         77516 my ($h1, $h2) = (0, 0);
99 60000 50       201455 $self->{_seg_size}*$dseg + $dptr < $self->{_data_end} || die "record ptr beyond EOF\n";
100 60000 50       254543 (my $r = read($fh, $buf, $self->{_data_hlen})) || die "read error ($!)\n";
101             HDR1: {
102 60000 50       73974 if ($self->{_version} == 4) { ($h1, $h2) = unpack('n2', $buf) if ($r == $self->{_data_hlen}); last HDR1 }
  60000 100       158260  
  30000         135769  
  30000         67266  
103 30000 100       73378 if ($self->{_version} == 5) { ($h1, $h2, $del) = unpack('N2C', $buf) if ($r == $self->{_data_hlen}); last HDR1 }
  30000 50       153390  
  30000         79400  
104             }
105 60000 100       138985 if ($h1 == 0) {
106 15         31 $dseg++;
107 15         35 $dptr = $self->{_data_start};
108 15 50       57 close $fh if ($fh != $self->{_fh});
109 15         108 $fh = _open_db_segment($self->{_db_fpath}.$self->{_db_fname}, $dseg);
110 15 50       133 seek($fh, $self->{_data_start}, SEEK_SET) || die "seek error ($!)\n";
111 15 50       7086 read($fh, $buf, $self->{_data_hlen}) == $self->{_data_hlen} || die "read error ($!)\n";
112             HDR2: {
113 15 100       45 if ($self->{_version} == 4) { ($h1, $h2) = unpack('n2', $buf); last HDR2 }
  15         65  
  6         25  
  6         19  
114 9 50       36 if ($self->{_version} == 5) { ($h1, $h2, $del) = unpack('N2C', $buf); last HDR2 }
  9         52  
  9         29  
115             }
116             }
117 60000 100       178805 $del = ($h2 == 0) if ($self->{_version} == 4);
118 60000 100       115421 if ($del) {
119 270 50       3462 seek($fh, $h1, SEEK_CUR) || die "seek error ($!)\n";
120             } else {
121 59730 50       198215 read($fh, $buf, $h1) == $h1 || die "read error ($!)\n";
122 59730         179993 $data = [$buf, $h1];
123             }
124 60000         136367 $dptr += $self->{_data_hlen}+$h1;
125 60000         250867 undef $buf;
126             } until ($del == 0);
127 59730 100       163323 close $self->{_fh} if ($fh != $self->{_fh});
128 59730         89699 $self->{_fh} = $fh;
129 59730         79431 $self->{_seg_cur} = $dseg;
130 59730         105164 $self->{_data_cur} = $dptr;
131 59730         86305 $self->{_rec_cur}++;
132 59730 100       165971 $self->{_rec_cur} = -1 if ($self->{_rec_cur} >= $self->{_rec_tot});
133            
134 59730         235526 return $data;
135             }
136              
137             #################
138             ## constructor ##
139             #################
140             sub new {
141              
142 14     14 1 7662 my ($class, $filename) = @_;
143              
144 14         192 my $self = {
145             _err => 0,
146             _db_fname => undef,
147             _db_fpath => undef,
148             _fh => undef,
149             _rec_tot => undef,
150             _rec_cur => undef,
151             _version => undef,
152             _data_hlen => undef,
153             _data_cur => undef,
154             _seg_tot => undef,
155             _seg_cur => undef,
156             _seg_size => undef,
157             _data_start => undef,
158             _data_end => undef,
159             _data_keys => undef,
160             };
161              
162 14         32 eval {
163 14         27 my ($buf, $hdr_kcnt, $hdr_zip, $hdr_enc);
164 14 50       476 (($self->{_db_fname}, $self->{_db_fpath}) = fileparse($filename)) || die "cannot parse filename\n";
165             # open file, segment 0
166 14         81 $self->{_fh} = _open_db_segment($self->{_db_fpath}.$self->{_db_fname}, 0);
167             # read 1st 512 bytes (1 block)
168 14 50       393 read($self->{_fh}, $buf, 512) == 512 || die "read failed ($!)\n";
169             # check file type and version
170 14         121 (my $t, $self->{_version}) = unpack('Nn', $buf);
171 14 50       101 $t == 0x10121419 || die "wrong file type\n";
172             # acquire data area ptr
173             DATA_PTR: {
174 14 100       34 if ($self->{_version} == 4) { $self->{_data_start} = unpack('N', substr($buf, 0x001e, 4)); last DATA_PTR; }
  14         67  
  7         38  
  7         26  
175 7 50       31 if ($self->{_version} == 5) { $self->{_data_start} = unpack('N', substr($buf, 0x0022, 4)); last DATA_PTR; }
  7         36  
  7         23  
176 0         0 die "unsupported file version\n";
177             }
178             # read the rest of header
179 14 100 50     120 (read($self->{_fh}, $buf, $self->{_data_start}-512, 512) == $self->{_data_start}-512 || die "read failed ($!)\n") if ($self->{_data_start} > 0);
180             # acquire some more infos from header
181             READ_HDR: {
182 14 100       31 if ($self->{_version} == 4) {
  14         68  
183 7         47 $self->{_data_hlen} = unpack('n', substr($buf, 0x0054, 2));
184 7         41 $self->{_seg_tot} = unpack('n', substr($buf, 0x0062, 2))+1;
185 7         36 $self->{_seg_size} = unpack('N', substr($buf, 0x0066, 4));
186 7         53 $self->{_data_end} = unpack('N', substr($buf, 0x0018, 4)) + unpack('n', substr($buf, 0x001c, 2))*$self->{_seg_size};
187 7         35 $self->{_rec_tot} = unpack('N', substr($buf, 0x0034, 4));
188 7         43 $hdr_kcnt = unpack('C', substr($buf, 0x0078, 1));
189 7         29 $hdr_zip = unpack('C', substr($buf, 0x0079, 1));
190 7         39 $hdr_enc = unpack('C', substr($buf, 0x007a, 1));
191             # get keys data
192 7 50       39 $self->{_data_keys} = [] if ($hdr_kcnt > 0);
193 7         30 for my $i (0 .. $hdr_kcnt-1) {
194 13         69 my $key_segs = unpack('C', substr($buf, 0x00a0+$i*(11+16*3)+7, 1));
195 13 50       58 $self->{_data_keys}->[$i] = [] if ($key_segs > 0);
196 13         132 for my $j (0 .. $key_segs-1) {
197 19         219 $self->{_data_keys}->[$i]->[$j] = [
198             unpack('C', substr($buf, 0x00a0+$i*(11+16*3)+11+$j*3+0, 1)), # size
199             unpack('n', substr($buf, 0x00a0+$i*(11+16*3)+11+$j*3+1, 2)) # offset
200             ];
201             }
202             }
203 7         24 last READ_HDR;
204             }
205 7 50       34 if ($self->{_version} == 5) {
206 7         35 $self->{_data_hlen} = unpack('n', substr($buf, 0x0068, 2));
207 7         46 $self->{_seg_tot} = unpack('n', substr($buf, 0x0076, 2))+1;
208 7         35 $self->{_seg_size} = unpack('N', substr($buf, 0x007a, 4));
209 7         50 $self->{_data_end} = unpack('N', substr($buf, 0x001c, 4)) + unpack('n', substr($buf, 0x0020, 2))*$self->{_seg_size};
210 7         29 $self->{_rec_tot} = unpack('N', substr($buf, 0x003e, 4));
211 7         28 $hdr_kcnt = unpack('C', substr($buf, 0x009d, 1));
212 7         21 $hdr_zip = unpack('C', substr($buf, 0x009e, 1));
213 7         25 $hdr_enc = unpack('C', substr($buf, 0x009f, 1));
214             # get keys data
215 7 50       38 $self->{_data_keys} = [] if ($hdr_kcnt > 0);
216 7         31 for my $i (0 .. $hdr_kcnt-1) {
217 13         142 my $key_segs = unpack('C', substr($buf, 0x00c2+$i*(10+16*6)+7, 1));
218 13 50       62 $self->{_data_keys}->[$i] = [] if ($key_segs > 0);
219 13         36 for my $j (0 .. $key_segs-1) {
220 19         249 $self->{_data_keys}->[$i]->[$j] = [
221             unpack('n', substr($buf, 0x00c2+$i*(10+16*6)+10+$j*6+0, 2)), # size
222             unpack('N', substr($buf, 0x00c2+$i*(10+16*6)+10+$j*6+2, 4)) # offset
223             ];
224             }
225             }
226 7         22 last READ_HDR;
227             }
228             }
229             # check for unsupported archives
230 14 50       53 $hdr_zip == 0 || die "compressed DBs are not supported (yet)\n";
231 14 50       73 $hdr_enc == 0 || die "encrypted DBs are not supported (yet)\n";
232             # check if all segments files are open-able
233 14   50     121 close(_open_db_segment($self->{_db_fpath}.$self->{_db_fname}, $_) || die "some data files are unavailable\n") for (1 .. $self->{_seg_tot}-1);
234             # finalize init
235 14 100       571 $self->{_rec_cur} = ($self->{_data_start} > 0) ? 0 : -1;
236 14         30 $self->{_seg_cur} = 0;
237 14         521 $self->{_data_cur} = $self->{_data_start};
238             };
239 14 50       56 if ($@) {
240 0 0       0 close $self->{_fh} if ($self->{_fh});
241 0         0 chomp $@;
242 0         0 $self->{_err} = @_;
243             #Carp::carp "Unable to initialize DB: $@";
244 0         0 warn "\n*** $@\n";
245 0         0 return undef;
246             }
247              
248 14         507 bless ($self, $class);
249 14         1038 return $self;
250             }
251              
252             ################
253             ## destructor ##
254             ################
255             sub DESTROY {
256              
257 14     14   1730 my ($self) = @_;
258              
259 14         40 _free_vars($self);
260             }
261              
262             ###############################
263             # Free object, discard all #
264             # in: - #
265             # out: - #
266             ###############################
267             sub free {
268              
269 14     14 1 4484 my ($self) = @_;
270              
271 14         376 close($self->{_fh});
272 14         53 _free_vars($self);
273             }
274              
275             ###################################
276             # get records counter #
277             # in: - #
278             # out: $scalar (record counter) #
279             ###################################
280             sub records {
281              
282 6     6 1 15 my $self = shift;
283 6         31 return $self->{_rec_tot};
284             }
285              
286             ####################################
287             # get/set curr.record ptr #
288             # in: $recno #
289             # neg.values count from end #
290             # out: $scalar (curr.record) #
291             # undef if error #
292             ####################################
293             sub recno {
294              
295 32     32 1 64 my $self = shift;
296 32 100       91 if (@_) {
297 16         30 my $rec = shift;
298 16 100       59 $rec += $self->{_rec_tot} if ($rec < 0);
299 16         32 eval {
300 16 100       59 $rec >= 0 || die "requested record is below BOF\n";
301 12 100       77 $rec < $self->{_rec_tot} || die "requested record is beyond EOF\n";
302 8 100       37 $self->reset if ($rec < $self->{_rec_cur});
303 8         34 while ($rec > $self->{_rec_cur}) { _read_next($self) }
  39814         77924  
304             #while ($rec > $self->{_rec_cur}) { (my $d = _read_next($self)) || return undef; undef $d }
305             };
306 16 100       72 if ($@) {
307 8         22 chomp $@;
308 8         24 $self->{_err} = $@;
309             #Carp::carp "Unable to set recno: $@";
310 8         41 return undef;
311             }
312             }
313 24         144 return $self->{_rec_cur};
314             }
315              
316             ##################################
317             # reset records counter #
318             # in: - #
319             # out: $scalar (curr.record=0) #
320             # undef if error #
321             ##################################
322             sub reset {
323            
324 8     8 1 21 my $self = shift;
325              
326 8         17 my $fh = $self->{_fh};
327 8         14 eval {
328 8 100       51 $self->{_data_start} > 0 || die "cannot reset on an empty DB\n";
329 6 100       38 $fh = _open_db_segment($self->{_db_fpath}.$self->{_db_fname}, 0) if ($self->{_seg_cur} != 0);
330 6 50 50     94 (seek($fh, $self->{_data_start}, SEEK_SET) || die "seek error ($!)\n") if ($self->{_data_start} > 0);
331             };
332 8 100       32 if ($@) {
333 2         8 chomp $@;
334 2         8 $self->{_err} = $@;
335             #Carp::carp "Unable to reset recno: $@";
336 2         10 return undef;
337             }
338 6 100       145 close $self->{_fh} if ($fh != $self->{_fh});
339 6         14 $self->{_fh} = $fh;
340 6 50       29 $self->{_rec_cur} = ($self->{_data_start} > 0) ? 0 : -1;
341 6         13 $self->{_seg_cur} = 0;
342 6         15 $self->{_data_cur} = $self->{_data_start};
343 6         28 return 0;
344             }
345              
346             ################################
347             # get next record #
348             # in: - #
349             # out: $record_objref #
350             # undef if error #
351             ################################
352             sub next {
353              
354 19920     19920 1 24018 my $self = shift;
355              
356 19920         21953 my $rec_ref;
357 19920         24277 eval {
358 19920 100       56857 $self->{_data_start} > 0 || die "cannot next on an empty DB\n";
359 19918         35752 my $data_ref = _read_next($self);
360 19916         49332 $rec_ref = VisionDB::Read::record->new($data_ref);
361 19916         34683 undef $data_ref;
362             };
363 19920 100       57816 if ($@) {
364 4         15 chomp $@;
365 4         12 $self->{_err} = $@;
366             #Carp::carp "Unable to read next record: $@";
367 4         20 return undef;
368             }
369 19916         44327 return $rec_ref;
370             }
371              
372             ###############################
373             # get DB base name #
374             # in: - #
375             # out: $scalar (base name) #
376             ###############################
377             sub filename {
378              
379 2     2 1 877 my $self = shift;
380 2         25 return $self->{_db_fname};
381             }
382              
383             ###############################
384             # get version number #
385             # in: - #
386             # out: $scalar (version) #
387             ###############################
388             sub version {
389              
390 20     20 1 5988 my $self = shift;
391 20         148 return $self->{_version};
392             }
393              
394             ###############################
395             # get error number #
396             # in: - #
397             # out: $scalar (error num.) #
398             ###############################
399             sub error {
400              
401 12     12 1 25 my $self = shift;
402 12         64 return $self->{_err};
403             }
404              
405             ################################
406             # get fields structure #
407             # in: - #
408             # out: $array (fields array) #
409             ################################
410             sub fields {
411              
412 4     4 1 7 my $self = shift;
413              
414 4         8 my @f = ();
415 4         6 foreach my $key (@{$self->{_data_keys}}) { # each key
  4         12  
416 8         15 foreach my $seg (@$key) { # each segment
417 12         29 my ($len, $off) = @$seg;
418 12 100       39 push(@f, $off) if (!exists { map { $_ => 1 } @f }->{$off});
  30         104  
419 12 100       43 push(@f, $off+$len) if (!exists { map { $_ => 1 } @f }->{($off+$len)});
  38         190  
420             }
421             }
422 4         47 return sort {$a <=> $b} @f;
  28         79  
423             }
424              
425             #############################################
426             # sub-package VisionDB::Read::record #
427             # #
428             # just a data container... #
429             #############################################
430              
431             package VisionDB::Read::record;
432              
433 9     9   80 use strict;
  9         20  
  9         2373  
434              
435             our (@ISA, @EXPORT, @EXPORT_OK);
436              
437             #################
438             ## constructor ##
439             #################
440             sub new {
441              
442 19916     19916   28634 my ($class, $dataref) = @_;
443 19916         65137 my $self = {
444             _data => $dataref->[0],
445             _len => $dataref->[1],
446             };
447 19916         45417 bless($self, $class);
448 19916         38457 return $self;
449             }
450              
451             ################
452             ## destructor ##
453             ################
454             sub DESTROY {
455              
456 19916     19916   77112 my $self = shift;
457              
458 19916         29916 undef $self->{_data};
459 19916         71457 undef $self->{_len};
460             }
461              
462             ###############################
463             # Free object, discard all #
464             # in: - #
465             # out: - #
466             ###############################
467             sub dispose {
468              
469 19910     19910   25976 my $self = shift;
470            
471 19910         30578 undef $self->{_data};
472 19910         44365 undef $self->{_len};
473             }
474              
475             ###############################
476             # Get data ref #
477             # in: - #
478             # out: $data_ref #
479             ###############################
480             sub data {
481              
482 6     6   10 my $self = shift;
483              
484 6         33 return $self->{_data};
485             }
486            
487             ###############################
488             # Get data len #
489             # in: - #
490             # out: $data_len #
491             ###############################
492             sub size {
493              
494 0     0     my $self = shift;
495              
496 0           return $self->{_len};
497             }
498              
499             1;
500             __END__