I wrote this program in PERL several years ago to automate syncronization of the hard drives that hold my music library. It works well, not only for music files, but any files that you might wish to copy from one hard drive to another. See the comments in the code for more details.
#
# Author: Tommy’s Tunes Software
#
# Description:
#
# This program synchronizes a pair of directories. One is called the source, and
# the other is called the destination directory. The act of syncronizing
# accomplishes the following steps:
# 1) Creates any directories and subdirectories in the destination
# directory which are found in the source directory but not the
# destination.
# 2) Deletes any directories and subdirectories found in the destination
# directory but not found in the source directory.
# 3) Copies any files from the source to the destination directory
# (including files in subdirectories) that, either
# a) are found in the source but not the destination, or
# b) have differing modification time stamps. That is, any
# modified files since the last synchronization in the source
# are copied to the destination.
#
# Set the source and destination directories. Make sure that each of these has
# a trailing slash.
$gSrcDir = ‘h:\\’; # Source Directory
$gDestDir = ‘g:\\’; # Destination Directory
# Read the contents of the source and destination directories into lists.
print “Reading source directory: $gSrcDir . . . “;
my $srcFileListRef = {};
ReadDirectory($gSrcDir, $gSrcDir, $srcFileListRef);
print “Done.\n”;
print “Reading destination directory: $gDestDir . . . “;
my $destFileListRef = {};
ReadDirectory($gDestDir, $gDestDir, $destFileListRef);
print “Done.\n”;
# Compare the directories and note any files (or directories) that exist
# in one but not the other.
print “Synchronizing the source and destination directories. . .\n”;
SyncDirectories($gSrcDir, $srcFileListRef, $gDestDir, $destFileListRef);
print “Done.\n”;
exit 0;
################################
# Copies a directory or a file (whose complete path is supplied in the $itemToCopy
# parameter) to the destination directory as specified in the $destDir parameter.
#
sub CopyItem
{
my $srcDir = shift;
my $itemToCopy = shift;
my $itemMode = shift;
my $destDir = shift;
# If the item is a file, then copy the file. If it’s a directory, then
# copy the directory.
my $destItemPath = $destDir . “\\” . $itemToCopy;
my $entryType = $itemMode & 040000;
if($entryType)
{
print “Making directory $destItemPath\n”;
system(“mkdir”, “$destItemPath”);
}
else
{
my $srcItemPath = $srcDir . “\\” . $itemToCopy;
print “Copying file $srcItemPath to $destDir\n”;
system(“copy”, $srcItemPath, $destItemPath);
}
}
################################
# Deletes a directory or a file (whose relative path is supplied in the
# $itemToDelete parameter) fromthe destination directory as specified in the
# $destDir parameter.
#
sub DeleteItem
{
my $destDir = shift;
my $itemToDelete = shift;
my $itemMode = shift;
# If the item is a file, then delete the file. If it’s a directory, then
# remove the directory.
# Also, if the destination directory is the root directory of a drive
# (its path ends with a \ character), then do not include a backslash
# between the destination directory and the relative path to the file
# to delete. Otherwise, include the backslash.
my $destItemPath;
my $ch = substr($destDir, ((length $destDir) – 1));
if($ch eq “\\”)
{
$destItemPath = $destDir . $itemToDelete;
}
else
{
$destItemPath = $destDir . “\\” . $itemToDelete;
}
my $entryType = $itemMode & 040000;
if($entryType)
{
print “Deleting directory $destItemPath\n”;
# Remove the directory using rmdir with /S to remove all subdirectories
# and files in the target directory, and /Q to keep rmdir from asking
# whether or not to delete a particular item.
system(‘rmdir’, “/S/Q”, $destItemPath);
}
else
{
print “Deleting file $destItemPath from $destDir\n”;
system(“del”, “\”$destItemPath\”");
}
}
##################################
#
sub SyncDirectories
{
my $srcDir = shift;
my $srcFileListRef = shift;
my $destDir = shift;
my $destFileListRef = shift;
my $item;
my $key;
# Check for any files in the destination list but not in the src list, and
# delete those files from the destination directory. We use the ‘reverse’
# command here so that the files in a directory are deleted first, and then
# the directory itself. This step should come before new files are
# copied to the destination, to assure that the most space will be
# available for the new files (that vacated by the old files).
print “Deleting old items from the destination directory.\n”;
foreach $key (reverse sort keys %$destFileListRef)
{
if(!exists $srcFileListRef->{$key})
{
# Delete the file.
DeleteItem($destDir, $key, $destFileListRef->{$key}->{MODE});
delete $destFileListRef->{$key};
}
} # for(…)
# Check for
# - Any files in the src list but not in the destination list OR
# - Files that do exist in both lists but whose modification time stamps
# differ.
#
# copy those that meet either of the above conditions from the source to the
# destination directory.
#
print “Copying new items from source to destination directory.\n”;
foreach $key (sort keys %$srcFileListRef)
{
# print “Key = $key\n”;
# If the file does not exist in the destination directory, then
# copy if there from the source directory.
if(!exists($destFileListRef->{$key}))
{
# Copy the file, if a file, or create the directory in the
# destination, if a directory.
$item = $srcFileListRef->{$key}->{RPATH};
CopyItem($srcDir,
$item,
$srcFileListRef->{$key}->{MODE},
$destDir);
$destFileListRef->{$key}->{MODE} = $srcFileListRef->{$key}->{MODE};
$destFileListRef->{$key}->{MTIME} = $srcFileListRef->{$key}->{MTIME};
$destFileListRef->{$key}->{RPATH} = $srcFileListRef->{$key}->{RPATH};
$destFileListRef->{$key}->{SIZE} = $srcFileListRef->{$key}->{SIZE};
}
} # for(…)
# Check for any files in the src directory whose modification timestamp or
# size differs from the corresponding file in the destination directory, and
# copy the file from the source to the destination if a difference is found.
print “Copying modified items from source to destination directory.\n”;
foreach $key (sort keys %$srcFileListRef)
{
if($key =~ /RECYCLER/)
{
next;
}
my $entryType = $srcFileListRef->{$key}->{MODE} & 040000;
if( ($srcFileListRef->{$key}->{MTIME} != $destFileListRef->{$key}->{MTIME}) &&
(!$entryType))
{
# Copy the file, if a file,
$item = $destFileListRef->{$key}->{RPATH};
CopyItem($srcDir,
$item,
$srcFileListRef->{$key}->{MODE},
$destDir);
$destFileListRef->{$key}->{MODE} = $srcFileListRef->{$key}->{MODE};
$destFileListRef->{$key}->{MTIME} = $srcFileListRef->{$key}->{MTIME};
$destFileListRef->{$key}->{RPATH} = $srcFileListRef->{$key}->{RPATH};
$destFileListRef->{$key}->{SIZE} = $srcFileListRef->{$key}->{SIZE};
}
} # for(…)
}
#############################
#
sub ReadDirectory
{
# Open the directory.
my $dir = shift;
my $baseDir = shift;
my $fileListRef = shift;
my $currentFile;
my $dirHandle;
my $filePath;
my $listEntry;
opendir $dirHandle, $dir ||
die “Unable to open directory: $dir\n”;
while($currentFile = readdir $dirHandle)
{
if(($currentFile eq “.”) or
($currentFile eq “..”) or
($currentFile =~ /RECYCLER/))
{
next;
}
$filePath = $dir . “\\” . $currentFile;
# Get information about each file read.
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
$atime, $mtime, $ctime, $blksize, $blocks) =
stat $filePath;
# Remove the base directory (if any) from the file path, making it
# a relative path.
my $relativeFilePath = substr($filePath, (length $baseDir) + 1);
my $key = lc $relativeFilePath;
# print “$key\n”;
$fileListRef->{$key}->{MODE} = $mode;
$fileListRef->{$key}->{MTIME} = $mtime;
$fileListRef->{$key}->{RPATH} = $relativeFilePath;
$fileListRef->{$key}->{SIZE} = $size;
# print “Relative path: $fileListRef->{$key}->{RPATH} \n”;
my $entryType = $mode & 040000;
if($entryType)
{
# print “===== Processing subdirectory: $filePath ===========\n”;
ReadDirectory($filePath, $baseDir, $fileListRef);
}
else
{
# print “===== Processing file: $filePath with type: “;
# printf “%o”, $entryType;
# print ” =====\n”;
}
# print “=============== Done ====================\n”;
} # End while
closedir $dirHandle;
} # End ReadDirectory()
__END__