File Coverage

blib/lib/Net/FTP/Recursive.pm
Criterion Covered Total %
statement 24 387 6.2
branch 0 310 0.0
condition 0 99 0.0
subroutine 8 28 28.5
pod 6 10 60.0
total 38 834 4.5


line stmt bran cond sub pod time code
1             package Net::FTP::Recursive;
2              
3 1     1   24585 use Net::FTP;
  1         514800  
  1         68  
4 1     1   12 use Carp;
  1         2  
  1         70  
5 1     1   6 use Cwd 'getcwd';
  1         8  
  1         46  
6 1     1   5 use strict;
  1         2  
  1         33  
7              
8 1     1   4 use vars qw/@ISA $VERSION/;
  1         2  
  1         46  
9 1     1   4 use vars qw/%options %filesSeen %dirsSeen %linkMap $success/;
  1         1  
  1         4899  
10              
11             @ISA = qw|Net::FTP|;
12             $VERSION = '2.04';
13              
14             sub new {
15 0     0 1   my $class = shift;
16              
17 0           my $ftp = new Net::FTP(@_);
18              
19 0 0         bless $ftp, $class if defined($ftp);
20              
21 0           return $ftp;
22             }
23              
24             #------------------------------------------------------------------
25             # - cd to directory, lcd to directory
26             # - grab all files, process symlinks according to options
27             #
28             # - foreach directory
29             # - create it unless options say to flatten
30             # - call function recursively.
31             # - cd .. unless options say to flatten
32             # - lcd ..
33             #
34             # -----------------------------------------------------------------
35              
36             sub rget{
37 0     0 1   my $ftp = shift;
38              
39 0           %options = (
40             ParseSub => \&parse_files,
41             SymLinkIgnore => 1,
42             @_,
43             InitialDir => $ftp->pwd
44             ); #setup the options
45              
46 0           local %dirsSeen = ();
47 0           local %filesSeen = ();
48              
49 0 0         if ( $options{SymlinkFollow} ) {
50 0           $dirsSeen{ $ftp->pwd } = Cwd::cwd();
51             }
52              
53 0           local $success = '';
54              
55 0           $ftp->_rget(); #do the real work here
56              
57 0           return $success;
58             }
59              
60             sub _rget {
61 0     0     my($ftp) = shift;
62              
63 0           my @dirs;
64              
65 0           my @ls = $ftp->dir();
66              
67 0           my @files = $options{ParseSub}->( @ls );
68              
69 0 0         @files = grep { $_->filename =~ $options{MatchAll} } @files
  0            
70             if $options{MatchAll};
71              
72 0 0         @files = grep { $_->filename !~ $options{OmitAll} } @files
  0            
73             if $options{OmitAll};
74              
75 0 0         print STDERR join("\n", @ls), "\n"
76             if $ftp->debug;
77              
78 0           my $remote_pwd = $ftp->pwd;
79 0           my $local_pwd = Cwd::cwd();
80              
81             FILE:
82 0           foreach my $file (@files){
83             #used to make sure that if we're deleting the files, we
84             #successfully retrieved the file
85 0           my $get_success = 1;
86 0           my $filename = $file->filename();
87              
88             #if it's not a directory we just need to get the file.
89 0 0         if ( $file->is_plainfile() ) {
    0          
    0          
90              
91 0 0 0       if( ( $options{MatchFiles}
      0        
      0        
92             and $filename !~ $options{MatchFiles} )
93             or
94             ( $options{OmitFiles}
95             and $filename =~ $options{OmitFiles} )){
96              
97 0           next FILE;
98             }
99              
100 0 0 0       if ( $options{FlattenTree} and $filesSeen{$filename} ) {
101 0 0         print STDERR "Retrieving $filename as ",
102             "$filename.$filesSeen{$filename}.\n"
103             if $ftp->debug;
104              
105 0           $get_success = $ftp->get( $filename,
106             "$filename.$filesSeen{$filename}" );
107             } else {
108 0 0         print STDERR "Retrieving $filename.\n"
109             if $ftp->debug;
110              
111 0           $get_success = $ftp->get( $filename );
112             }
113              
114 0 0         $filesSeen{$filename}++ if $options{FlattenTree};
115              
116 0 0         if ( $options{RemoveRemoteFiles} ) {
117 0 0         if ( $options{CheckSizes} ) {
118 0 0 0       if ( -e $filename and ( (-s $filename) == $file->size ) ) {
119 0           $ftp->delete( $filename );
120 0 0         print STDERR "Deleting '$filename'.\n"
121             if $ftp->debug;
122             } else {
123 0 0         print STDERR "Will not delete '$filename': ",
124             'remote file size and local file size ',
125             "do not match!\n"
126             if $ftp->debug;
127             }
128             } else {
129 0 0         if ( $get_success ) {
130 0           $ftp->delete( $filename );
131 0 0         print STDERR "Deleting '$filename'.\n"
132             if $ftp->debug;
133             } else {
134 0 0         print STDERR "Will not delete '$filename': ",
135             "error retrieving file!\n"
136             if $ftp->debug;
137             }
138             }
139             }
140             }
141             elsif ( $file->is_directory() ) {
142              
143 0 0 0       if( ( $options{MatchDirs}
      0        
      0        
144             and $filename !~ $options{MatchDirs} )
145             or
146             ( $options{OmitDirs}
147             and $filename =~ $options{OmitDirs} )){
148              
149 0           next FILE;
150             }
151              
152 0 0         if ( $options{SymlinkFollow} ) {
153 0           $dirsSeen{"$remote_pwd/$filename"} = "$local_pwd/$filename";
154 0           print STDERR "Mapping '$remote_pwd/$filename' to ",
155             "'$local_pwd/$filename'.\n";
156             }
157              
158 0           push @dirs, $file;
159             }
160             elsif ( $file->is_symlink() ) {
161              
162             #SymlinkIgnore is really the default.
163 0 0         if ( $options{SymlinkIgnore} ) {
164 0 0         print STDERR "Ignoring the symlink ", $filename, ".\n"
165             if $ftp->debug;
166 0 0         if ( $options{RemoveRemoteFiles} ) {
167 0           $ftp->delete( $filename );
168 0 0         print STDERR 'Deleting \'', $filename, "'.\n"
169             if $ftp->debug;
170             }
171 0           next FILE;
172             }
173              
174 0 0 0       if( ( $options{MatchLinks}
      0        
      0        
175             and $filename !~ $options{MatchLinks} )
176             or
177             ( $options{OmitLinks}
178             and $filename =~ $options{OmitLinks} )){
179              
180 0           next FILE;
181             }
182              
183             #otherwise we need to see if it points to a directory
184 0 0         print STDERR "Testing to see if $filename refers to a directory.\n"
185             if $ftp->debug;
186 0           my $path_before_chdir = $ftp->pwd;
187 0           my $is_directory = 0;
188              
189 0 0         if ( $ftp->cwd($file->filename()) ) {
190 0           $ftp->cwd( $path_before_chdir );
191 0           $is_directory = 1;
192             }
193              
194 0 0 0       if ( not $is_directory and $options{SymlinkCopy} ) {
    0 0        
    0          
195             #if it's not a directory and SymlinkCopy is set,
196             # we'll just copy the file as a regular file
197              
198             #symlink to non-directory. need to grab it and
199             #make sure the filename does not collide
200 0           my $get_success;
201 0 0 0       if ( $options{FlattenTree} and $filesSeen{$filename}) {
202 0 0         print STDERR "Retrieving $filename as ",
203             $filename.$filesSeen{$filename},
204             ".\n"
205             if $ftp->debug;
206              
207 0           $get_success = $ftp->get($filename,
208             "$filename.$filesSeen{$filename}");
209             } else {
210 0 0         print STDERR "Retrieving $filename.\n"
211             if $ftp->debug;
212              
213 0           $get_success = $ftp->get( $filename );
214             }
215              
216 0           $filesSeen{$filename}++;
217              
218 0 0 0       if ( $get_success and $options{RemoveRemoteFiles} ) {
219 0           $ftp->delete( $filename );
220              
221 0 0         print STDERR "Deleting '$filename'.\n"
222             if $ftp->debug;
223             }
224             } #end of if (not $is_directory and $options{SymlinkCopy}
225             elsif ( $is_directory and $options{SymlinkFollow} ) {
226             #we need to resolve the link to an absolute path
227              
228 0           my $remote_abs_path = path_resolve( $file->linkname(),
229             $remote_pwd,
230             $filename
231             );
232              
233 0           print STDERR "'$filename' got converted to '",
234             $remote_abs_path, "'.\n";
235              
236             #if it's a directory structure we've already seen,
237             #we'll just make a relative symlink to that
238             #directory
239              
240             # OR
241              
242             #if it's in the same tree that we started
243             #downloading, we should get to it later, so we'll
244             #just make a relative symlink to that directory.
245              
246 0 0 0       if ( $dirsSeen{$remote_abs_path}
247             or $remote_abs_path =~ s{^$options{InitialDir}}
248             {$dirsSeen{$options{InitialDir}}}){
249              
250 0 0         unless( $options{FlattenTree} ){
251 0 0         print STDERR "\$dirsSeen{$remote_abs_path} = ",
252             $dirsSeen{$remote_abs_path}, "\n"
253             if $ftp->debug;
254              
255 0 0 0       print STDERR "Calling convert_to_relative( '",
256             $local_pwd, '/', $filename, "', '",
257             ( $dirsSeen{$remote_abs_path}
258             || $remote_abs_path ),
259             "');\n"
260             if $ftp->debug;
261              
262 0   0       my $rel_path =
263             convert_to_relative( "$local_pwd/$filename",
264             $dirsSeen{$remote_abs_path}
265             || $remote_abs_path
266             );
267              
268 0 0         print STDERR "Symlinking '$filename' to '$rel_path'.\n"
269             if $ftp->debug;
270              
271 0           symlink $rel_path, $filename;
272             }
273              
274 0 0         if ( $options{RemoveRemoteFiles} ) {
275 0           $ftp->delete( $filename );
276              
277 0 0         print STDERR "Deleting '$filename'.\n"
278             if $ftp->debug;
279             }
280              
281 0           next FILE;
282             }
283             # Otherwise we need to grab the directory and put
284             # the info in a hash in case there is another link
285             # to this directory
286             else {
287              
288 0 0         print STDERR "New directory to grab!\n"
289             if $ftp->debug;
290 0           push @dirs, $file;
291              
292 0           $dirsSeen{$remote_abs_path} = "$local_pwd/$filename";
293 0 0         print STDERR "Mapping '$remote_abs_path' to '",
294             "$local_pwd/$filename'.\n"
295             if $ftp->debug;
296             #no deletion, will handle that down below.
297              
298             }
299              
300             } #end of elsif($is_directory and $options{SymlinkFollow})
301              
302             # if it's a dir and SymlinkFollow is not set but
303             # SymlinkLink is set, we'll just create the link.
304              
305             # OR
306              
307             # if it was a file and SymlinkCopy is not set but
308             # SymlinkLink is, we'll just create the link.
309              
310             elsif ( $options{SymlinkLink} ) {
311             #we need to make the symlink and that's it.
312 0           symlink $file->linkName(), $file->filename();
313              
314 0 0         if ( $options{RemoveRemoteFiles} ) {
315 0           $ftp->delete( $file->filename );
316              
317 0 0         print STDERR "Deleting '$filename'.\n"
318             if $ftp->debug;
319             }
320 0           next FILE;
321             }
322             }
323              
324 0 0         $success .= "Had a problem retrieving '$remote_pwd/$filename'!\n"
325             unless $get_success;
326             } #end of foreach ( @files )
327              
328 0           undef @files; #save memory in recursing.
329              
330             #this will do depth-first retrieval
331              
332             DIRECTORY:
333 0           foreach my $file (@dirs) {
334 0           my $filename = $file->filename;
335              
336             #check to make sure that we actually have permissions to
337             #change into the directory
338              
339 0 0         unless ( $ftp->cwd($filename) ) {
340 0 0         print STDERR 'Was unable to cd to ', $filename,
341             ", skipping!\n"
342             if $ftp->debug;
343              
344 0           $success .= "Was not able to chdir to '$remote_pwd/$filename'!\n";
345 0           next DIRECTORY;
346             }
347              
348 0 0         unless ( $options{FlattenTree} ) {
349 0 0         print STDERR "Making dir: ", $filename, "\n"
350             if $ftp->debug;
351              
352 0           mkdir $filename, "0755"; # mkdir, ignore errors due to
353             # pre-existence
354              
355 0           chmod 0755, $filename; # just in case the UMASK in the
356             # mkdir doesn't work
357              
358 0 0         unless ( chdir $filename ){
359 0 0         print STDERR 'Could not change to the local directory ',
360             $filename, "!\n"
361             if $ftp->debug;
362              
363 0           $ftp->cwd( $remote_pwd );
364 0           $success .= q{Could not chdir to local directory '}
365             . "$local_pwd/$filename'!\n";
366              
367 0           next DIRECTORY;
368             }
369             }
370              
371             #don't delete files that are accessed through a symlink
372              
373 0           my $remove;
374 0 0 0       if ( $options{RemoveRemoteFiles} and $file->is_symlink() ) {
375 0           $remove = $options{RemoveRemoteFiles};
376 0           $options{RemoveRemoteFiles} = 0;
377             }
378              
379             #need to recurse
380 0 0         print STDERR 'Calling rget in ', $remote_pwd, "\n"
381             if $ftp->debug;
382 0           $ftp->_rget( );
383              
384             #once we've recursed, we'll go back up a dir.
385 0 0         print STDERR 'Returned from rget in ', $remote_pwd, ".\n"
386             if $ftp->debug;
387              
388 0 0         if ( $file->is_symlink() ) {
389 0           $ftp->cwd( $remote_pwd );
390 0           $options{RemoveRemoteFiles} = $remove;
391             } else {
392 0           $ftp->cdup;
393             }
394              
395 0 0         chdir '..' unless $options{FlattenTree};
396              
397 0 0         if ( $options{RemoveRemoteFiles} ) {
398 0 0         if ( $file->is_symlink() ) {
399 0 0         print STDERR "Removing symlink '$filename'.\n"
400             if $ftp->debug;
401              
402 0           $ftp->delete( $filename );
403             } else {
404 0 0         print STDERR "Removing directory '$filename'.\n"
405             if $ftp->debug;
406              
407 0           $ftp->rmdir( $filename );
408             }
409             }
410             }
411             }
412              
413             sub rput{
414 0     0 1   my $ftp = shift;
415              
416 0           %options = (
417             ParseSub => \&parse_files,
418             @_
419             );
420              
421 0           local %filesSeen = ();
422              
423 0           local $success = '';
424              
425 0           $ftp->_rput(); #do the real work here
426              
427 0           return $success;
428             }
429              
430             #------------------------------------------------------------------
431             # - make the directory on the remote host
432             # - cd to directory, lcd to directory
433             # - foreach directory, call the function recursively
434             # - cd .., lcd ..
435             # -----------------------------------------------------------------
436              
437             sub _rput {
438 0     0     my($ftp) = shift;
439              
440 0           my @dirs; #list of directories to recurse into after this dir is processed
441              
442 0           my @files = read_current_directory();
443              
444 0 0         print STDERR join("\n", sort map { $_->filename() } @files),"\n"
  0            
445             if $ftp->debug;
446              
447 0           my $remote_pwd = $ftp->pwd;
448              
449 0           foreach my $file (@files){
450 0           my $put_success = 1;
451 0           my $filename = $file->filename(); #we're gonna need it a lot here
452              
453             #if it's a file we just need to put the file
454 0 0         if ( $file->is_plainfile() ) {
    0          
    0          
455              
456             #we're going to check for filename conflicts here if
457             #the user has opted to flatten out the tree
458 0 0 0       if ( $options{FlattenTree} and $filesSeen{$filename} ) {
459 0 0         print STDERR "Sending $filename as ",
460             "$filename.$filesSeen{$filename}.\n"
461             if $ftp->debug;
462 0           $put_success = $ftp->put( $filename,
463             "$filename.$filesSeen{$filename}" );
464             } else {
465 0 0         print STDERR "Sending $filename.\n" if $ftp->debug;
466              
467             #I've saved $put_success here, but apparently the
468             #return val isn't very useful-can probably stop
469             #saving it
470 0           $put_success = $ftp->put( $filename );
471             }
472              
473 0 0         $filesSeen{$filename}++ if $options{FlattenTree};
474              
475 0 0 0       if ( $options{RemoveLocalFiles} and $options{CheckSizes} ) {
    0          
476 0 0         if ( $ftp->size($filename) == (-s $filename) ) {
477 0 0         print STDERR q{Removing '}, $filename,
478             "' from the local system.\n"
479             if $ftp->debug;
480              
481 0           unlink $file->filename();
482             } else {
483 0 0         print STDERR "Will not delete '$filename': ",
484             'remote file size and local file size',
485             " do not match!\n"
486             if $ftp->debug;
487             }
488             }
489             elsif( $options{RemoveLocalFiles} ) {
490 0 0         print STDERR q{Removing '}, $filename,
491             "' from the local system.\n"
492             if $ftp->debug;
493 0           unlink $file->filename();
494             }
495             }
496              
497             #otherwise, if it's a directory, we have to create the directory
498             #on the remote machine, cd to it, then recurse
499              
500             elsif ( $file->is_directory() ) {
501 0           push @dirs, $file;
502             }
503              
504             #if it's a symlink, there's nothing we can do with it.
505             elsif ( $file->is_symlink() ) {
506              
507 0 0         if ( $options{SymlinkIgnore} ) {
508 0 0         print STDERR "Not doing anything to ", $filename,
509             " as it is a link.\n"
510             if $ftp->debug;
511              
512 0 0         if ( $options{RemoveLocalFiles} ) {
513 0 0         print STDERR q{Removing '}, $filename,
514             "' from the local system.\n"
515             if $ftp->debug;
516              
517 0           unlink $file->filename();
518             }
519             }
520             else {
521             # check to see what kind of file the link target is
522 0 0 0       if ( -f $filename and $options{SymlinkCopy} ) {
    0 0        
523 0 0 0       if ( $options{FlattenTree} and $filesSeen{$filename}) {
524 0 0         print STDERR "Sending $filename as ",
525             "$filename.$filesSeen{$filename}.\n"
526             if $ftp->debug;
527              
528 0           $put_success = $ftp->put( $filename,
529             "$filename.$filesSeen{$filename}" );
530              
531             } else {
532 0 0         print STDERR "Sending $filename.\n"
533             if $ftp->debug;
534              
535 0           $put_success = $ftp->put( $filename );
536             }
537              
538 0 0         $filesSeen{$filename}++ if $options{FlattenTree};
539              
540 0 0 0       if ( $put_success and $options{RemoveLocalFiles} ) {
541 0 0         print STDERR q{Removing '}, $filename,
542             "' from the local system.\n"
543             if $ftp->debug;
544              
545 0           unlink $file->filename();
546             }
547             }
548             elsif ( -d $file->filename() and $options{SymlinkFollow} ) {
549             #then it's a directory, we need to add it to the
550             #list of directories to grab
551 0           push @dirs, $file;
552             }
553             }
554             }
555              
556 0 0         $success .= "Had trouble putting $filename into $remote_pwd\n"
557             unless $put_success;
558              
559             }
560              
561 0           undef @files; #save memory in recursing.
562              
563             # we'll use an absolute path to chdir at the end.
564 0           my $local_pwd = Cwd::cwd();
565              
566 0           foreach my $file (@dirs) {
567              
568 0           my $filename = $file->filename();
569              
570 0 0         unless ( chdir $filename ){
571 0 0         print STDERR 'Could not change to the local directory ',
572             $filename, "!\n"
573             if $ftp->debug;
574              
575 0           $success .= 'Could not change to the local directory '
576             . qq{'$local_pwd/$filename'!\n};
577 0           next;
578             }
579              
580             # try to chdir to the remote path, if it's not possible,
581             # try to make the directory instead
582 0 0         unless( $ftp->cwd($filename) ){
583 0 0         print STDERR "Making dir: ", $filename, "\n"
584             if $ftp->debug;
585              
586 0 0         unless( $ftp->mkdir($filename) ){
587 0 0         print STDERR 'Could not make remote directory ',
588             $filename, "!\n"
589             if $ftp->debug;
590              
591 0           $success .= q{Could not make remote directory '}
592             . qq{$remote_pwd/$filename}
593             . qq{!\n};
594             }
595              
596 0 0         unless ( $ftp->cwd($filename) ){
597 0 0         print STDERR 'Could not change remote directory to ',
598             $filename, ", skipping!\n"
599             if $ftp->debug;
600              
601 0           $success .= qq{Could not change remote directory to '}
602             . qq{$remote_pwd/$filename}
603             . qq{'!\n};
604 0           next;
605             }
606             }
607              
608 0 0         print STDERR "Calling rput in ", $local_pwd, "\n"
609             if $ftp->debug;
610 0           $ftp->_rput();
611              
612             #once we've recursed, we'll go back up a dir.
613 0 0         print STDERR 'Returned from rput in ',
614             $filename, ".\n"
615             if $ftp->debug;
616              
617 0           $ftp->cdup;
618              
619 0 0         if ( $file->is_symlink() ) {
620 0           chdir $local_pwd;
621 0 0         unlink $filename if $options{RemoveLocalFiles};
622             } else {
623 0           chdir '..';
624 0 0         rmdir $filename if $options{RemoveLocalFiles};
625             }
626             }
627             }
628              
629              
630             sub rdir{
631 0     0 1   my($ftp) = shift;
632              
633 0           %options = ( ParseSub => \&parse_files,
634             OutputFormat => '%p %lc %u %g %s %d %f %l',
635             @_,
636             InitialDir => $ftp->pwd
637             ); #setup the options
638              
639 0 0         unless( $options{Filehandle} ) {
640 0           Carp::croak("You must pass a filehandle when using rdelete/rls!");
641             }
642              
643 0           local %dirsSeen = ();
644 0           local %filesSeen = ();
645              
646 0           $dirsSeen{$ftp->pwd}++;
647              
648 0           local $success = '';
649              
650 0           $ftp->_rdir;
651              
652 0           return $success;
653             }
654              
655             sub _rdir{
656 0     0     my $ftp = shift;
657              
658 0           my @ls = $ftp->dir;
659              
660 0 0         print STDERR join("\n", @ls) if $ftp->debug;
661              
662 0           my(@dirs);
663 0           my $fh = $options{Filehandle};
664 0 0         print $fh $ftp->pwd, ":\n" unless $options{FilenameOnly};
665              
666 0           my $remote_pwd = $ftp->pwd;
667 0           my $local_pwd = Cwd::cwd();
668              
669             LINE:
670 0           foreach my $line ( @ls ) {
671 0           my($file) = $options{ParseSub}->( $line );
672 0 0         next LINE unless $file;
673              
674 0           my $filename = $file->filename;
675              
676             # if it's a symlink that points to a directory, we need to
677             # check it for cycles, and then put it on the list of directories
678             # to examine
679              
680 0 0 0       if ( $file->is_symlink() and $ftp->cwd($filename) ) {
    0          
681 0           $ftp->cwd( $remote_pwd );
682              
683             #we need to resolve the link to an absolute path
684 0           my $remote_abs_path = path_resolve( $file->linkname,
685             $remote_pwd,
686             $filename );
687              
688 0           print STDERR qq{'$filename' got converted to '$remote_abs_path'.\n};
689              
690             #if it's a directory structure we've already seen,
691             #we'll just treat it as a regular file
692              
693             # OR
694              
695             #if it's in the same tree that we started
696             #downloading, we should get to it later, so we'll
697             #just treat it as a regular file
698              
699 0 0 0       unless ( $dirsSeen{$remote_abs_path}
700             or $remote_abs_path =~ m%^$options{InitialDir}% ){
701              
702             # Otherwise we need to grab the directory and put
703             # the info in a hash in case there is another link
704             # to this directory
705              
706 0           push @dirs, $file;
707 0           $dirsSeen{$remote_abs_path}++;
708              
709 0 0         if( $ftp->debug() ){
710 0           print STDERR q{Mapping '},
711             $remote_abs_path,
712             q{' to '},
713             $dirsSeen{$remote_abs_path},
714             ".\n";
715             }
716             }
717             }
718             elsif ( $file->is_directory() ) {
719 0           push @dirs, $file;
720              
721             #since we won't get to the code below, we need this
722             #code here
723 0 0 0       if ( $options{FilenameOnly} && $options{PrintType} ) {
724 0           print $fh $remote_pwd, '/', $filename, " d\n";
725             }
726              
727 0 0         next LINE if $options{FilenameOnly};
728             }
729              
730              
731 0 0         if( $options{FilenameOnly} ){
732 0           print $fh $remote_pwd, '/', $filename;
733 0 0         if ( $options{PrintType} ) {
734 0           my $filetype;
735 0 0         if ( $file->is_symlink() ) {
    0          
736 0           print $fh ' s';
737             } elsif ( $file->is_plainfile() ) {
738 0           print $fh ' f';
739             }
740             }
741 0           print $fh "\n";
742             }
743             else {
744 0           print $fh $line, "\n";
745             }
746             }
747              
748 0 0         print $fh "\n" unless $options{FilenameOnly};
749              
750 0           foreach my $dir (@dirs){
751 0           my $dirname = $dir->filename;
752              
753 0 0         unless ( $ftp->cwd( $dirname ) ){
754 0 0         print STDERR 'Was unable to cd to ', $dirname,
755             " in $remote_pwd, skipping!\n"
756             if $ftp->debug;
757 0           $success .= qq{Was unable to cd to '$remote_pwd/$dirname'\n};
758 0           next;
759             }
760              
761 0 0         print STDERR "Calling rdir in ", $remote_pwd, "\n"
762             if $ftp->debug;
763 0           $ftp->_rdir( );
764              
765             #once we've recursed, we'll go back up a dir.
766 0 0         print STDERR "Returned from rdir in ", $dirname, ".\n"
767             if $ftp->debug;
768              
769 0 0         if ( $dir->is_symlink() ) {
770 0           $ftp->cwd($remote_pwd);
771             }
772             else {
773 0           $ftp->cdup;
774             }
775             }
776             }
777              
778             sub rls{
779 0     0 1   my $ftp = shift;
780 0           return $ftp->rdir(@_, FilenameOnly => 1);
781             }
782              
783             #---------------------------------------------------------------
784             # CD to directory
785             # Recurse through all subdirectories and delete everything
786             # This will not go into symlinks
787             #---------------------------------------------------------------
788              
789             sub rdelete {
790              
791 0     0 1   my($ftp) = shift;
792              
793 0           %options = ( ParseSub => \&parse_files,
794             @_
795             ); #setup the options
796              
797 0           local $success = '';
798              
799 0           $ftp->_rdelete(); #do the real work here
800              
801 0           return $success;
802              
803             }
804              
805             sub _rdelete {
806              
807 0     0     my $ftp = shift;
808              
809 0           my @dirs;
810              
811 0           my @ls = $ftp->dir;
812              
813 0 0         print STDERR join("\n", @ls) if $ftp->debug;
814              
815 0           my $remote_pwd = $ftp->pwd;
816              
817 0           foreach my $line ( @ls ){
818 0           my($file) = $options{ParseSub}->($line);
819              
820             #just delete plain files and symlinks
821 0 0 0       if ( $file->is_plainfile() or $file->is_symlink() ) {
    0          
822 0           my $filename = $file->filename();
823 0           my $del_success = $ftp->delete($filename);
824              
825 0 0         $success .= qq{Had a problem deleting '$remote_pwd/$filename'!\n}
826             unless $del_success;
827             }
828             #otherwise, if it's a directory, we have more work to do.
829             elsif ( $file->is_directory() ) {
830 0           push @dirs, $file;
831             }
832             }
833              
834             #this will do depth-first delete
835 0           foreach my $file (@dirs) {
836 0           my $filename = $file->filename();
837              
838             #in case we didn't have permissions to cd into that
839             #directory
840 0 0         unless ( $ftp->cwd( $file->filename() ) ){
841 0 0         print STDERR qq{Could not change dir to $filename!\n}
842             if $ftp->debug;
843 0           $success .= qq{Could not change dir to '$remote_pwd/$filename'!\n};
844 0           next;
845             }
846              
847             #need to recurse
848 0 0         print STDERR 'Calling _rdelete in ', $ftp->pwd, "\n"
849             if $ftp->debug;
850 0           $ftp->_rdelete( );
851              
852             #once we've recursed, we'll go back up a dir.
853 0 0         print STDERR "Returned from _rdelete in ", $ftp->pwd, ".\n"
854             if $ftp->debug;
855 0           $ftp->cdup;
856              
857             ##now delete the directory we just came out of
858 0 0         $ftp->rmdir($file->filename())
859             or $success .= 'Could not delete remote directory "'
860             . qq{$remote_pwd/$filename}
861             . qq{"!\n};
862             }
863             }
864              
865             #-------------------------------------------------------------#
866             #
867             # read_current_directory()
868             #
869             # Used by the _rput() method to retrieve the list of local
870             # files to send to the remote server. This eliminates the need
871             # to use "ls" or "dir" to read the local directory and then parse
872             # the output from those commands.
873             #
874             #-------------------------------------------------------------#
875             sub read_current_directory {
876 0 0   0 0   opendir THISDIR, '.' or die "Couldn't open ", getcwd();
877              
878 0           my $path = getcwd();
879              
880 0           my @to_return;
881              
882 0           foreach my $file ( sort readdir(THISDIR) ){
883 0 0         next if $file =~ /^[.]{1,2}$/;
884              
885 0           my $file_obj;
886              
887             # checking for the symlink must come first; -d and -f can resolve
888             # to true if the link points to either a dir or a plain file
889 0 0         if( -l $file ){
    0          
    0          
890 0           $file_obj
891             = Net::FTP::Recursive::File->new(
892             'symlink' => 1,
893             filename => $file,
894             path => $path,
895             linkname => readlink($file),
896             );
897             }
898             elsif( -d $file ){
899 0           $file_obj = Net::FTP::Recursive::File->new(
900             directory => 1,
901             filename => $file,
902             path => $path,
903             );
904             }
905             elsif( -f $file ){
906 0           $file_obj = Net::FTP::Recursive::File->new(
907             plainfile => 1,
908             filename => $file,
909             path => $path,
910             );
911             }
912              
913 0 0         push @to_return, $file_obj if $file_obj;
914             }
915              
916 0           closedir THISDIR;
917              
918 0           return @to_return;
919             }
920              
921             #-------------------------------------------------------------------#
922             # Should look at all of the output from the current dir and parse
923             # through and extract the filename, date, size, and whether it is a
924             # directory or not
925             #
926             # The date should also have a time, so that if the script needs to be
927             # run several times in one day, it will grab any files that changed
928             # that day.
929             #-------------------------------------------------------------------#
930              
931             sub parse_files {
932 0     0 0   my(@to_return) = ();
933              
934 0           foreach my $line (@_) {
935 0 0         next unless $line =~ /^
936             (\S+)\s+ #permissions
937             \d+\s+ #link count
938             \S+\s+ #user owner
939             \S+\s+ #group owner
940             \d+\s+ #size
941             \w+\s+\w+\s+\S+\s+ #last modification date
942             (.+?)\s* #filename
943             (?:->\s*(.+))? #optional link part
944             $
945             /x;
946              
947 0           my($perms, $filename, $linkname) = ($1, $2, $3);
948              
949 0 0         next if $filename =~ /^\.{1,2}$/;
950              
951 0           my $file;
952 0 0         if ($perms =~/^-/){
    0          
    0          
953 0           $file = Net::FTP::Recursive::File->new( plainfile => 1,
954             filename => $filename );
955             }
956             elsif ($perms =~ /^d/) {
957 0           $file = Net::FTP::Recursive::File->new( directory => 1,
958             filename => $filename );
959             } elsif ($perms =~/^l/) {
960 0           $file = Net::FTP::Recursive::File->new( 'symlink' => 1,
961             filename => $filename,
962             linkname => $linkname );
963             } else {
964 0           next; #didn't match, skip the file
965             }
966              
967 0           push(@to_return, $file);
968             }
969              
970 0           return(@to_return);
971             }
972              
973             =begin blah
974              
975             This subroutine takes a path and converts the '.' and
976             '..' parts of it to make it into a proper absolute path.
977              
978             =end blah
979              
980             =cut
981              
982             sub path_resolve{
983 0     0 0   my($link_path, $pwd, $filename) = @_;
984 0           my $remote_pwd; #value to return
985              
986             #this case is so that if we have gotten to this
987             #symlink through another symlink, we can actually
988             #retrieve the correct files (make the correct
989             #symlink, whichever)
990              
991 0 0 0       if ( $linkMap{$pwd} and $link_path !~ m#^/# ) {
    0          
992 0           $remote_pwd = $linkMap{$pwd} . '/' . $link_path;
993             }
994              
995             # if it was an absolute path, just make sure there aren't
996             # any . or .. in it, and make sure it ends with a /
997             elsif ( $link_path =~ m#^/# ) {
998 0           $remote_pwd = $link_path;
999             }
1000              
1001             #otherwise, it was a relative path and we need to
1002             #prepend the current working directory onto it and
1003             #then eliminate any .. or . that are present
1004             else {
1005 0           $remote_pwd = $pwd;
1006 0           $remote_pwd =~ s#(?
1007 0           $remote_pwd .= $link_path;
1008             }
1009              
1010             #Collapse the resulting path if it has . or .. in it. The
1011             #while loop is needed to make it start over after each
1012             #match (as it will need to go back for parts of the
1013             #regex). It's probably possible to write a regex to do it
1014             #without the while loop, but I don't think that making it
1015             #less readable is a good idea. :)
1016              
1017 0           while ( $remote_pwd =~ s#(?:^|/)\.(?:/|$)#/# ) {}
1018 0           while ( $remote_pwd =~ s#(?:/[^/]+)?/\.\.(?:/|$)#/# ){}
1019              
1020             #the %linkMap will store as keys the absolute paths
1021             #to the links and the values will be the "real"
1022             #absolute paths to those locations (to take care of
1023             #../-type links
1024              
1025 0           $filename =~ s#/$##;
1026 0           $remote_pwd =~ s#/$##;
1027              
1028 0           $pwd =~ s#(?
1029 0           $linkMap{$pwd . $filename} = $remote_pwd;
1030              
1031 0           $remote_pwd; #return the result
1032             }
1033              
1034             =begin comment
1035              
1036             This subroutine takes two absolute paths and basically
1037             'links' them together. The idea is that all of the paths
1038             that are created for the symlinks should be relative
1039             paths. This is the sub that does that.
1040              
1041             There are essentially 6 cases:
1042              
1043             -Different root hierarchy:
1044             /tmp/testdata/blah -> /usr/local/bin/blah
1045             -Current directory:
1046             /tmp/testdata/blah -> /tmp/testdata
1047             -A file in the current directory:
1048             /tmp/testdata/blah -> /tmp/testdata/otherblah
1049             -Lower in same hierarchy:
1050             /tmp/testdata/blah -> /tmp/testdata/dir/otherblah
1051             -A higher directory along the same path (part of link abs path) :
1052             /tmp/testdata/dir/dir2/otherblah -> /tmp/testdata/dir
1053             -In same hierarchy, somewhere else:
1054             /tmp/testdata/dir/dir2/otherblah -> /tmp/testdata/dir/file
1055              
1056             The last two cases are very similar, the only difference
1057             will be that it will create '../' for the first rather
1058             than the possible '../../dir'. The last case will indeed
1059             get the '../file'.
1060              
1061             =end comment
1062              
1063             =cut
1064              
1065             sub convert_to_relative{
1066 0     0 0   my($link_loc, $realfile) = (shift, shift);
1067 0           my $i;
1068             my $result;
1069 0           my($new_realfile, $new_link, @realfile_parts, @link_parts);
1070              
1071 0           @realfile_parts = split m#/#, $realfile;
1072 0           @link_parts = split m#/#, $link_loc;
1073              
1074 0           for ( $i = 0; $i < @realfile_parts; $i++ ) {
1075 0 0         last unless $realfile_parts[$i] eq $link_parts[$i];
1076             }
1077              
1078 0           $new_realfile = join '/', @realfile_parts[$i..$#realfile_parts];
1079 0           $new_link = join '/', @link_parts[$i..$#link_parts];
1080              
1081 0 0 0       if( $i == 1 ){
    0 0        
    0          
    0          
1082 0           $result = $realfile;
1083             }
1084             elsif ( $i > $#realfile_parts and $i == $#link_parts ) {
1085 0           $result = '.';
1086             }
1087             elsif ( $i == $#realfile_parts and $i == $#link_parts ) {
1088 0           $result = $realfile_parts[$i];
1089             }
1090             elsif ( $i >= $#link_parts ) {
1091 0           $result = join '/', @realfile_parts[$i..$#realfile_parts];
1092             }
1093             else {
1094 0           $result = '../' x ($#link_parts - $i);
1095 0 0         $result .= join '/', @realfile_parts[$i..$#realfile_parts]
1096             if $#link_parts - $i > 0;
1097             }
1098              
1099 0           return $result;
1100             }
1101              
1102              
1103             package Net::FTP::Recursive::File;
1104              
1105 1     1   10 use vars qw/@ISA/;
  1         2  
  1         52  
1106 1     1   5 use Carp;
  1         2  
  1         248  
1107              
1108             @ISA = ();
1109              
1110             sub new{
1111 0     0     my $pkg = shift;
1112              
1113 0           my $self = { plainfile => 0,
1114             directory => 0,
1115             'symlink' => 0,
1116             @_
1117             };
1118              
1119 0 0         croak 'Must set a filename when creating a File object!'
1120             unless defined $self->{filename};
1121              
1122 0 0 0       if( $self->{'symlink'} and not $self->{linkname} ){
1123 0           croak 'Must set a linkname when creating a File object for a symlink!';
1124             }
1125              
1126 0           bless $self, $pkg;
1127             }
1128              
1129             sub linkname{
1130 0     0     return $_[0]->{linkname};
1131             }
1132              
1133             sub filename{
1134 0     0     return $_[0]->{filename};
1135             }
1136              
1137             sub is_symlink{
1138 0     0     return $_[0]->{symlink};
1139             }
1140              
1141             sub is_directory{
1142 0     0     return $_[0]->{directory};
1143             }
1144              
1145             sub is_plainfile{
1146 0     0     return $_[0]->{plainfile};
1147             }
1148              
1149             1;
1150              
1151             __END__