#!/usr/bin/perl
#
#
use strict;
use Getopt::Std;
use Sys::Syslog;
use Data::Dumper;
use Digest::SHA qw(sha256);
use Benno::Config;
use Benno::Boxstate;
use Benno::Mailfile;

my $VERSION = '2.8.16';

my %opts;
getopts('AhDdtqvc:C:i:',\%opts);

$opts{h} and help_exit();

my $conf;
$conf->{always}         = $opts{A};
$conf->{container}      = $opts{C};
$conf->{delete}         = $opts{d};
$conf->{inboxdir}       = $opts{i};
$conf->{test}           = $opts{t};
$conf->{throttle}       = $opts{T} || 2;
$conf->{quiet}          = $opts{q};
$conf->{verbose}        = $opts{v};
$conf->{bennoxml}       = $opts{c} || '/etc/benno/benno.xml';
$conf->{DEBUG}          = $ENV{DEBUG};

$conf->{container} || help_exit();
$conf->{DEBUG}     && print "DEBUG config:\n",Dumper([$conf]);

$conf->{log} = new LWs::Log($conf);
my $BennoXML = new Benno::Config($conf->{bennoxml});
$conf->{BennoXML} = $BennoXML;

if ($conf->{container} eq 'ALL') {
    # all containers
    foreach my $Container ($BennoXML->get_containers) {
        eval {
            $conf->{log}->info('Check container '.$Container->id);
            reimport($conf,$Container);
        };
        if ($@) {
            $conf->{log}->error($@);
        }
    }
    exit;
}
elsif ($conf->{container}) {
    my $Container = $BennoXML->get_container($conf->{container})
                or die "Container \"$conf->{container}\" does not exist.\n";
    eval {
        reimport($conf,$Container);
    };
    if ($@) {
        $conf->{log}->error($@);
    }

    exit;
}

help_exit();


### SUBS #######################################################################



### reimport
sub reimport
{
    my ($conf,$Container) = @_;

    my $containerid = $Container->id;
    $conf->{log}->info("Process Container $containerid") if $conf->{verbose};

    my @secretheader = $conf->{BennoXML}->get_secretheader;
 
    my $repopath = $Container->get_config('repopath');
    my $bstpath  = $repopath.'/boxstate.xml';
    my $Boxstate = new Benno::Boxstate($bstpath);
    $conf->{DEBUG} && print "DEBUG Boxstate:\n",Dumper([$Boxstate]);
    my $Box = $Boxstate->get_open();

    my $suffix = '';
    if ($Box->get_config('compression') eq 'gzip') {
        $suffix = '.gz';
    }
    # get filelist
    my $boxid = $Box->id;
    my $boxpath = $repopath.'/'.$boxid;
    opendir my $repodh, $boxpath or die "Cannot read repodir $boxpath: $!\n";

    while (my $repofile = readdir $repodh) {        # iterate over files in container
        my $repopath = "$boxpath/$repofile";
        next if -d "$repopath";
        $repopath .= $suffix;
        my $BennoFile;
        eval {
            if(exists_upstream($conf,$Box,$repofile)) {
                $conf->{log}->info("Object $repopath found upstream") if $conf->{verbose};
                next unless $conf->{always};
            }
            $BennoFile = new Benno::Mailfile($boxid,$repopath,@secretheader);
            my $filedigest = $BennoFile->checksum;
            if ($repofile !~ /^$filedigest\d\d/) {   # check checksum against filename
                die "Filename checksum mismatch\n";
            }
        };
        if ($@) {
            $conf->{log}->error("$repopath $@");
            next;
        }

        my $inboxdir = $conf->{inboxdir} || $conf->{BennoXML}->inbox;

        my $operation = $conf->{delete} ? 'Move' : 'Copy';

        my $filedigest = $BennoFile->checksum;
        my $tmpfile = $inboxdir.'/'.$containerid.':'.$filedigest.'.rpr';
        (my $emlfile = $tmpfile) =~ s/\.rpr$/.eml/;
        $conf->{log}->info("$operation abandoned mail $repopath to $emlfile");
        if (! $conf->{test}) {
            eval {
                open my $tmpfh, ">$tmpfile" or die "Cannot open tmpfile $tmpfile: $!\n";
                print $tmpfh $BennoFile->eml;
                close $tmpfh or die "Cannot close $tmpfile. $!\n";

                link($tmpfile, $emlfile) or die "Cannot unlink $tmpfile\n";
                unlink $tmpfile;
            };
            if ($@) {
                unlink $tmpfile;
                $conf->{log}->error("Cannot write exportfile $emlfile: $!");
                next;
            }

            $conf->{delete} && unlink $repopath;
        }
    }

    return;
}


### check_bucket
sub exists_upstream
{
    require Net::Amazon::S3;
    my ($conf,$Box,$repofile) = @_;

   my $endpoint   = $Box->get_config('endpoint');
    my $authkeyid  = $Box->get_config('authKeyId');
    my $authsecret = $Box->get_config('authKey');

    my $S3 = Net::Amazon::S3->new({
                  aws_access_key_id     => $authkeyid,
                  aws_secret_access_key => $authsecret,
                  host                  => $endpoint,
             });

    my $bennopath = $Box->get_config('bennoPath');
    my $boxid     = $Box->id;
    my $s3id      = $bennopath.'/'.$boxid.':'.$repofile;
     
    my $Bucket = $S3->bucket($Box->get_config('bucket'));
    my $meta = $Bucket->head_key($s3id);

    my $req_sec = 1 / $conf->{throttle};
    select(undef,undef,undef,$req_sec);

    my $md5empty = 'd41d8cd98f00b204e9800998ecf8427e';  # ETAG is md5 of object
    ($meta->{etag} eq '')        && return 0;
    ($meta->{etag} eq $md5empty) && die "Upstream object seems empty\n";

    $meta->{etag} && return 1;
}


### help_exit
sub help_exit
{
    my ($msg) = @_;
    print "$msg\n\n" if $msg;
    print "Usage: benno-s3-reprocess [-h] [-c <configfile>] -C <continer|ALL> options\n";
    print "  -c <bennoxml>      config file (/etc/benno/benno.xml)\n";
    print "\n";
    print "  -C <container>     container id or ALL for all containers\n";
    print "  -i <inboxdir>      inbox directory (default from benno.xml)\n";
    print "  -d                 delete after copy to inbox\n";
    print "  -A                 reimport always (default only missing objects)\n";
    print "  -t                 test mode, no changes\n";
    print "  -T <req/sec>       throttle (default 2/s)\n";
    print "  -v                 verbose mode\n";
    print "  -q                 quiet mode, log errors to syslog\n";
    print "  -h                 this help\n";
    print "\n";
    exit 1;
}


### EOP ###
1;


package LWs::Log;
use strict;
use Sys::Syslog;

sub new
{
    my ($class,$config) = @_;

    my $self = { verbose => $config->{verbose} };
    bless $self, $class;

    if ($conf->{quiet}) {
        $self->{target} = 'syslog';
        openlog('benno-s3-reprocess','nowait,pid','mail');
    }

    return $self;
}


sub write
{
    my ($self,$logmsg,$prefix) = @_;

    if ($self->{target} eq 'syslog') {
        $prefix = 'INFO' unless $prefix;
        syslog($prefix,$logmsg);
    }
    else {
        my $msg = $prefix.': ' if $prefix;
        $msg .= $logmsg;
        chomp $msg;
        if ($prefix eq 'ERROR') {
            print STDERR $msg,"\n";
        }
        else {
            print $msg,"\n";
        }
    }
}


sub error
{
    my ($self,$logmsg) = @_;

    $self->write($logmsg,'ERROR');
}


sub info
{
    my ($self,$logmsg) = @_;

    $self->write($logmsg,'INFO');
}


sub DESTROY {
    my $self = shift;

    if ($conf->{quiet}) {
        closelog;
    }
}

### EOP ###
1;

