#!/opt/bin/perl -w

# "quickie" script (how come these quickie scripts are never all that quick?)
#  to safely merge mailboxes (where "safely" means that the procmail 'lockfile'
#  utility and temporary files are used to prevent collisions with procmail
#  while we make changes to mailboxes. 

use strict;

my $pwd = `pwd`;
die "Failed to get cwd.\n" if $?;
chomp $pwd;
my $me = getpwuid( $< );
my $myinbox = "/var/mail/$me";
my $locker = "/opt/procmail/bin/lockfile";

my $cull = 0;
my $errors = 0;
if ( $ARGV[0] and ( $ARGV[0] =~ m/^-/ ) )
{
   if    ( $ARGV[0] eq "--cull" )  {  ( $cull = 1 );  shift @ARGV;  }
   elsif ( $ARGV[0] =~ m/^-h$/ or $ARGV[0] =~ m/^--help$/ )  {  $errors++;  } 
   else  {  print "\nillegal command line switch $ARGV[0]\n";  $errors++; }
}

die "\nusage: $0 [--cull] [path/]mailbox [[path/]mailbox2]\n\n"
   if (@ARGV < 1 or @ARGV > 2 or $errors );

my $box1 = shift @ARGV;
my $box2 = shift @ARGV || "";
chomp $box1;
chomp $box2;
my $inbox = 0;
$box2 = $myinbox unless ( $box2 );
$box1 = "$pwd/$box1" unless $box1 =~ m/\//;
$box2 = "$pwd/$box2" unless $box2 =~ m/\//;
$inbox = 1 if ( $box1 eq "$myinbox" );
$inbox = 2 if ( $box2 eq "$myinbox" );
my $box1orig = $box1;
my $box2orig = $box2;

print "operating on:\n        box1: $box1\n        box2: $box2\n";

die "\nno such mailbox:\n              $box1\n\n" unless ( -e "$box1" );
die "\nno such mailbox:\n              $box2\n\n" unless ( -e "$box2" );
die "\ncan't merge file with itself:\n" .
      "              $box1\n" .
      "              $box2\n\n" if ( $box1 eq $box2 );

print "moving boxes to temporary locations:\n";

foreach ( $box1, $box2 )
{
   my $tmp = "";
   my $tmpbox = "$_.tmp";
   while ( -e "$tmpbox$tmp" )
   {
      $tmp ++;
      die ( "Too many temp files:\n" .
            "              $tmpbox\[0-9]\n\n" ) if $tmp == 10;
   }
   $tmpbox .= $tmp;
   undef $tmp;

   if ( ( $inbox == 1 ) and ( $box1 eq $_ ) or
        ( $inbox == 2 ) and ( $box2 eq $_ ) ) 
      {  `$locker -1 -r10 -ml`;  }
   else 
      {  `$locker -1 -r10 $_.lock`;   }
   die "Failed to obtain lock:\n" .
       "              $_.lock\nNo changes were made.\n\n" if $?;
   `touch $_` unless ( -e $_ );
   die "Unable to touch:\n   $_\n\n" if $?;
   rename "$_", "$tmpbox"
      and print "        moved $_\n           to $tmpbox\n"
         or die "Couldn't move $_\n           to $tmpbox\n";
   if ( ( $inbox == 1 ) and ( $box1 eq $_ ) or
        ( $inbox == 2 ) and ( $box2 eq $_ ) )
      {  `$locker -mu`;  }
   else
      {  unlink "$_.lock";   }
   die "Failed to release lock:\n" .
       "              $_.lock\n" .
       "              $_ was moved\n " .
       "           to $tmpbox\n\n" if $?;
   $box1 = $tmpbox if ( $box1 eq $_ );
   $box2 = $tmpbox if ( $box2 eq $_ );
}

print "really operating on:\n        box1: $box1\n        box2: $box2\n\n";

print "culling is ";
print $cull ? "on\n\n" : "off\n\n";

my $tmp = "";
my $newbox;
$box1orig =~ m/^(.*\/)(.+)$/;
$newbox = "/var/mail/$me-$2-";
$box2orig =~ m/^(.*\/)(.+)$/;
$newbox .= "$2.merged";
while ( -e "$newbox$tmp" )
{
   $tmp ++;
   die "Too many merge files:\n              $newbox\[0-9]\n\n" if $tmp == 10;
}
$newbox = "$newbox$tmp";
undef $tmp;

# if we're culling duplicate messages, then we'll need to read both mailboxes
#  and check for duplicates using two arrays as follows:
#   a) read both inboxes into array A
#   b) find the beginning of a message in array A and find its Message-Id field
#   c) check for a match in the hash of Message-Id fields.  If there is one,
#       skip this message and go back to (b).  If there isn't one, push this
#       message into array B, add its Message-Id to the hash as a KEY, then
#       go back to (b) - exception: if it's the "DON'T DELETE ..." message,
#       just discard it (ironic, eh?)
#   d) if, for some reason, a message doesn't have a Message-Id field, push it
#       into array B (just to be safe)

if ( $cull )
{
   print "Performing message-by-message merge of\n" .
         "              $box1\n" .
         "          and $box2\n" .
         "         into $newbox\n\n";

   open BOX1, "< $box1" or die "Couldn't open:\n" .
              "              $box1\nfor reading.\n\n";
   open BOX2, "< $box2" or die "Couldn't open:\n" .
              "              $box2\nfor reading.\n\n";

   my ( @inlist, @outlist );
   my %id;
 
   foreach( <BOX1>, <BOX2> )  {  push @inlist, $_;  }

   close BOX1;
   close BOX2;

   my $i = 0;
   while ( $inlist[$i] )
   {
      if ( $inlist[$i] =~ m/^From\s/ )
      {
         my $copy = 1;

         my $candidate;
         my $j = $i + 1;
         while ( $inlist[$j] and ( $inlist[$j] !~ m/^From\s/ ) )
         {
            ( $candidate = $1 ) and last
               if ( $inlist[$j] =~ m/^Message-Id: (.*)$/ );
            $j++;
         }

         if ( defined $candidate )  {  print "Processing Message-ID: $candidate\n"  }
         else                       {  print "Processing message with no Message-ID\n";  }

         $j = $i + 1;
         while ( $inlist[$j] and ( $inlist[$j] !~ m/^From\s/ ) )
         {
            if ( $inlist[$j] =~ m/^Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA\s*$/ )
            {
               $copy = 0;
               print "   Discarding \"DON\'T DELETE THIS MESSAGE\" message\n";
               last;
            }
            $j++;
         }

         if( $copy )
         {
            unless( defined $candidate and defined $id{ $candidate } )
            {
               $id{ $candidate } = "" if defined $candidate;
               push @outlist, $inlist[$i];
            }

            else
            {
               print "   Discarding duplicate message with ID $candidate\n";
               $copy = 0;
            }

         }

         $i++;
         while( $inlist[$i] and ( $inlist[$i] !~ m/^From\s/ ) )
         {
            push @outlist, $inlist[$i] if $copy;
            $i++;
         }
      }
      else  {  $i++;  }
   }

   open ( BOX3, ">$newbox" ) or die "Couldn't open:\n" .
                                    "              $newbox\nfor writing.\n";
   foreach ( @outlist )  {  print BOX3;  }
   close BOX3;
}

# if we're not culling repeated messages, simply cat the boxes together

else
{
   print "Concatenating $box1\n" .
         "          and $box2\n" .
         "         into $newbox\n";
   `cat $box1 $box2 >> $newbox`;
   die "Failed to concatenate:\n" .
       "              $box1\n" .
       "          and $box2\n" .
       "         into $newbox\n" if $?;
}

# figure out possible names for backup files as we shuffle things around,
#  bearing in mind that we might have moved the inbox (sheesh!)

$tmp = "";
my $box1bk = "$box1orig.bak";
while ( -e "$box1bk$tmp" )
{
   $tmp ++;  $tmp = sprintf ( "%02i", $tmp );
   die ( "Too many backup files:\n" .
         "              $box1bk\[00-99]\n\n" ) if $tmp == 100;
}
$box1bk .= $tmp;

$tmp = "";
my $box2bk = "$box2orig.bak";
while ( -e "$box2bk$tmp" )
{
   $tmp ++;  $tmp = sprintf ( "%02i", $tmp );
   die ( "Too many backup files:\n" .
         "              $box2bk\[00-99]\n\n" ) if $tmp == 100;
}
$box2bk .= $tmp;
undef $tmp;

# prompt the user to determine what they want to do with the merged mailbox

my $choice = 0;

until ( $choice )
{
   print "\nMerged boxes  $box1\n" .
           "          and $box2\n" .
           "         into $newbox\n\n";
   print "Do you want to:\n";
   print "  (1) replace $box1orig\n         with $newbox\n";
   print "      (moving $box1orig\n           to $box1bk),\n";
   print "      leaving $box2orig intact.\n";
   print "  (2) replace $box2orig\n         with $newbox\n";
   print "      (moving $box2orig\n           to $box2bk),\n";
   print "      leaving $box1orig intact.\n";
   print "  (n) replace nothing ( merged box will be left as\n" .
         "              $newbox)\n\n";
   print "Please choose (1,2,n): ";

   $choice = lcfirst <STDIN>;

   print "\nInvalid choice.  Please type '1', '2', or 'n'." .
         "  (without the quotes).\n"
      unless ( $choice =~ m/^[12n]$/ );
}
print "\n";

# handle shuffling of files

my ( $source, $target );

chomp $choice;
unless ( $choice eq "n" )
{

# first, rename the file that's getting replaced by the
#  merged file to a backup location

   $source = $box1 if $choice == 1;
   $source = $box2 if $choice == 2;
   $target = $box1bk if $choice == 1;
   $target = $box2bk if $choice == 2;

   rename $source, $target
      and print "Moved         $source\n" .
                "           to $target\n"
         or die "Failed to move:\n" .
                "              $source\n" .
                "           to $target\n\n";

# now, rename the merged file to the original filename

   $target = $source;
   $source = $newbox;

#  rename $source, $target
#      and print "Moved         $source\n" .
#                "           to $target\n"
#         or die "Failed to move:\n" .
#                "              $source\n" .
#                "           to $target\n\n";
   `mv $source $target`;
   die "Failed to move:\n" .
       "              $source\n" .
       "           to $target\n\n" if $?;
   print "Moved         $source\n" .
         "           to $target\n"
}

# now move the boxes from their temporary names back to their original names,
#  being careful not to clobber any new messages that may have arrived

foreach ( $box1, $box2 )
{
   $source = $_;
   $target = $box1orig if ( $box1 eq $_ );
   $target = $box2orig if ( $box2 eq $_ );

   my $tmp = "";
   my $tmpbox = "$target.recent";
   while ( -e "$tmpbox$tmp" )
   {
      $tmp ++;
      die ( "Too many temp files:\n" .
            "              $tmpbox\[0-9]\n\n" ) if $tmp == 10;
   }
   $tmpbox .= $tmp;
   undef $tmp;

   if ( ( $inbox == 1 ) and ( $box1 eq $_ ) or
        ( $inbox == 2 ) and ( $box2 eq $_ ) )
      {  `$locker -1 -r10 -ml`;  }   
   else
      {  `$locker -1 -r10 $target.lock`;   }
   die "Failed to obtain lock:\n" .
       "              $target.lock\n" .
       "              $source was not merged\n" .
       "         with $target\n" if $?;

   if ( -e $target )
   {
      rename $target, $tmpbox
         and print "Moved         $target\n" .
                   "           to $tmpbox\n"
            or die "Failed to move:\n" .
                   "              $target\n" .
                   "           to $tmpbox\n\n";
      `cat $source $tmpbox >> $target`;
      die          "Failed to append:\n" .
                   "              $tmpbox\n" .
                   "         onto $target\n\n" if $?;
      print        "Appended      $tmpbox\n" .
                   "         onto $target\n\n";
      unlink $tmpbox or warn "Failed to delete:\n" .
                             "              $tmpbox\n";
      unlink $source or warn "Failed to delete:\n" .
                             "              $source\n";
   }

   else
   {
      rename $source, $target
         and print "Moved         $source\n" .
                   "           to $target\n"
            or die "Failed to move:\n" .
                   "              $source\n" .
                   "           to $target\n\n";
   }

   if ( ( $inbox == 1 ) and ( $box1 eq $_ ) or
        ( $inbox == 2 ) and ( $box2 eq $_ ) )
      {  `$locker -mu`;  }
   else
      {  unlink "$target.lock";   }

   die "Failed to release lock:\n" .
       "              $target.lock\n" .
       "All changes were successful.\n\n" if $?;
}

print "\nAll done!\n\n";
