Project #1
As The title suggest, you will create a program that is used to
maintain an up-to-date copy (backup?) of files you are working on.
Any files in the work directory tree that are modified or new will
be copied to the destination tree replacing any files already
there.
Note:
- Project #1 does not automatically delete files.
- What does the "rsync" command do?
A Perl version of my maintenance/backup program is below. My Perl
code may be used as a resource/template/design/... for your program.
Project #2
Create a program that automatically deletes files in the destination (backup)
directory tree that are no longer in the work directory tree.
My Perl Program
#!/bin/perl -w
#===================================================================
# keep a source directory tree up to date
#
# 1. If there are files in the source tree that are newer that
# the destination tree, replace the destination tree file.
# 2. If a file in the source tree does not exist in the destination
# tree copy it to the destination tree.
# 3. A directory in the skip list (and all of its sub-directories)
# will not be processed (skipped).
#
# Note: This script will not create directories.
#
#===================================================================
use strict;
use FileHandle;
use DirHandle;
use File::Basename;
use File::Copy;
#-------------------------------------------------------------------
# global variables
#
# DELBUG debug flag
# DIR_P_COUNT count - directories processed
# DROOTDIR destination tree root directory
# FAILURE returned failure code
# FILE_A_COUNT count - files added to destination tree
# FILE_P_COUNT count - source files processed
# FILE_U_COUNT count - files updated in destination tree
# FORCEMODE force the copy of all files (don't test for latest)
# RDIRLIST a list of all of the directories in the source tree
# (relative to the source tree root directory)
# SKIPDIRS a list of directories to skip
# SROOTDIR source tree root directories
# STARTTIME script start time
# SUCCESS returned success code
# TESTMODE test mode flag
# VERBOSE verbose flag
#
#-------------------------------------------------------------------
# -- work computer
# -- copy files from the flash drive to C drive (desktop)
#my $SROOTDIR = 'U:/Secure-Web-Pages-Class'; # flash drive
#my $DROOTDIR = 'C:/Secure_Web_Pages-Class'; # C drive
# -- work computer
# -- copy files from C drive (desktop) to flash drive
#my $SROOTDIR = 'C:/Secure-Web-Pages-Class'; # C drive
#my $DROOTDIR = 'U:/Secure-Web-Pages-Class'; # flash drive
# -- home computer
# -- copy files from the flash drive to the C drive
#my $SROOTDIR = 'L:/Secure-Web-Pages-Class';
#my $DROOTDIR = 'C:/Secure-Web-Pages-Class';
# -- home computer
# -- copy files from the C drive to the flash drive
#my $SROOTDIR = 'C:/Secure-Web-Pages-Class';
#my $DROOTDIR = 'L:/Secure-Web-Pages-Class';
# -- copy files from work computer to AFS space
#my $SROOTDIR = 'C:/Secure-Web-Pages-Class'; # C drive
#my $DROOTDIR = 'T:/www/Secure-Web-Pages-Class'; # AFS
# -- copy files from flash drive to AFS space
#my $SROOTDIR = 'U:/Secure-Web-Pages-Class'; # flash drive
#my $DROOTDIR = 'T:/www/Secure-Web-Pages-Class'; # AFS
# directories to be skipped (by name)
my %SKIPDIRS = ('x', 1,
'java', 1,
'X', 1,
'instructor', 1,
'zz', 1);
# -- files to be skipped (by name)
my %SKIPFILENAMES = ('keep_dir_tree_up_to_date.pl', 1,
'Thumbs.db', 1);
# files to be skipped (by regexp pattern)
# (including expression delimiters)
my @SKIPFILEPATTERNS = ('\.vsd$');
#my @SKIPFILEPATTERNS = ();
my $DEBUG = 0;
my $DIR_P_COUNT = 0;
my $FAILURE = 0;
my $FILE_A_COUNT = 0;
my $FILE_P_COUNT = 0;
my $FILE_U_COUNT = 0;
my $FORCEMODE = 0;
my $MAKEDIRS = 0;
my @RDIRLIST = (''); # initial value '' so that the root
# source directory will be processed
my $STARTTIME = time;
my $SUCCESS = 1;
my $TESTMODE = 1;
my $ADDFILES = 0;
my $VERBOSE = 1;
#===================================================================
#===================================================================
# main
#===================================================================
#===================================================================
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# make sure the root directories do not end with a / or \ character
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$SROOTDIR =~ s/\/$//;
$DROOTDIR =~ s/\/$//;
$SROOTDIR =~ s/\\$//;
$DROOTDIR =~ s/\\$//;
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# process command line arguments
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (ProcessCommandLineArguments() != $SUCCESS) { exit 1; };
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# create a list of all of the source tree directories,
# skipping the directories in the skip list (and their sub-directories)
# the list values are the relative to the source root directory
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if ($VERBOSE) { print "\nCreating source tree directory list\n"; }
if (CreateSourceDirList($SROOTDIR) != $SUCCESS) { exit 1; }
if ($DEBUG > 1)
{
foreach (@RDIRLIST)
{
print "Relative source directory ($_)\n";
}
}
#-------------------------------------------------------------------
# verity that there is a destination directory for every
# source directory
#-------------------------------------------------------------------
if ($VERBOSE) { print "\nVerifying source directories have " .
"a matching destination directory\n"; }
if (VerifyDestDirsExist() != $SUCCESS) { exit 1; }
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# update each destination directory with files from its
# corresponding source directory
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if ($VERBOSE) { print "\nUpdating destination directories\n"; }
my $sdir; # source directory
foreach $sdir (@RDIRLIST)
{
if (UpdateDestDir($sdir) != $SUCCESS)
{
exit 1;
}
}
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# display processing statistics
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
print "\n";
print "---------------------------------------------------\n";
print "Source directory : $SROOTDIR\n";
print "Destination directory : $DROOTDIR\n";
print "Test mode : $TESTMODE\n";
print "Make dirs : $MAKEDIRS\n";
print "Directories processed : $DIR_P_COUNT\n";
print "Files processed : $FILE_P_COUNT\n";
print "Files added : $FILE_A_COUNT\n";
print "Files updated : $FILE_U_COUNT\n";
print "----------------------------------------------------\n";
print "\n";
exit 0;
#===================================================================
#===================================================================
# subroutines
#===================================================================
#===================================================================
#-------------------------------------------------------------------
# create a list of all of the source tree directories,
# skipping the directories in the skip list
# (and their sub-directories)
#-------------------------------------------------------------------
sub CreateSourceDirList
{
my $sdir = $_[0]; # source directory
my $dh = new DirHandle; # directory handle
my $e; # list entry
my $f; # file name
my @fl; # list of file names
my @fs; # sorted list of file names
if ($DEBUG) { print "CreateSourceDirList($sdir)\n"; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get a list of all of the files in the source directory
# (this includes other directories)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (! opendir($dh,$sdir))
{
print "\nCan't open $sdir: $!\n\n";
return $FAILURE;
}
@fl = readdir $dh;
closedir $dh;
# sort the list of file names
@fs = sort @fl;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# process each sub-directory in the current directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
foreach $e (@fs)
{
if ($e eq '.') { next; };
if ($e eq '..') { next; };
$f = $sdir . '/' . $e;
if (-d $f)
{
if (defined $SKIPDIRS{$e})
{
if ($VERBOSE) { print "\nSkipping directory $e\n"; }
next;
}
$f =~ s/^$SROOTDIR//; # remove the root dir from complete
# path and (dir) file name leaving a
# relative path and (dir) file name
push @RDIRLIST,$f;
}
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# now process every sub-directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
foreach $e (@fs)
{
if ($e eq '.') { next; };
if ($e eq '..') { next; };
$f = $sdir . '/' . $e;
if (-d $f)
{
if (defined $SKIPDIRS{$e}) { next; }
if (CreateSourceDirList($f) != $SUCCESS)
{
return $FAILURE;
}
}
}
return $SUCCESS;
}
#-------------------------------------------------------------------
# verity that there is a destination directory for every
# source directory
#-------------------------------------------------------------------
sub VerifyDestDirsExist
{
if ($DEBUG) { print "VerifyDestDirsExist()\n"; }
my $ddir; # destination directory
my $rdir; # relative directory
my $sdir; # source directory
foreach $rdir (@RDIRLIST)
{
$sdir = $SROOTDIR . $rdir;
$ddir = $DROOTDIR . $rdir;
if (! -e $ddir)
{
if ($MAKEDIRS)
{
if (mkdir($ddir))
{
print "Directory created: $ddir\n";
next;
}
print "\nError: conable to create directory\n";
print "Dir: $ddir\n";
print "$!\n";
return $FAILURE;
}
print "\nError: source directory does not " .
"have a matching destination directory\n";
print " Source: $sdir\n";
print " Destination: $ddir\n";
return $FAILURE;
}
}
return $SUCCESS;
}
#-------------------------------------------------------------------
# update a destination directory
#-------------------------------------------------------------------
sub UpdateDestDir
{
my $rdir = $_[0]; # relative source directory
if ($DEBUG) { print "UpdateDestDir($rdir)\n"; }
$DIR_P_COUNT++;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# define the source and destination directories from the
# relative source directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
my $sdir = $SROOTDIR . $rdir; # source directory
my $ddir = $DROOTDIR . $rdir; # destination directory
if ($VERBOSE)
{
print "Processing: $sdir\n";
}
if ($VERBOSE == 3)
{
print "Updating destination: $ddir\n";
print " source : $sdir\n";
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get a list of all of the files in the source directory
# (this includes other directories)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
my $dh = new DirHandle; # directory handle
my $df; # destination file name and path
my $e; # list entry
my $sf; # source file name and path
my @fl; # list of file names
my @fs; # sorted list of file names
if (! opendir($dh,$sdir))
{
print "\nCan't open $sdir: $!\n\n";
return $FAILURE;
}
@fl = readdir $dh;
closedir $dh;
# sort the list of file names
@fs = sort @fl;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# process each file in the source directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
my $supdate; # source file update date
my $dupdate; # destination file update date
my $t; # delta time value
my $p; # pattern
foreach $e (@fs)
{
if ($e eq '.') { next; };
if ($e eq '..') { next; };
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# special case for Perl editor files
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if ($e =~ /^perl5db\.pl$/) { next; }
if ($e =~ /\.pltemp\.pl$/) { next; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# create source and destination file (path and name)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$sf = $sdir . '/' . $e;
$df = $ddir . '/' . $e;
if (-d $sf) { next; } # skip directories
$FILE_P_COUNT++;
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# skip the file (by name) ?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (exists $SKIPFILENAMES{$e})
{
if ($VERBOSE > 1) { print " S: $sf\n"; }
next;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# skip the file by regexp pattern match ?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (SkipFileByPattern($e)) { next; }
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# if in force mode, bypass test file update dates and
# copy all files to the destination directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if ($FORCEMODE)
{
if (! $TESTMODE)
{
if (copy($sf,$df) != 1)
{
print "\nError: copying file, $!\n";
print " Src: $sf\n";
print " Dst: $df\n";
return $FAILURE;
}
}
$FILE_A_COUNT++;
if ($VERBOSE) { print " A: $sf\n"; }
next;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# if the file does not exist in the destination directory,
# copy the file to the destination directory
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (! -e $df)
{
if (! $TESTMODE)
{
if (copy($sf,$df) != 1)
{
print "\nError: copying file, $!\n";
print " Src: $sf\n";
print " Dst: $df\n";
return $FAILURE;
}
}
$FILE_A_COUNT++;
if ($VERBOSE) { print " A: $sf\n"; }
next;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# if the file exist in the destination directory,
# and the source file is newer that the destination file,
# copy the file to the destination directory
#
# Note: There is a 2 second fudge factor built into the code.
# This will hopefully mask differences between the system
# clocks.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$supdate = (stat($sf))[9];
$dupdate = (stat($df))[9];
$t = abs($dupdate - $supdate);
if ($t <= 2) { next; }
if ($ supdate > $dupdate)
{
if (! $TESTMODE)
{
if (copy($sf,$df) != 1)
{
print "\nError: copying file, $!\n";
print " Src: $sf\n";
print " Dst: $df\n";
print " Src update: $supdate\n";
print " Dst update: $dupdate\n";
return $FAILURE;
}
}
$FILE_U_COUNT++;
if ($VERBOSE) { print " U: $sf\n"; }
if ($VERBOSE == 3)
{
print " Src: $supdate\n";
print " Dst: $dupdate\n";
print " Del: $t (sec)\n";
}
}
}
return $SUCCESS;
}
#-------------------------------------------------------------------
# match filename to skip regexp patterns
#-------------------------------------------------------------------
sub SkipFileByPattern
{
my $f = $_[0]; # filename
my $p; # filename pattern
foreach $p (@SKIPFILEPATTERNS)
{
if ($f =~ /$p/) { return 1; }
}
return 0;
}
#-------------------------------------------------------------------
# process command line arguments
#-------------------------------------------------------------------
sub ProcessCommandLineArguments
{
my $e; # command line argument
my $ee; # command line argument (uppercase)
for $e (@ARGV)
{
$ee = $e;
$ee =~ tr/a-z/A-Z/;
if ($ee eq "DEBUG") { $DEBUG = 1; next; }
if ($ee eq "DEBUG1") { $DEBUG = 1; next; }
if ($ee eq "DEBUG2") { $DEBUG = 2; next; }
if ($ee eq "DEBUG3") { $DEBUG = 3; next; }
if ($ee eq "DEBUG4") { $DEBUG = 4; next; }
if ($ee eq "FORCE") { $FORCEMODE = 1; next; }
if ($ee eq "FORCEMODE") { $FORCEMODE = 1; next; }
if ($ee eq "VERBOSE") { $VERBOSE = 1; next; }
if ($ee eq "VERBOSE1") { $VERBOSE = 1; next; }
if ($ee eq "VERBOSE2") { $VERBOSE = 2; next; }
if ($ee eq "VERBOSE3") { $VERBOSE = 3; next; }
if ($ee eq "COPY") { $TESTMODE = 0; next; }
if ($ee eq "UPDATE") { $TESTMODE = 0; next; }
if ($ee eq "NOTEST") { $TESTMODE = 0; next; }
}
return $SUCCESS;
}