File Coverage

blib/lib/DirDB/FTP.pm
Criterion Covered Total %
statement 17 142 11.9
branch 1 72 1.3
condition 0 6 0.0
subroutine 6 17 35.2
pod 0 1 0.0
total 24 238 10.0


line stmt bran cond sub pod time code
1             package DirDB::FTP;
2              
3             require 5.005;
4 1     1   7413 use strict;
  1         2  
  1         39  
5             # use warnings;
6 1     1   6 use Carp;
  1         3  
  1         77  
7              
8 1     1   1139 use Net::FTP;
  1         275489  
  1         58  
9 1     1   883 use Net::FTP::blat;
  1         636  
  1         26  
10              
11 1     1   6 use vars '$VERSION';
  1         1  
  1         1554  
12              
13             $VERSION = '0.03';
14              
15             sub new{
16             # DirDB::FTP -> new ( $hostname, $user, $pass );
17 1     1 0 122 shift; # we don't need the package name
18 1 50       12 my $f = Net::FTP->new( $_[0] , timeout => 300 ) or croak
19             "cannot connect to $_[0]";
20 0           my $pl = 0+length $_[2];
21 0 0         $f->login($_[1], $_[2]) or croak
22             "cannot login with username $_[1] and $pl char passwd";
23 0           $f->binary();
24 0           $f;
25             };
26              
27              
28             sub TIEHASH {
29 0     0     my ($self, $ftp, $rootpath) = @_;
30 0 0         ref($ftp) eq 'Net::FTP' or croak <<'EOSYNTAX';
31             Syntax:
32             tie my %hash, DirDB::FTP => $ftp_object [, "/some/directory"];
33             EOSYNTAX
34              
35 0 0         if(defined $rootpath){
36 0           $rootpath =~ s#/+$##; # lose trailing slash(es)
37 0 0         if (length $rootpath){
38 0 0 0       $ftp->cwd($rootpath) or $ftp->mkdir($rootpath,'recurse')
39             or
40             croak "could not change to or create dir $rootpath: "
41             . $ftp->message;
42             };
43             }else{
44 0           $rootpath = $ftp->pwd();
45             };
46 0           bless [$ftp,"$rootpath/"], $self;
47             };
48              
49             sub TIEARRAY {
50 0     0     confess "DirDB does not support arrays yet";
51             };
52              
53             sub TIESCALAR {
54 0     0     confess "DirDB does not support scalars yet -- try Tie::Slurp";
55             };
56              
57              
58             sub EXISTS {
59 0     0     my ($ftp,$rootpath) = @{+shift};
  0            
60 0           my $key = shift;
61 0           $key =~ s/^ / /; #escape leading space into two spaces
62 0 0         $key eq '' and $key = ' EMPTY';
63 0           my $mdtm = $ftp->mdtm("$rootpath$key");
64 0 0         $ftp->message =~ m/no such/i and return 0;
65 0           return 1;
66              
67             # my @mdtm = $ftp->ls("$rootpath$key");
68             # print "Debug: $mdtm\n";
69 0 0         defined $mdtm and return 1;
70 0 0         $ftp->message =~ m/ not a plain file/ and return 1;
71 0           0;
72             };
73              
74             sub FETCH {
75 0     0     my $ref = shift;
76 0           my ($ftp,$rootpath) = @{$ref};
  0            
77 0           my $key = shift;
78 0           $key =~ s/^ / /; #escape leading space into two spaces
79 0 0         $key eq '' and $key = ' EMPTY';
80             # FIXME
81             # Our goal here is to mimic the DirDB semantics, using
82             # commands defined in Net::FTP, plus slurp and blat.
83             # We are allowing some error message reading but want
84             # to keep that down to as little as possible.
85              
86            
87 0           sleep 1 while $ftp->ls( "$rootpath LOCK$key");
88 0           my $result = $ftp->slurp( "$rootpath$key" );
89             # print "DEBUG: ",$ftp->message(),"\n";
90            
91 0 0         defined $result and return $result;
92              
93 0 0         $ftp->message =~ /no such/i and return undef;
94            
95             # assume a directory, ...
96 0 0         tie my %newhash, ref($ref),$ftp,"$rootpath$key"
97             or croak "Could not fetch [$rootpath$key]: ".$ftp->message;
98 0           return \%newhash;
99             };
100              
101             {
102             my %CircleTracker;
103             sub STORE {
104 0     0     my ($ref , $key, $value) = @_;
105 0           my ($ftp,$rootpath) = @{$ref};
  0            
106            
107 0           my $rnd = rand(10000).{}.$$;
108 0           $rnd =~ tr/a-zA-Z0-9//cd;
109 0           $key =~ s/^ / /; #escape leading space into two spaces
110 0 0         $key eq '' and $key = ' EMPTY';
111 0           my $refvalue = ref $value;
112 0 0         if ($refvalue){
113              
114 0 0         if ( $CircleTracker{$value}++ ){
115 0           croak "$ref version $VERSION cannot store circular structures\n";
116             };
117              
118 0 0         $refvalue eq 'HASH' or
119             croak
120             "$ref version $VERSION only stores references to HASH, not $refvalue\n";
121              
122 0 0         if (tied (%$value)){
123             # recursive copy
124 0 0         tie my %tmp, ref($ref), $ftp, "$rootpath TMP$rnd" or
125             die "tie failed";
126 0           eval{
127             # %tmp = %$value
128              
129 0           my ($k,$v);
130 0           while(($k,$v) = each %$value){
131 0           $tmp{$k}=$v;
132             };
133             };
134             # print "$rootpath TMP$rnd should now contain @{[%$value]}\n";
135 0 0         if($@){
136 0           my $message = $@;
137 0           eval {$ftp->rmdir( "$rootpath TMP$rnd", 1)};
  0            
138 0           croak "trouble writing [$value] to [$rootpath$key]: $message";
139              
140             };
141            
142             # print "lock (tied)";
143 0           sleep 1 while !$ftp->mkdir( "$rootpath LOCK$key");
144             {
145             # no warnings;
146 0           $ftp->rename( "$rootpath$key", "$rootpath GARBAGE$rnd");
  0            
147             };
148 0           $ftp->rename( "$rootpath TMP$rnd", "$rootpath$key");
149              
150             }else{
151             # cache, bless, restore
152 0           my @cache = %$value;
153 0           %$value = ();
154             # print "lock (untied)";
155 0           while( !$ftp->mkdir( "$rootpath LOCK$key")){
156             # print "lock conflivt: $!";
157 0           sleep 1;
158             };
159             {
160             # no warnings;
161 0           $ftp->rename( "$rootpath$key","$rootpath GARBAGE$rnd");
  0            
162             };
163 0 0         tie %$value, ref($ref), $ftp, "$rootpath$key" or
164             warn "tie to [$rootpath$key] failed: ".$ftp->message;
165             # print "assignment";
166 0           %$value = @cache;
167             };
168            
169 0           $ftp->rmdir( "$rootpath LOCK$key");
170 0           delete $CircleTracker{$value};
171            
172             # print "GC";
173 0           eval {$ftp->rmdir( "$rootpath GARBAGE$rnd",'recurse')};
  0            
174 0 0         if($@){
175 0           croak "GC problem: $@";
176             };
177 0           return;
178              
179             };
180              
181             # store a scalar using write-to-temp-and-rename
182 0 0         $ftp->blat($value,"$rootpath TMP$rnd") or croak $ftp->message;
183 0 0         $ftp->rename( "$rootpath TMP$rnd" , "$rootpath$key") or
184             croak
185             " could not rename temp file to [$rootpath$key]: ".$ftp->message;
186             };
187             };
188              
189             sub DELETE {
190 0     0     my ($ref , $key) = @_;
191 0           my ($ftp,$rootpath) = @{$ref};
  0            
192            
193 0           my $retval = undef;
194            
195 0 0         if(defined wantarray){
196 0           $retval = FETCH( $ref,$key );
197 0 0         if (ref $retval) {
198 0           my %hash;
199 0           my @keys = keys %$retval;
200 0           my $k;
201 0           foreach $k (@keys) {
202 0           $hash{$k} = delete $retval->{$k};
203             };
204 0           $retval = \%hash;
205             };
206             };
207              
208 0           $key =~ s/^ / /; #escape leading space into two spaces
209 0 0         $key eq '' and $key = ' EMPTY';
210              
211             # -e "$rootpath$key" or return undef;
212 0 0         $ftp->delete( "$rootpath$key" ) or
213             $ftp->rmdir( "$rootpath$key", 'recurse' );
214            
215 0           return $retval;
216             };
217              
218             sub CLEAR{
219 0     0     my ($ref , $key, $value) = @_;
220 0           my ($ftp,$rootpath) = @{$ref};
  0            
221            
222             # maybe we can delete the whole thing?
223             # we will check to make sure this succeeds because we
224             # want to support clearing a whole directory that we
225             # have been issued by an administrator
226            
227 0 0 0       $ftp->rmdir($rootpath, 'recurse')
228             and
229             $ftp->mkdir($rootpath)
230             and
231             return;
232              
233 0           my @dirents = $ftp->ls($rootpath);
234 0           for my $ent (@dirents){
235 0 0         $ftp->delete("$rootpath$ent")
236             or
237             $ftp->rmdir("$rootpath$ent",1)
238             };
239            
240             };
241              
242             {
243              
244             my %IteratorListings;
245              
246             sub FIRSTKEY {
247 0     0     my ($ref , $key, $value) = @_;
248 0           my ($ftp,$rootpath) = @{$ref};
  0            
249             # opendir FSDBFH, $path or croak "opendir $path: $!";
250             # $IteratorListings{$ref} = [ grep {!($_ =~ /^\.\.?\Z/)} readdir FSDBFH ];
251 0           $IteratorListings{$ref} = [ $ftp->ls ];
252              
253 0           $ref->NEXTKEY;
254             };
255              
256             sub NEXTKEY{
257 0     0     my $ref = shift;
258 0           my ($ftp,$rootpath) = @{$ref};
  0            
259             #print "next key in path <$$ref> will be shifted from <@{$IteratorListings{$ref}}>\n";
260 0 0         @{$IteratorListings{$ref}} or return undef;
  0            
261 0           my $key = shift @{$IteratorListings{$ref}};
  0            
262 0 0         if ($key =~ s/^ //){
263 0 0         if ($key = m/^ /){
    0          
264             # we have unescaped a leading space.
265             }elsif ($key eq 'EMPTY'){
266 0           $key = ''
267             #}elsif($key eq 'REF'){
268             # return $ref->NEXTKEY(); # next
269             #}elsif($key =~ m/^ARRAY){
270             # return $ref->NEXTKEY(); # next
271             }else{
272             # per-container metadata does not
273             # appear in iterations through data.
274 0           return $ref->NEXTKEY(); # next
275             }
276             };
277 0 0         wantarray or return $key;
278 0           return @{[$key, $ref->FETCH($key)]};
  0            
279             };
280            
281             sub DESTROY{
282             # no warnings;
283 0     0     delete $IteratorListings{$_[0]};
284             };
285            
286             };
287              
288              
289              
290              
291             1;
292             __END__