| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package AnyData::Storage::File; | 
| 2 | 5 |  |  | 5 |  | 20 | use strict; | 
|  | 5 |  |  |  |  | 6 |  | 
|  | 5 |  |  |  |  | 122 |  | 
| 3 | 5 |  |  | 5 |  | 17 | use warnings; | 
|  | 5 |  |  |  |  | 5 |  | 
|  | 5 |  |  |  |  | 116 |  | 
| 4 | 5 |  |  | 5 |  | 2395 | use IO::File; | 
|  | 5 |  |  |  |  | 39072 |  | 
|  | 5 |  |  |  |  | 660 |  | 
| 5 | 5 |  |  | 5 |  | 33 | use Fcntl qw(:flock); | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 670 |  | 
| 6 | 5 |  |  | 5 |  | 27 | use File::Basename; | 
|  | 5 |  |  |  |  | 8 |  | 
|  | 5 |  |  |  |  | 370 |  | 
| 7 | 5 |  |  | 5 |  | 28 | use constant HAS_FLOCK => eval { flock STDOUT, 0; 1 }; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 19 |  | 
|  | 5 |  |  |  |  | 379 |  | 
| 8 | 5 |  |  | 5 |  | 22 | use constant HAS_FILE_SPEC => eval { require File::Spec }; | 
|  | 5 |  |  |  |  | 7 |  | 
|  | 5 |  |  |  |  | 6 |  | 
|  | 5 |  |  |  |  | 248 |  | 
| 9 | 5 |  |  | 5 |  | 19 | use vars qw($DEBUG); | 
|  | 5 |  |  |  |  | 6 |  | 
|  | 5 |  |  |  |  | 9571 |  | 
| 10 |  |  |  |  |  |  | $DEBUG = 0; | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | sub new { | 
| 14 | 4 |  |  | 4 | 0 | 9 | my $class = shift; | 
| 15 | 4 |  | 100 |  |  | 13 | my $self  = shift || {}; | 
| 16 |  |  |  |  |  |  | #$self->{f_dir} ||= './'; | 
| 17 | 4 |  |  |  |  | 14 | return bless $self, $class; | 
| 18 |  |  |  |  |  |  | } | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | sub seek_first_record { | 
| 21 | 8 |  |  | 8 | 0 | 7 | my $self = shift; | 
| 22 | 8 |  |  |  |  | 8 | my $fh   = $self->{fh}; | 
| 23 | 8 |  |  |  |  | 8 | my $start = $self->{first_row_pos}; | 
| 24 | 8 | 100 | 50 |  |  | 26 | $start | 
|  |  |  | 50 |  |  |  |  | 
| 25 |  |  |  |  |  |  | ? $fh->seek($start,0) || die $! | 
| 26 |  |  |  |  |  |  | : $fh->seek(0,0) || die $!; | 
| 27 |  |  |  |  |  |  | } | 
| 28 | 0 |  |  | 0 | 0 | 0 | sub get_pos { return shift->{fh}->tell } | 
| 29 | 0 |  |  | 0 | 0 | 0 | sub go_pos  { my($s,$pos)=@_; $s->{fh}->seek($pos,0); } | 
|  | 0 |  |  |  |  | 0 |  | 
| 30 |  |  |  |  |  |  | my $open_table_re = | 
| 31 |  |  |  |  |  |  | HAS_FILE_SPEC ? | 
| 32 |  |  |  |  |  |  | sprintf('(?:%s|%s|%s)', | 
| 33 |  |  |  |  |  |  | quotemeta(File::Spec->curdir()), | 
| 34 |  |  |  |  |  |  | quotemeta(File::Spec->updir()), | 
| 35 |  |  |  |  |  |  | quotemeta(File::Spec->rootdir())) | 
| 36 |  |  |  |  |  |  | : '(?:\.?\.)?\/'; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | sub open_local_file { | 
| 40 | 3 |  |  | 3 | 0 | 6 | my( $self,$file, $open_mode ) = @_; | 
| 41 | 3 |  | 50 |  |  | 20 | my $dir = $self->{f_dir} || './'; | 
| 42 | 3 |  |  |  |  | 89 | my($fname,$path) = fileparse($file); | 
| 43 | 3 |  |  |  |  | 30 | my($foo2,$os_cur_dir) = fileparse(''); | 
| 44 | 3 | 50 | 33 |  |  | 22 | my $haspath = 1 if $path and $path ne $os_cur_dir; | 
| 45 | 3 | 0 | 33 |  |  | 22 | if (!$haspath && $file !~ /^$open_table_re/o) { | 
| 46 | 0 |  |  |  |  | 0 | $file = HAS_FILE_SPEC | 
| 47 |  |  |  |  |  |  | ? File::Spec->catfile($dir, $file) | 
| 48 |  |  |  |  |  |  | : $dir . "/$file"; | 
| 49 |  |  |  |  |  |  | } | 
| 50 | 3 |  |  |  |  | 4 | my $fh; | 
| 51 | 3 |  | 50 |  |  | 7 | $open_mode ||= 'r'; | 
| 52 | 3 |  |  |  |  | 19 | my %valid_mode = ( | 
| 53 |  |  |  |  |  |  | r  => q/read       read an existing file, fail if already exists/, | 
| 54 |  |  |  |  |  |  | u  => q/update     read & modify an existing file, fail if already exists/, | 
| 55 |  |  |  |  |  |  | c  => q/create     create a new file, fail if it already exists/, | 
| 56 |  |  |  |  |  |  | o  => q/overwrite  create a new file, overwrite if it already exists/, | 
| 57 |  |  |  |  |  |  | ); | 
| 58 | 3 |  |  |  |  | 8 | my %mode = ( | 
| 59 |  |  |  |  |  |  | r   => O_RDONLY, | 
| 60 |  |  |  |  |  |  | u   => O_RDWR, | 
| 61 |  |  |  |  |  |  | c   => O_CREAT | O_RDWR | O_EXCL, | 
| 62 |  |  |  |  |  |  | o   => O_CREAT | O_RDWR | O_TRUNC | 
| 63 |  |  |  |  |  |  | ); | 
| 64 | 3 |  |  |  |  | 3 | my $help = qq( | 
| 65 |  |  |  |  |  |  | r  if file exists, get shared lock | 
| 66 |  |  |  |  |  |  | u  if file exists, get exclusive lock | 
| 67 |  |  |  |  |  |  | c  if file doesn't exist, get exclusive lock | 
| 68 |  |  |  |  |  |  | o  truncate if file exists, else create; get exclusive lock | 
| 69 |  |  |  |  |  |  | ); | 
| 70 | 3 | 50 |  |  |  | 9 | if ( !$valid_mode{$open_mode} ) { | 
| 71 | 0 |  |  |  |  | 0 | print "\nBad open_mode '$open_mode'\nValid modes are :\n"; | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 0 |  |  |  |  | 0 | for ('r','u','c','o'){ | 
| 74 | 0 |  |  |  |  | 0 | print "   $_ = $valid_mode{$_}\n"; | 
| 75 |  |  |  |  |  |  | } | 
| 76 | 0 |  |  |  |  | 0 | exit; | 
| 77 |  |  |  |  |  |  | } | 
| 78 | 3 | 50 |  |  |  | 7 | if ($open_mode eq 'c') { | 
| 79 | 0 | 0 |  |  |  | 0 | if (-f $file) { | 
| 80 | 0 |  |  |  |  | 0 | die "Cannot create '$file': Already exists"; | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  | } | 
| 83 | 3 | 100 |  |  |  | 12 | if ($open_mode =~ /[co]/ ) { | 
| 84 | 1 | 50 |  |  |  | 12 | if (!($fh = IO::File->new( $file, $mode{$open_mode} ))) { | 
| 85 | 0 |  |  |  |  | 0 | die "Cannot open '$file': $!"; | 
| 86 |  |  |  |  |  |  | } | 
| 87 | 1 | 50 |  |  |  | 189 | if (!$fh->seek(0, 0)) { | 
| 88 | 0 |  |  |  |  | 0 | die " Error while seeking back: $!"; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  | } | 
| 91 | 3 | 100 |  |  |  | 20 | if ($open_mode =~ /[ru]/) { | 
| 92 | 2 | 50 |  |  |  | 63 | die "Cannot read file '$file': doesn't exist!" unless -f $file; | 
| 93 | 2 | 50 |  |  |  | 8 | if (!($fh = IO::File->new($file, $mode{$open_mode}))) { | 
| 94 | 0 |  |  |  |  | 0 | die " Cannot open '$file': $!"; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  | } | 
| 97 | 3 |  |  |  |  | 141 | binmode($fh); | 
| 98 | 3 |  |  |  |  | 21 | $fh->autoflush(1); | 
| 99 | 3 |  |  |  |  | 108 | if ( HAS_FLOCK ) { | 
| 100 | 3 | 100 |  |  |  | 8 | if ( $open_mode eq 'r') { | 
| 101 | 2 | 50 |  |  |  | 15 | if (!flock($fh, LOCK_SH)) { | 
| 102 | 0 |  |  |  |  | 0 | die "Cannot obtain shared lock on '$file': $!"; | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  | } else { | 
| 105 | 1 | 50 |  |  |  | 8 | if (!flock($fh, LOCK_EX)) { | 
| 106 | 0 |  |  |  |  | 0 | die " Cannot obtain exclusive lock on '$file': $!"; | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  | } | 
| 110 | 3 | 50 |  |  |  | 7 | print "OPENING $file, mode = '$open_mode'\n" if $DEBUG; | 
| 111 | 3 | 100 |  |  |  | 46 | return( $file, $fh, $open_mode) if wantarray; | 
| 112 | 1 |  |  |  |  | 4 | return( $fh ); | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | sub print_col_names { | 
| 116 | 0 |  |  | 0 | 0 | 0 | my($self,$parser,$col_names) = @_; | 
| 117 | 0 |  | 0 |  |  | 0 | my $fields = $col_names || $self->{col_names} || $parser->{col_names}; | 
| 118 | 0 | 0 |  |  |  | 0 | return undef unless scalar @$fields; | 
| 119 | 0 |  |  |  |  | 0 | $self->{col_names} = $fields; | 
| 120 | 0 | 0 |  |  |  | 0 | return $fields if $parser->{keep_first_line}; | 
| 121 | 0 |  |  |  |  | 0 | my $first_line = $self->get_record(); | 
| 122 | 0 |  |  |  |  | 0 | my $fh         = $self->{fh}; | 
| 123 | 0 |  |  |  |  | 0 | $self->seek_first_record; | 
| 124 |  |  |  |  |  |  |  | 
| 125 | 0 |  | 0 |  |  | 0 | my $end = $parser->{record_sep} || "\n"; | 
| 126 | 0 |  |  |  |  | 0 | my $colStr =  $parser->write_fields(@$fields); | 
| 127 | 0 | 0 |  |  |  | 0 | $colStr = join( ',',@$fields) . $end if ref($parser) =~ /Fixed/; | 
| 128 | 0 |  |  |  |  | 0 | $fh->write($colStr,length $colStr); | 
| 129 | 0 |  |  |  |  | 0 | $self->{first_row_pos} = $fh->tell(); | 
| 130 |  |  |  |  |  |  | } | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | sub get_col_names { | 
| 133 | 2 |  |  | 2 | 0 | 2 | my($self,$parser) = @_; | 
| 134 | 2 |  |  |  |  | 3 | my @fields = (); | 
| 135 | 2 | 100 |  |  |  | 4 | if ($parser->{keep_first_line}) { | 
| 136 | 1 |  |  |  |  | 2 | my $cols = $parser->{col_names}; | 
| 137 | 1 | 50 |  |  |  | 2 | return undef unless $cols; | 
| 138 | 1 | 50 |  |  |  | 4 | return $cols if ref $cols eq 'ARRAY'; | 
| 139 | 0 |  |  |  |  | 0 | @fields = split ',',$cols; | 
| 140 |  |  |  |  |  |  | #die "@fields"; | 
| 141 |  |  |  |  |  |  | return scalar @fields | 
| 142 | 0 | 0 |  |  |  | 0 | ? \@fields | 
| 143 |  |  |  |  |  |  | : undef; | 
| 144 |  |  |  |  |  |  | } | 
| 145 | 1 |  |  |  |  | 1 | my $fh         = $self->{fh}; | 
| 146 | 1 | 50 |  |  |  | 6 | $fh->seek(0,0) if $fh; | 
| 147 | 1 |  |  |  |  | 5 | my $first_line = $self->get_record($parser); | 
| 148 |  |  |  |  |  |  | #print $first_line; | 
| 149 | 1 | 50 |  |  |  | 3 | if ( $first_line ) { | 
| 150 | 1 | 50 |  |  |  | 9 | @fields = ref($parser) =~ /Fixed/ | 
| 151 |  |  |  |  |  |  | ? split /,/,$first_line | 
| 152 |  |  |  |  |  |  | : $parser->read_fields($first_line); | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  | #    my @fields = $first_line | 
| 155 |  |  |  |  |  |  | #         ? $parser->read_fields($first_line) | 
| 156 |  |  |  |  |  |  | #        : (); | 
| 157 |  |  |  |  |  |  | #print "<$_>" for @fields; print "\n"; | 
| 158 |  |  |  |  |  |  | return "CAN'T FIND COLUMN NAMES ON FIRST LINE OF '" | 
| 159 |  |  |  |  |  |  | . $self->{file} | 
| 160 | 1 | 50 |  |  |  | 5 | . "' : '@fields'" if "@fields" =~ /[^ a-zA-Z0-9_]/; | 
| 161 | 1 |  |  |  |  | 2 | $parser->{col_names}   = \@fields; | 
| 162 | 1 |  |  |  |  | 2 | $self->{col_names}     = \@fields; | 
| 163 | 1 |  |  |  |  | 2 | $self->{col_nums}      = $self->set_col_nums; | 
| 164 | 1 |  |  |  |  | 3 | $self->{first_row_pos} = $fh->tell(); | 
| 165 | 1 |  |  |  |  | 4 | return( \@fields); | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  | sub open_table { | 
| 168 | 2 |  |  | 2 | 0 | 4 | my( $self, $parser, $file, $open_mode ) = @_; | 
| 169 | 2 |  |  |  |  | 3 | my($newfile, $fh); | 
| 170 | 2 |  | 50 |  |  | 5 | $file ||= ''; | 
| 171 | 2 | 50 |  |  |  | 12 | if ( $file =~ m'http://|ftp://' ) { | 
| 172 |  |  |  |  |  |  | #       die "wrong storage!"; | 
| 173 | 0 |  |  |  |  | 0 | $newfile = $file; | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | else { | 
| 176 | 2 | 50 | 33 |  |  | 12 | ($newfile,$fh) = | 
| 177 |  |  |  |  |  |  | $self->open_local_file($file,$open_mode) if $file && !(ref $file); | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | } | 
| 180 | 2 |  | 33 |  |  | 5 | $newfile ||= $file; | 
| 181 |  |  |  |  |  |  | #die AnyData::dump($parser); | 
| 182 | 2 |  | 100 |  |  | 8 | my $col_names = $parser->{col_names}  || ''; | 
| 183 |  |  |  |  |  |  | #    my @array = split(/,/,$col_names); | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 2 |  |  |  |  | 1 | my @array; | 
| 186 | 2 | 100 |  |  |  | 13 | @array = ref $col_names eq 'ARRAY' | 
| 187 |  |  |  |  |  |  | ? @$col_names | 
| 188 |  |  |  |  |  |  | : split ',',$col_names; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 2 | 50 |  |  |  | 15 | my $pos = $fh->tell() if $fh; | 
| 191 | 2 |  |  |  |  | 19 | my %table = ( | 
| 192 |  |  |  |  |  |  | file => $newfile, | 
| 193 |  |  |  |  |  |  | open_mode => $open_mode, | 
| 194 |  |  |  |  |  |  | fh => $fh, | 
| 195 |  |  |  |  |  |  | col_nums => {}, | 
| 196 |  |  |  |  |  |  | col_names => \@array, | 
| 197 |  |  |  |  |  |  | first_row_pos => $pos | 
| 198 |  |  |  |  |  |  | ); | 
| 199 | 2 |  |  |  |  | 5 | for my $key(keys %table) { | 
| 200 | 12 |  |  |  |  | 20 | $self->{$key}=$table{$key}; | 
| 201 |  |  |  |  |  |  | } | 
| 202 | 2 |  |  |  |  | 18 | my $skip = $parser->init_parser($self); | 
| 203 | 2 | 50 | 33 |  |  | 11 | if (!$skip && defined $newfile) { | 
| 204 | 2 | 50 |  |  |  | 10 | $open_mode =~ /[co]/ | 
| 205 |  |  |  |  |  |  | ? $self->print_col_names($parser) | 
| 206 |  |  |  |  |  |  | : $self->get_col_names($parser); | 
| 207 |  |  |  |  |  |  | } | 
| 208 | 2 |  |  |  |  | 4 | $self->{col_nums} = $self->set_col_nums(); | 
| 209 |  |  |  |  |  |  | # use Data::Dumper; die Dumper $self; | 
| 210 |  |  |  |  |  |  | } | 
| 211 | 0 |  |  | 0 | 0 | 0 | sub get_file_handle    { return shift->{fh} } | 
| 212 | 0 |  |  | 0 | 0 | 0 | sub get_file_name      { return shift->{file} } | 
| 213 | 0 |  |  | 0 | 0 | 0 | sub get_file_open_mode { return shift->{open_mode} } | 
| 214 |  |  |  |  |  |  |  | 
| 215 | 33 |  |  | 33 | 0 | 43 | sub file2str { return shift->get_record(@_) } | 
| 216 |  |  |  |  |  |  | sub get_record { | 
| 217 | 34 |  |  | 34 | 0 | 33 | my($self,$parser)=@_; | 
| 218 | 34 |  | 50 |  |  | 99 | local $/ =  $parser->{record_sep} || "\n"; | 
| 219 | 34 |  |  |  |  | 29 | my $fh =  $self->{fh} ; | 
| 220 | 34 |  | 100 |  |  | 596 | my $record = $fh->getline || return undef; | 
| 221 | 31 |  |  |  |  | 648 | $record =~ s/\015$//g; | 
| 222 | 31 |  |  |  |  | 72 | $record =~ s/\012$//g; | 
| 223 | 31 |  |  |  |  | 108 | return $record; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | sub set_col_nums { | 
| 227 | 3 |  |  | 3 | 0 | 3 | my $self = shift; | 
| 228 | 3 |  |  |  |  | 4 | my $col_names = $self->{col_names}; | 
| 229 | 3 | 50 |  |  |  | 5 | return {} unless $col_names; | 
| 230 | 3 |  |  |  |  | 3 | my $col_nums={}; my $i=0; | 
|  | 3 |  |  |  |  | 3 |  | 
| 231 | 3 |  |  |  |  | 4 | for (@$col_names) { | 
| 232 | 13 | 50 |  |  |  | 17 | next unless $_; | 
| 233 | 13 |  |  |  |  | 19 | $col_nums->{$col_names->[$i]} = $i; | 
| 234 | 13 |  |  |  |  | 13 | $i++; | 
| 235 |  |  |  |  |  |  | } | 
| 236 | 3 |  |  |  |  | 11 | return $col_nums; | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | sub truncate { | 
| 240 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 241 | 0 | 0 |  |  |  | 0 | if (!$self->{fh}->truncate($self->{fh}->tell())) { | 
| 242 | 0 |  |  |  |  | 0 | die "Error while truncating " . $self->{file} . ": $!"; | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | sub drop ($) { | 
| 247 | 0 |  |  | 0 | 0 | 0 | my($self) = @_; | 
| 248 |  |  |  |  |  |  | # We have to close the file before unlinking it: Some OS'es will | 
| 249 |  |  |  |  |  |  | # refuse the unlink otherwise. | 
| 250 | 0 | 0 |  |  |  | 0 | $self->{'fh'}->close() || die $!; | 
| 251 | 0 | 0 |  |  |  | 0 | unlink($self->{'file'}) || die $!; | 
| 252 | 0 |  |  |  |  | 0 | return 1; | 
| 253 |  |  |  |  |  |  | } | 
| 254 | 0 | 0 |  | 0 | 0 | 0 | sub close{ shift->{'fh'}->close() || die $!; } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | sub push_row { | 
| 257 | 0 |  |  | 0 | 0 | 0 | my $self  = shift; | 
| 258 | 0 |  |  |  |  | 0 | my $rec   = shift; | 
| 259 | 0 |  |  |  |  | 0 | my $fh = $self->{fh}; | 
| 260 |  |  |  |  |  |  | #####!!!! DON'T USE THIS ####    $fh->seek(0,2) or die $!; | 
| 261 | 0 | 0 |  |  |  | 0 | $fh->write($rec,length $rec) | 
| 262 |  |  |  |  |  |  | || die "Couldn't write to file: $!\n"; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | sub delete_record { | 
| 266 | 0 |  |  | 0 | 0 | 0 | my $self  = shift; | 
| 267 | 0 |  | 0 |  |  | 0 | my $parser  = shift || {}; | 
| 268 | 0 |  |  |  |  | 0 | my $fh = $self->{fh}; | 
| 269 | 0 |  | 0 |  |  | 0 | my $travel =  length($parser->{record_sep}) || 0; | 
| 270 | 0 |  |  |  |  | 0 | my $pos = $fh->tell - $travel; | 
| 271 | 0 |  |  |  |  | 0 | $self->{deleted}->{$pos}++; | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  | sub is_deleted { | 
| 274 | 30 |  |  | 30 | 0 | 20 | my $self  = shift; | 
| 275 | 30 |  | 50 |  |  | 46 | my $parser  = shift || {}; | 
| 276 | 30 |  |  |  |  | 28 | my $fh = $self->{fh}; | 
| 277 | 30 |  | 50 |  |  | 36 | my $travel =  length($parser->{record_sep}) || 0; | 
| 278 | 30 |  |  |  |  | 60 | my $pos = $fh->tell - $travel; | 
| 279 | 30 |  |  |  |  | 141 | return $self->{deleted}->{$pos}; | 
| 280 |  |  |  |  |  |  | } | 
| 281 |  |  |  |  |  |  | sub seek { | 
| 282 | 0 |  |  | 0 | 0 | 0 | my($self, $pos, $whence) = @_; | 
| 283 | 0 | 0 | 0 |  |  | 0 | if ($whence == 0  &&  $pos == 0) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 284 | 0 |  |  |  |  | 0 | $pos = $self->{first_row_pos}; | 
| 285 |  |  |  |  |  |  | } elsif ($whence != 2  ||  $pos != 0) { | 
| 286 | 0 |  |  |  |  | 0 | die "Illegal seek position: pos = $pos, whence = $whence"; | 
| 287 |  |  |  |  |  |  | } | 
| 288 | 0 | 0 |  |  |  | 0 | if (!$self->{fh}->seek($pos, $whence)) { | 
| 289 | 0 |  |  |  |  | 0 | die "Error while seeking in " . $self->{'file'} . ": $!"; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  | #print "<$pos-$whence>"; | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | sub DESTROY { | 
| 295 | 4 |  |  | 4 |  | 6 | my $self = shift; | 
| 296 | 4 |  |  |  |  | 6 | my $fh = $self->{fh}; | 
| 297 | 4 | 50 | 66 |  |  | 22 | print "CLOSING ", $self->get_file_name, "\n" if $fh && $DEBUG; | 
| 298 | 4 | 100 |  |  |  | 37 | $fh->close if $fh; | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  | __END__ |