#!/usr/bin/perl -w

use strict;
use Cyrus::IndexFile;
use IO::File;
use Digest::SHA1;
use Getopt::Std;
use ME::ImapSlot;

my %Opts;
getopts('gG', \%Opts);

my $digest = Digest::SHA1->new();

my $SlotName = shift;
my $Slot = ME::ImapSlot->new($SlotName);

unless (($Opts{g} or $Opts{G}) and $Slot) {
  print "Need -g or -G and a slot\n";
  exit 1;
}

my $folders = $Slot->AllMailboxes();
my $conf = $Slot->ImapdConf();

my ($login,$pass,$uid,$gid) = getpwnam('cyrus');

my @users = sort keys %$folders;
my $count = @users;
my $n;
foreach my $user (@users) {
  $n++;
  $0 = "guidhelper: $SlotName $user ($n/$count)";
  print "Doing user $user ($n/$count)\n";
  foreach my $mailbox (@{$folders->{$user}}) {
    my $metadir = $conf->GetUserLocation('meta', $user, 'default', $mailbox);
    my $datadir = $conf->GetUserLocation('spool', $user, 'default', $mailbox);
    update_index($metadir, $datadir, $Opts{G});
  }
}

sub update_index {
  my $metadir = shift;
  my $datadir = shift;
  my $do_sha1 = shift;

  my $file = "$metadir/cyrus.index";
  my $efile = "$metadir/cyrus.expunge";

  my %sha1;
  if ($do_sha1) {
    foreach my $fname ($file, $efile) {
      next unless -e $fname;
      my $index = Cyrus::IndexFile->new_file($fname) || die "Can't open file $fname\n";
    
      while (my $record = $index->next_record()) {
        unless ($record->{MessageGuid} and substr($record->{MessageGuid}, 25) =~ m/[^0]/) {
          $sha1{$record->{Uid}} = get_sha1("$datadir/$record->{Uid}.");
        }
      }
    }
  }
  
  # always lock the index file
  my $locked_index = Cyrus::IndexFile->new_file($file, 1) || die "Can't lock index file $file";
  my $new_index = IO::File->new("> $file.NEW");
  my @todo = ([$file, $locked_index, $new_index]);

  # add the expunge file if it exists
  if (-e $efile) {
    my $locked_expunge = Cyrus::IndexFile->new_file($efile, 1) || die "Can't lock expunge file $efile";
    my $new_expunge = IO::File->new("> $efile.NEW");
    push @todo, [$efile, $locked_expunge, $new_expunge];
  }

  # process each index file in order
  foreach my $item (@todo) {
    my $source = $item->[1];
    my $dest = $item->[2];
    $source->stream_copy($dest, sub {
      my $header = shift;
      my $index = shift;
      if ($do_sha1) {
        # don't overwrite it if we didn't need to calculate a new one
        if ($sha1{$index->{Uid}}) {
          $index->{MessageGuid} = $sha1{$index->{Uid}};
        }
      }
      else {
        # always blank it, might as well
        $index->{MessageGuid} = '0' x 40;
      }
      return 1;
    }, version => 10);
  }

  foreach my $item (reverse @todo) { # unlock backwards
    $item->[2]->close();
    chown($uid, $gid, "$item->[0].NEW");
    rename("$item->[0].NEW", $item->[0]);
    @$item = (); # cause cleanup
  }
}

sub get_sha1 {
  my $file = shift;
  my $fh = IO::File->new($file);
  $digest->reset();
  $digest->addfile($fh);
  return $digest->hexdigest();
}
