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   28 use strict;
  5         8  
  5         163  
3 5     5   27 use warnings;
  5         10  
  5         120  
4 5     5   4740 use IO::File;
  5         57201  
  5         954  
5 5     5   44 use Fcntl qw(:flock);
  5         12  
  5         754  
6 5     5   42 use File::Basename;
  5         12  
  5         459  
7 5     5   30 use constant HAS_FLOCK => eval { flock STDOUT, 0; 1 };
  5         9  
  5         15  
  5         31  
  5         361  
8 5     5   29 use constant HAS_FILE_SPEC => eval { require File::Spec };
  5         16  
  5         13  
  5         246  
9 5     5   26 use vars qw($DEBUG);
  5         12  
  5         13274  
10             $DEBUG = 0;
11              
12              
13             sub new {
14 4     4 0 13 my $class = shift;
15 4   100     38 my $self = shift || {};
16             #$self->{f_dir} ||= './';
17 4         19 return bless $self, $class;
18             }
19              
20             sub seek_first_record {
21 8     8 0 22 my $self = shift;
22 8         18 my $fh = $self->{fh};
23 8         13 my $start = $self->{first_row_pos};
24 8 100 50     40 $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 11 my( $self,$file, $open_mode ) = @_;
41 3   50     33 my $dir = $self->{f_dir} || './';
42 3         106 my($fname,$path) = fileparse($file);
43 3         89 my($foo2,$os_cur_dir) = fileparse('');
44 3 50 33     36 my $haspath = 1 if $path and $path ne $os_cur_dir;
45 3 0 33     27 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         7 my $fh;
51 3   50     11 $open_mode ||= 'r';
52 3         21 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         12 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         7 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       11 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       15 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       25 if ($open_mode =~ /[co]/ ) {
84 1 50       25 if (!($fh = IO::File->new( $file, $mode{$open_mode} ))) {
85 0         0 die "Cannot open '$file': $!";
86             }
87 1 50       241 if (!$fh->seek(0, 0)) {
88 0         0 die " Error while seeking back: $!";
89             }
90             }
91 3 100       29 if ($open_mode =~ /[ru]/) {
92 2 50       106 die "Cannot read file '$file': doesn't exist!" unless -f $file;
93 2 50       13 if (!($fh = IO::File->new($file, $mode{$open_mode}))) {
94 0         0 die " Cannot open '$file': $!";
95             }
96             }
97 3         252 binmode($fh);
98 3         25 $fh->autoflush(1);
99 3         153 if ( HAS_FLOCK ) {
100 3 100       13 if ( $open_mode eq 'r') {
101 2 50       27 if (!flock($fh, LOCK_SH)) {
102 0         0 die "Cannot obtain shared lock on '$file': $!";
103             }
104             } else {
105 1 50       15 if (!flock($fh, LOCK_EX)) {
106 0         0 die " Cannot obtain exclusive lock on '$file': $!";
107             }
108             }
109             }
110 3 50       12 print "OPENING $file, mode = '$open_mode'\n" if $DEBUG;
111 3 100       65 return( $file, $fh, $open_mode) if wantarray;
112 1         6 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         4 my @fields = ();
135 2 100       9 if ($parser->{keep_first_line}) {
136 1         2 my $cols = $parser->{col_names};
137 1 50       3 return undef unless $cols;
138 1 50       6 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         2 my $fh = $self->{fh};
146 1 50       12 $fh->seek(0,0) if $fh;
147 1         13 my $first_line = $self->get_record($parser);
148             #print $first_line;
149 1 50       5 if ( $first_line ) {
150 1 50       10 @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       9 return "CAN'T FIND COLUMN NAMES ON FIRST LINE OF '"
159             . $self->{file}
160             . "' : '@fields'" if "@fields" =~ /[^ a-zA-Z0-9_]/;
161 1         3 $parser->{col_names} = \@fields;
162 1         3 $self->{col_names} = \@fields;
163 1         4 $self->{col_nums} = $self->set_col_nums;
164 1         4 $self->{first_row_pos} = $fh->tell();
165 1         15 return( \@fields);
166             }
167             sub open_table {
168 2     2 0 6 my( $self, $parser, $file, $open_mode ) = @_;
169 2         4 my($newfile, $fh);
170 2   50     127 $file ||= '';
171 2 50       15 if ( $file =~ m'http://|ftp://' ) {
172             # die "wrong storage!";
173 0         0 $newfile = $file;
174             }
175             else {
176 2 50 33     17 ($newfile,$fh) =
177             $self->open_local_file($file,$open_mode) if $file && !(ref $file);
178            
179             }
180 2   33     9 $newfile ||= $file;
181             #die AnyData::dump($parser);
182 2   100     11 my $col_names = $parser->{col_names} || '';
183             # my @array = split(/,/,$col_names);
184              
185 2         3 my @array;
186 2 100       12 @array = ref $col_names eq 'ARRAY'
187             ? @$col_names
188             : split ',',$col_names;
189              
190 2 50       22 my $pos = $fh->tell() if $fh;
191 2         28 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         10 for my $key(keys %table) {
200 12         32 $self->{$key}=$table{$key};
201             }
202 2         26 my $skip = $parser->init_parser($self);
203 2 50 33     21 if (!$skip && defined $newfile) {
204 2 50       20 $open_mode =~ /[co]/
205             ? $self->print_col_names($parser)
206             : $self->get_col_names($parser);
207             }
208 2         6 $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 67 sub file2str { return shift->get_record(@_) }
216             sub get_record {
217 34     34 0 53 my($self,$parser)=@_;
218 34   50     164 local $/ = $parser->{record_sep} || "\n";
219 34         56 my $fh = $self->{fh} ;
220 34   100     956 my $record = $fh->getline || return undef;
221 31         1137 $record =~ s/\015$//g;
222 31         132 $record =~ s/\012$//g;
223 31         191 return $record;
224             }
225              
226             sub set_col_nums {
227 3     3 0 6 my $self = shift;
228 3         5 my $col_names = $self->{col_names};
229 3 50       10 return {} unless $col_names;
230 3         5 my $col_nums={}; my $i=0;
  3         5  
231 3         7 for (@$col_names) {
232 13 50       27 next unless $_;
233 13         27 $col_nums->{$col_names->[$i]} = $i;
234 13         23 $i++;
235             }
236 3         20 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 42 my $self = shift;
275 30   50     66 my $parser = shift || {};
276 30         46 my $fh = $self->{fh};
277 30   50     78 my $travel = length($parser->{record_sep}) || 0;
278 30         81 my $pos = $fh->tell - $travel;
279 30         298 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   9 my $self = shift;
296 4         9 my $fh = $self->{fh};
297 4 50 66     34 print "CLOSING ", $self->get_file_name, "\n" if $fh && $DEBUG;
298 4 100       185 $fh->close if $fh;
299             }
300             __END__