File Coverage

blib/lib/FlatFile/DataStore/Toc.pm
Criterion Covered Total %
statement 168 171 98.2
branch 63 70 90.0
condition 2 3 66.6
subroutine 26 26 100.0
pod 2 20 10.0
total 261 290 90.0


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package FlatFile::DataStore::Toc;
3             #---------------------------------------------------------------------
4              
5             =head1 NAME
6              
7             FlatFile::DataStore::Toc - Perl module that implements a flatfile
8             datastore TOC (table of contents) class.
9              
10             =head1 SYNOPSYS
11              
12             use FlatFile::DataStore::Toc;
13             my $toc;
14              
15             $toc = FlatFile::DataStore::Toc->new(
16             { int => 10,
17             datastore => $datastore_obj
18             } );
19              
20             # or
21              
22             $toc = FlatFile::DataStore::Toc->new(
23             { num => "A", # same as int=>10
24             datastore => $datastore_obj
25             } );
26              
27             =head1 DESCRIPTION
28              
29             FlatFile::DataStore::Toc is a Perl module that implements a flatfile
30             datastore TOC (table of contents) class.
31              
32             This module is used by FlatFile::DataStore. You will likely never call
33             any of it's methods yourself.
34              
35             =head1 VERSION
36              
37             FlatFile::DataStore::Toc version 1.03
38              
39             =cut
40              
41             our $VERSION = '1.03';
42              
43 23     23   34445 use 5.008003;
  23         81  
  23         1251  
44 23     23   131 use strict;
  23         44  
  23         704  
45 23     23   126 use warnings;
  23         41  
  23         765  
46              
47 23     23   129 use File::Path;
  23         44  
  23         1745  
48 23     23   131 use Carp;
  23         48  
  23         1672  
49              
50 23     23   930 use Math::Int2Base qw( base_chars int2base base2int );
  23         785  
  23         63396  
51              
52             my %Attrs = qw(
53             datastore 1
54             datafnum 1
55             keyfnum 1
56             tocfnum 1
57             numrecs 1
58             keynum 1
59             transnum 1
60             create 1
61             oldupd 1
62             update 1
63             olddel 1
64             delete 1
65             );
66              
67             #---------------------------------------------------------------------
68              
69             =head1 CLASS METHODS
70              
71             =head2 FlatFile::DataStore::Toc->new( $parms )
72              
73             Constructs a new FlatFile::DataStore::Toc object from a toc record
74             string in a tocfile.
75              
76             The parm C<$parms> is a hash reference containing these required keys:
77              
78             - datastore ... data store object, and one of:
79             - int ... data file number as integer, or
80             - num ... data file number as number in number base
81              
82             An C or C of 0 will load the first (totals) line from the
83             tocfile.
84              
85             =cut
86              
87             sub new {
88 293     293 1 14168 my( $class, $parms ) = @_;
89              
90 293         940 my $self = bless {}, $class;
91              
92 293 50       1467 $self->init( $parms ) if $parms;
93 289         1032 return $self;
94             }
95              
96              
97             #---------------------------------------------------------------------
98             # init(), called by new() to parse the parms
99             #
100             # Private method.
101              
102             sub init {
103 293     293 0 579 my( $self, $parms ) = @_;
104              
105 293   66     1099 my $ds = $parms->{'datastore'} || croak qq/Missing: datastore/;
106 292         750 $self->datastore( $ds );
107              
108 292         333 my $datafint;
109 292 100       1370 if( defined( my $int = $parms->{'int'} ) ) {
    100          
110 205         576 $datafint = $int;
111             }
112             elsif( defined( my $num = $parms->{'num'} ) ) {
113 86         297 $datafint = base2int $num, $ds->fnumbase;
114             }
115             else {
116 1         139 croak qq/Missing: int or num/;
117             }
118              
119 291         4461 my $string = $self->read_toc( $datafint );
120              
121 289 100       767 unless( $string ) {
122 69         210 $self->datafnum( $datafint );
123 69         163 $self->tocfnum( $self->toc_getfnum( $datafint ) );
124 69 100       303 $self->keynum( $datafint == 0? -1: 0 );
125             $self->$_( 0 )
126 69         269 for qw( keyfnum numrecs transnum create oldupd update olddel delete );
127 69         168 return $self;
128             }
129              
130 220         647 my $fnumbase = $ds->fnumbase;
131 220         680 my $keybase = $ds->keybase;
132 220         638 my $transbase = $ds->transbase;
133              
134 220         598 my $recsep = $ds->recsep;
135 220         1702 $string =~ s/\Q$recsep\E$//; # chompish
136 220         582 $self->string( $string );
137              
138 220         1580 my @fields = split " ", $string;
139 220         408 my $i = 0;
140             $self->$_( base2int $fields[ $i++ ], $fnumbase )
141 220         1182 for qw( datafnum keyfnum tocfnum );
142             $self->$_( base2int $fields[ $i++ ], $keybase )
143 220         847 for qw( numrecs keynum );
144             $self->$_( base2int $fields[ $i++ ], $transbase )
145 220         1134 for qw( transnum create oldupd update olddel delete );
146              
147 220         838 return $self;
148             }
149              
150             #---------------------------------------------------------------------
151              
152             =head1 OBJECT METHODS
153              
154             =head2 to_string()
155              
156             Returns the toc object as a string, appropriate for writing back to
157             the tocfile.
158              
159             =cut
160              
161             #---------------------------------------------------------------------
162             sub to_string {
163 175     175 1 801 my( $self ) = @_;
164              
165 175 100       403 return unless $self->keynum > -1; # empty data store
166              
167 174         373 my $ds = $self->datastore;
168              
169 174         587 my $fnumbase = $ds->fnumbase;
170 174         501 my $fnumlen = $ds->fnumlen;
171 174         550 my $keybase = $ds->keybase;
172 174         511 my $keylen = $ds->keylen;
173 174         486 my $transbase = $ds->transbase;
174 174         481 my $translen = $ds->translen;
175              
176 174         243 my @fields;
177             push @fields, int2base $self->$_(), $fnumbase, $fnumlen
178 174         625 for qw( datafnum keyfnum tocfnum );
179             push @fields, int2base $self->$_(), $keybase, $keylen
180 174         2580 for qw( numrecs keynum );
181             push @fields, int2base $self->$_(), $transbase, $translen
182 174         2984 for qw( transnum create oldupd update olddel delete );
183              
184 174         2718 return join( " " => @fields ) . $ds->recsep;
185             }
186              
187             #---------------------------------------------------------------------
188             # read_toc()
189             # Takes an integer which denotes which datafile we want a toc
190             # record for. It reads the appropriate line from a tocfile and
191             # returns the record as a string.
192             #
193             # Private method.
194              
195             # Case study illustrating the logic in the routine.
196             #
197             # seekpos if there's a tocmax, e.g., tocmax=3, fint=7, toclen=4
198             #
199             # 1: 0 xxxx skip = int( fint / tocmax )
200             # 1 xxxx = int( 7 / 3 )
201             # 2 xxxx = 2 (files to skip)
202             # 2: 3 xxxx seekpos = toclen * ( fint - ( skip * tocmax ) )
203             # 4 xxxx = 4 * ( 7 - ( 2 * 3 ) )
204             # 5 xxxx = 4 * ( 7 - 6 )
205             # 3: 6 xxxx = 4 * 1
206             # 7 =>xxxx = 4
207             # 8 xxxx '=>' marks seekpos 4 in file 3
208            
209             sub read_toc {
210 291     291 0 548 my( $self, $fint ) = @_;
211              
212 291         569 my $ds = $self->datastore;
213              
214 291         778 my $tocfile = $self->tocfile( $fint );
215 289 100       5951 return unless -e $tocfile;
216              
217             # look in tocs cache
218             # XXX is there a race condition between -M and locked_for_read?
219 220 100       988 if( my $tocs = $ds->tocs->{ $tocfile } ) {
220 219 50       725 if( -M _ <= $tocs->{'-M'} ) { # unchanged
221 219         551 for( $tocs->{ $fint } ) {
222 219 100       822 return $_ if defined;
223             }
224             }
225             }
226              
227 2         9 my $tocfh = $ds->locked_for_read( $tocfile );
228 2         10 my $toclen = $ds->toclen;
229              
230 2         3 my $seekpos;
231 2 50       8 if( my $tocmax = $ds->tocmax ) {
232 0         0 my $skip = int( $fint / $tocmax );
233 0         0 $seekpos = $toclen * ( $fint - ( $skip * $tocmax ) ); }
234             else {
235 2         5 $seekpos = $toclen * $fint; }
236              
237 2         12 my $tocline = $ds->read_bytes( $tocfh, $seekpos, $toclen );
238 2 50       34 close $tocfh or croak qq/Can't close $tocfile: $!/;
239              
240             # write to tocs cache
241 2         34 $ds->tocs->{ $tocfile }{'-M'} = -M $tocfile;
242 2         8 $ds->tocs->{ $tocfile }{ $fint } = $$tocline;
243              
244 2         11 $$tocline; # returned
245             }
246              
247             #---------------------------------------------------------------------
248             # write_toc()
249             # Takes an integer which denotes which datafile we want a toc
250             # record for. opens the appropriate tocfile, seeks to the
251             # appropriate line and writes the Toc object as a string.
252             # Uses logic similar to read_toc().
253             #
254             # Private method.
255              
256             sub write_toc {
257 168     168 0 285 my( $self, $fint ) = @_;
258              
259 168         350 my $ds = $self->datastore;
260              
261 168         409 my $tocfile = $self->tocfile( $fint );
262 168         568 my $tocfh = $ds->locked_for_write( $tocfile );
263 168         619 my $toclen = $ds->toclen;
264              
265 168         231 my $seekpos;
266 168 100       459 if( my $tocmax = $ds->tocmax ) {
267 12         24 my $skip = int( $fint / $tocmax );
268 12         24 $seekpos = $toclen * ( $fint - ( $skip * $tocmax ) ); }
269             else {
270 156         290 $seekpos = $toclen * $fint; }
271              
272 168         440 my $tocline = $self->to_string;
273              
274 168         693 $ds->write_bytes( $tocfh, $seekpos, \$tocline );
275 168 50       2605 close $tocfh or croak qq/Can't close $tocfile: $!/;
276              
277             # write to tocs cache
278 168         2774 $ds->tocs->{ $tocfile }{'-M'} = -M $tocfile;
279 168         608 $ds->tocs->{ $tocfile }{ $fint } = $tocline;
280             }
281              
282             #---------------------------------------------------------------------
283             # toc_getfnum(), called by tocfile() and init()
284             # Takes an integer which denotes which datafile we want a toc
285             # record for. Calculates the tocfile file number where that
286             # record should be found and returns the file number as an
287             # integer. In list context, returns both the integer and the
288             # number in the C.
289             #
290             # Private method.
291              
292             sub toc_getfnum {
293 528     528 0 770 my( $self, $fint ) = @_;
294              
295 528         1445 my $ds = $self->datastore;
296              
297             # get toc file number based on tocmax and fint
298 528         647 my $tocfint;
299              
300 528         1787 my $tocmax = $ds->tocmax;
301 528 100       1100 if( $tocmax ) { $tocfint = int( $fint / $tocmax ) + 1 }
  34         70  
302 494         702 else { $tocfint = 1 }
303              
304 528         1438 my $fnumlen = $ds->fnumlen;
305 528         1477 my $fnumbase = $ds->fnumbase;
306 528         1573 my $tocfnum = int2base $tocfint, $fnumbase, $fnumlen;
307              
308 528 100       16283 croak qq/Database exceeds configured size, tocfnum too long: $tocfnum/
309             if length $tocfnum > $fnumlen;
310              
311 526 50       2391 return( $tocfint, $tocfnum ) if wantarray;
312 0         0 return $tocfint;
313             }
314              
315             #---------------------------------------------------------------------
316             # tocfile()
317             # Takes an integer which denotes which datafile we want a toc
318             # record for. Returns the path of the tocfile where that record
319             # should be found.
320             #
321             # Private method.
322              
323             sub tocfile {
324 459     459 0 657 my( $self, $fint ) = @_;
325              
326 459         1002 my $ds = $self->datastore;
327              
328 459         1443 my $name = $ds->name;
329              
330 459         1217 my( $tocfint, $tocfnum ) = $self->toc_getfnum( $fint );
331 457 100       1399 my $tocfile = $name . ( $ds->tocmax? ".$tocfnum": "") . ".toc";
332              
333             # get toc path based on dirlev, dirmax, and toc file number
334 457 100       1348 if( my $dirlev = $ds->dirlev ) {
335 56         144 my $fnumlen = $ds->fnumlen;
336 56         144 my $fnumbase = $ds->fnumbase;
337 56         152 my $dirmax = $ds->dirmax;
338 56         90 my $path = "";
339 56         76 my $this = $tocfint;
340 56         108 for( 1 .. $dirlev ) {
341 84 50       203 my $dirint = $dirmax? (int( ( $this - 1 ) / $dirmax ) + 1): 1;
342 84         260 my $dirnum = int2base $dirint, $fnumbase, $fnumlen;
343 84 100       1078 $path = $path? "$dirnum/$path": $dirnum;
344 84         202 $this = $dirint;
345             }
346 56         176 $path = $ds->dir . "/$name/toc$path";
347 56 100       1793 mkpath( $path ) unless -d $path;
348 56         140 $tocfile = "$path/$tocfile";
349             }
350             else {
351 401         1156 $tocfile = $ds->dir . "/$tocfile";
352             }
353              
354 457         1234 return $tocfile;
355             }
356              
357             #---------------------------------------------------------------------
358              
359             =head1 OBJECT METHODS: Accessors
360              
361             The following read/write methods set and return their respective
362             attribute values if C<$value> is given. Otherwise, they just return
363             the value.
364              
365             $record->datastore( [$value] )
366             $record->string( [$value] )
367              
368             The following methods expect an integer parm and return an integer
369             value (even though these are stored in the tocfile as numbers in their
370             respective bases).
371              
372             $record->datafnum( [$value] )
373             $record->keyfnum( [$value] )
374             $record->tocfnum( [$value] )
375             $record->numrecs( [$value] )
376             $record->keynum( [$value] )
377             $record->transnum( [$value] )
378             $record->create( [$value] )
379             $record->oldupd( [$value] )
380             $record->update( [$value] )
381             $record->olddel( [$value] )
382             $record->delete( [$value] )
383              
384             =cut
385              
386 1913 100   1913 0 4790 sub datastore {for($_[0]->{datastore} ){$_=$_[1]if@_>1;return$_}}
  1913         3900  
  1913         3495  
387 224 100   224 0 578 sub string {for($_[0]->{string} ){$_=$_[1]if@_>1;return$_}}
  224         621  
  224         408  
388              
389 814 100   814 0 8935 sub datafnum {for($_[0]->{datafnum} ){$_=$_[1]if@_>1;return$_}}
  814         2052  
  814         3141  
390 704 100   704 0 8206 sub keyfnum {for($_[0]->{keyfnum} ){$_=$_[1]if@_>1;return$_}}
  704         1571  
  704         2178  
391 635 100   635 0 7284 sub tocfnum {for($_[0]->{tocfnum} ){$_=$_[1]if@_>1;return$_}}
  635         1511  
  635         1631  
392 804 100   804 0 8733 sub numrecs {for($_[0]->{numrecs} ){$_=$_[1]if@_>1;return$_}}
  804         1809  
  804         8724  
393 1056 100   1056 0 9271 sub keynum {for($_[0]->{keynum} ){$_=$_[1]if@_>1;return$_}}
  1056         2375  
  1056         2670  
394 806 100   806 0 6098 sub transnum {for($_[0]->{transnum} ){$_=$_[1]if@_>1;return$_}}
  806         1863  
  806         2246  
395 743 100   743 0 7758 sub create {for($_[0]->{create} ){$_=$_[1]if@_>1;return$_}}
  743         1624  
  743         10166  
396 495 100   495 0 6461 sub oldupd {for($_[0]->{oldupd} ){$_=$_[1]if@_>1;return$_}}
  495         1198  
  495         1503  
397 495 100   495 0 6193 sub update {for($_[0]->{update} ){$_=$_[1]if@_>1;return$_}}
  495         1342  
  495         1549  
398 499 100   499 0 6525 sub olddel {for($_[0]->{olddel} ){$_=$_[1]if@_>1;return$_}}
  499         1137  
  499         9859  
399 499 100   499 0 12856 sub delete {for($_[0]->{delete} ){$_=$_[1]if@_>1;return$_}}
  499         1223  
  499         1351  
400              
401             __END__