File Coverage

blib/lib/AnyData/Storage/File.pm
Criterion Covered Total %
statement 124 182 68.1
branch 40 92 43.4
condition 21 55 38.1
subroutine 18 30 60.0
pod 0 21 0.0
total 203 380 53.4


line stmt bran cond sub pod time code
1             package AnyData::Storage::File;
2 5     5   23 use strict;
  5         6  
  5         159  
3 5     5   24 use warnings;
  5         5  
  5         114  
4 5     5   2432 use IO::File;
  5         37171  
  5         647  
5 5     5   33 use Fcntl qw(:flock);
  5         7  
  5         700  
6 5     5   25 use File::Basename;
  5         12  
  5         374  
7 5     5   29 use constant HAS_FLOCK => eval { flock STDOUT, 0; 1 };
  5         5  
  5         7  
  5         20  
  5         363  
8 5     5   24 use constant HAS_FILE_SPEC => eval { require File::Spec };
  5         9  
  5         7  
  5         217  
9 5     5   18 use vars qw($DEBUG);
  5         7  
  5         9018  
10             $DEBUG = 0;
11              
12              
13             sub new {
14 4     4 0 9 my $class = shift;
15 4   100     12 my $self = shift || {};
16             #$self->{f_dir} ||= './';
17 4         13 return bless $self, $class;
18             }
19              
20             sub seek_first_record {
21 8     8 0 11 my $self = shift;
22 8         11 my $fh = $self->{fh};
23 8         9 my $start = $self->{first_row_pos};
24 8 100 50     30 $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     21 my $dir = $self->{f_dir} || './';
42 3         96 my($fname,$path) = fileparse($file);
43 3         36 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     15 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         3 my $fh;
51 3   50     8 $open_mode ||= 'r';
52 3         15 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         5 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       8 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       9 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       14 if ($open_mode =~ /[co]/ ) {
84 1 50       10 if (!($fh = IO::File->new( $file, $mode{$open_mode} ))) {
85 0         0 die "Cannot open '$file': $!";
86             }
87 1 50       204 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       66 die "Cannot read file '$file': doesn't exist!" unless -f $file;
93 2 50       11 if (!($fh = IO::File->new($file, $mode{$open_mode}))) {
94 0         0 die " Cannot open '$file': $!";
95             }
96             }
97 3         168 binmode($fh);
98 3         22 $fh->autoflush(1);
99 3         118 if ( HAS_FLOCK ) {
100 3 100       9 if ( $open_mode eq 'r') {
101 2 50       17 if (!flock($fh, LOCK_SH)) {
102 0         0 die "Cannot obtain shared lock on '$file': $!";
103             }
104             } else {
105 1 50       10 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         5 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 3 my($self,$parser) = @_;
134 2         3 my @fields = ();
135 2 100       4 if ($parser->{keep_first_line}) {
136 1         1 my $cols = $parser->{col_names};
137 1 50       3 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         7 my $first_line = $self->get_record($parser);
148             #print $first_line;
149 1 50       6 if ( $first_line ) {
150 1 50       8 @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 1 50       6 return "CAN'T FIND COLUMN NAMES ON FIRST LINE OF '"
159             . $self->{file}
160             . "' : '@fields'" if "@fields" =~ /[^ a-zA-Z0-9_]/;
161 1         2 $parser->{col_names} = \@fields;
162 1         2 $self->{col_names} = \@fields;
163 1         3 $self->{col_nums} = $self->set_col_nums;
164 1         3 $self->{first_row_pos} = $fh->tell();
165 1         6 return( \@fields);
166             }
167             sub open_table {
168 2     2 0 4 my( $self, $parser, $file, $open_mode ) = @_;
169 2         2 my($newfile, $fh);
170 2   50     5 $file ||= '';
171 2 50       10 if ( $file =~ m'http://|ftp://' ) {
172             # die "wrong storage!";
173 0         0 $newfile = $file;
174             }
175             else {
176 2 50 33     11 ($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         2 my @array;
186 2 100       9 @array = ref $col_names eq 'ARRAY'
187             ? @$col_names
188             : split ',',$col_names;
189              
190 2 50       16 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         6 for my $key(keys %table) {
200 12         19 $self->{$key}=$table{$key};
201             }
202 2         18 my $skip = $parser->init_parser($self);
203 2 50 33     16 if (!$skip && defined $newfile) {
204 2 50       8 $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 68 sub file2str { return shift->get_record(@_) }
216             sub get_record {
217 34     34 0 39 my($self,$parser)=@_;
218 34   50     116 local $/ = $parser->{record_sep} || "\n";
219 34         34 my $fh = $self->{fh} ;
220 34   100     791 my $record = $fh->getline || return undef;
221 31         805 $record =~ s/\015$//g;
222 31         98 $record =~ s/\012$//g;
223 31         135 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       8 return {} unless $col_names;
230 3         4 my $col_nums={}; my $i=0;
  3         3  
231 3         4 for (@$col_names) {
232 13 50       20 next unless $_;
233 13         18 $col_nums->{$col_names->[$i]} = $i;
234 13         15 $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 29 my $self = shift;
275 30   50     55 my $parser = shift || {};
276 30         32 my $fh = $self->{fh};
277 30   50     60 my $travel = length($parser->{record_sep}) || 0;
278 30         82 my $pos = $fh->tell - $travel;
279 30         211 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         8 my $fh = $self->{fh};
297 4 50 66     26 print "CLOSING ", $self->get_file_name, "\n" if $fh && $DEBUG;
298 4 100       55 $fh->close if $fh;
299             }
300             __END__