#!/usr/bin/perl

use strict;
use File::Find;
use File::Spec;

use vars qw( $VERSION );

$VERSION = 0.10;

die "Usage: $0 source dest\n" unless $#ARGV == 1;

# We have to store the times for the directories, and update them
# after we've finished copying the directory contents.
my @dir_times;

my $source_path = shift @ARGV;
my $destination_path = shift @ARGV;
my $starting_directory = `pwd`;
chomp $starting_directory;

if (-d $source_path)
{
  die "$destination_path must be a directory"
    if -e $destination_path && !-d $destination_path;

  find(\&wanted, $source_path);
}
else
{
  if (-d $destination_path)
  {
    my $dest;
    my $dest_file = (File::Spec->splitpath( $source_path, 0 ))[2];
    $destination_path = File::Spec->catfile($destination_path, $dest_file);

    copy_file_ex($source_path,$destination_path);
  }
  else
  {
    copy_file_ex($source_path,$destination_path);
  }
}

foreach my $time_info (@dir_times)
{
  utime $time_info->{atime}, $time_info->{mtime}, $time_info->{dir};
}

exit;

# ------------------------------------------------------------------------

sub wanted
{
  return if $_ eq File::Spec->updir() or $_ eq File::Spec->curdir();

  my $filename = $_;

  my @source_dirs = File::Spec->splitdir($source_path);
  my @file_dirs = File::Spec->splitdir($File::Find::dir);

  while (defined $file_dirs[0] && $file_dirs[0] eq $source_dirs[0])
  {
    shift @file_dirs, shift @source_dirs
  }

  my $relative_path;
  $relative_path = File::Spec->catdir(@file_dirs);

  my $dest;
  $dest = File::Spec->catdir($destination_path, $relative_path);
  $dest = File::Spec->catfile($dest, $filename);

  my $current_directory = `pwd`;
  chomp $current_directory;
  
  chdir $starting_directory;
  copy_file_ex($File::Find::name, $dest);
  chdir $current_directory;
}

# ------------------------------------------------------------------------

sub copy_file_ex
{
  my ($src, $dest) = @_;

#print qq{Copying "$src" to "$dest"\n};
  my $srctime = (stat($src))[9];
  my $srcmode = (stat($src))[2];

  my $destmode;

  if (-e $dest) {
      $destmode = (stat($dest))[2];
      chmod(0666, $dest);
  } else {
      # use dest file mode if avail, else use source file's mode
      $destmode = $srcmode;
  }

  my $src_directory = (File::Spec->splitpath( $src, 0 ))[1];
  my $dest_directory = (File::Spec->splitpath( $dest, 0 ))[1];

  my $directory_atime = (stat($src_directory))[8];
  my $directory_mtime = (stat($src_directory))[9];
  # Permissions are the low bits of the mode
  my $directory_perms = (stat($src_directory))[2] & 0777;

  if (!-e $dest_directory)
  {
    mkdir ( $dest_directory, $directory_perms ) or
      die("Couldn't create directory: $dest_directory: $!");
    push @dir_times, {dir => $dest_directory, atime =>
      $directory_atime, mtime => $directory_mtime};
  }

  unless (-d $src)
  {
    my $old_mask = umask;
    umask(0000);

    if (-e $dest)
    {
      # Not foolproof, since the file may already exist.
      my $tempfile = "$dest.$$.tmp";

      rename $dest,$tempfile;
      system qq{cp "$src" "$dest"};
      unlink $tempfile;
    }
    else
    {
      system qq{cp "$src" "$dest"};
    }

    utime $srctime, $srctime, $dest;
    chmod $destmode, $dest;

    umask($old_mask);
  }
}
