Listing 5: mirror_put
Recursive Traversal of an FTP Site
The Perl Journal, Winter 1999
 

Listing 5: mirror_put


#!/usr/bin/perl -w
# Transfers local file tree to the remote machine.
#     usage: mirror_put [-d -D] host localdir remotedir name password
#        -d  Debug mode - don't actually transfer anything.
#        -D  Turn on Net::FTP debug messages
# NOTES
#    remotedir must already exist on the remote server

use Getopt::Std;
use Net::FTP;
use Cwd;

getopts("dD");
defined($opt_d) or $opt_d = 0;
defined($opt_D) or $opt_D = 0;

my $host      = shift;
my $localdir  = shift;
my $remotedir = shift;
my $name      = shift;
my $password  = shift;

defined($host)        and 
  defined($localdir)  and 
  defined($remotedir) and 
  defined($name)      and 
  defined($password)  or 
  die "usage: $0 [-D] host localdir remotedir name password";

$ftp = Net::FTP->new($host, Debug => $opt_D ? 1 : 0)
    or die "ERROR: Net::FTP->new() failed\n"; 
$ftp->login($name, $password) or die "ERROR: login() failed\n";
$ftp->binary;     # Assume binary transfers.

# Go to the starting point in the local tree.
chdir($localdir) or die "ERROR: can't cwd() to $localdir\n";  

# Go to the starting point in the remote tree.
$ftp->cwd($remotedir) or die "ERROR: can't cwd() to $remotedir\n";  

# Keep track of directory path as separate elements to 
# facilitate mirroring of directory paths on remote system.
my @path;

finddepth_gl(\&process_item, 1);    # Crawl over the local tree

$ftp->quit;

sub process_item {
    my ($level, $isdir, $item, $parent) = @_;
    foreach (1..$level) { print "   " }
    if ($isdir) {
        print "DIRECTORY: ",$parent,$item,"\n";
        $ftp->mkdir($parent.$item) or die "ERROR: can't mkdir ",$parent,$item,"\n";
    } else {
        print "FILE: ",$item,"\n";
        my $save_remote = $ftp->pwd;
        $ftp->cwd($parent) or die "ERROR: can't cwd() to $parent\n";  
        $ftp->put($item);
        $ftp->cwd($save_remote) or die "ERROR: can't cwd() to $save_remote\n";
    }    
}

sub finddepth_gl {
    my ($callback, $level) = @_;

    # Make a listing of files and/or directories.
    my $cwd = cwd;
    opendir(DIR, $cwd) or die "ERROR: can't opendir() on $cwd: $!\n";
    
    my ($item, @list);
    while (defined($item = readdir(DIR))) { push(@list, $item)}
    closedir(DIR);   
    
    foreach $item (@list) {
        next if $item =~ /\.\.?$/;
        my $parent = "";
        my $i;
        foreach $i (@path) { ($parent .= $i."/" }
        if ( -d $item) {
            # It's a directory — call the callback.
            &$callback($level, 1, $item, $parent);
            
            # Recursively crawl the subtree under this directory.
            push(@path, $item);
            my $save_local = cwd;
            chdir($item) or die "ERROR: can't chdir() to $item\n";
            finddepth_gl($callback, $level+1);

            # Restore location in local tree.
            chdir($save_local) or die "ERROR: can't chdir() to $save_local\n";
            pop(@path);
        } elsif ( -f $item) {
            # It's a file - call the callback.
            &$callback($level, 0, $item, $parent);
        }
    }
}