#!/usr/bin/perl
#
#
use strict;
use Getopt::Std;
use Time::Local qw(timegm_posix);

my %opts;
getopts('hLrSvb:C:H:j:l:n:r:P:R:s:',\%opts);

$opts{h}        and help_exit();

my $exportentries = 1;

my $container   = $opts{C};
my $journalarg  = $opts{j};
my $logfile     = $opts{l} || "s3journal-export.log";
my $numstop     = $opts{n};
my $startentry  = $opts{s};
my $verbose     = $opts{v};
my $listfiles   = $opts{L};
my $resume      = $opts{r};
my $runuser     = $opts{R} || 'benno';
my $hosturi     = $opts{H} || 'http://localhost:29180',
my $restsecret  = fetchSecret('/etc/benno/rest.secret');
my $simulate    = $opts{S};

$main::VERSION = '2.10.2';

($journalarg and $container) or help_exit();

my $Log = new Logfile($logfile,$simulate);

my (@journalfiles, $boxdir);
if (-f $journalarg) {
    ($boxdir) = $journalarg =~ m!^(.+?)journal/[^/]+\.journal(\.gz)?!;
    push @journalfiles, $journalarg;
}
elsif (-d $journalarg) {
    $boxdir = $journalarg;
    @journalfiles = journalfiles($journalarg);
}

if ($listfiles) {
    foreach my $journalfile (@journalfiles) {
        $Log->write("Journalfile: $journalfile",2);
    }
    exit 0;
}

if (!@journalfiles) {
    print STDERR "ERROR boxdir or journalfile not valid: $journalarg\n";
    exit 3;
}

LWs::RunAs->import($runuser);

my $RC = new RESTClient($hosturi,$restsecret,$container);

$startentry = Logfile->getLastId($logfile)  if $resume;
if ($startentry) {
    $Log->write("Start at entry $startentry",2);
    $exportentries = 0;
}

$Log->write("Append to $logfile",2);

my $current_time = current_time();
$Log->write("$current_time: Export files from $journalarg",1);

my $n = 0;
foreach my $journalfile (@journalfiles) {
    $Log->write("Process journal file $journalfile",2) if $verbose;
    $Log->write(current_time().": Process journal file $journalfile",1);

    my $jh;
    if ($journalfile =~ /\.gz$/) {
        open $jh, "<:gzip", $journalfile   or die "Cannot open journal file \"$journalfile\": $!\n";
    }
    else {
        open $jh, $journalfile             or die "Cannot open journal file \"$journalfile\": $!\n";
    }


    foreach my $line (<$jh>) {
        chomp $line;
        # 2023-06-29 14:37:36 UTC: (SUCCESS) ARCHIVED   "2023:016ADE9F4C6640DC69B0A0B95D751E4B56AD5E6AF38851EFB4264DE0B11BE99500": archived
        my ($yyyy,$mo,$dd,$hh,$mi,$ss,$yeardir,$first,$second,$third,$filename) = $line =~ /(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)\sUTC:\s\(SUCCESS\)\s+?ARCHIVED\s+?"(\d{4}):(\w\w)(\w\w)(\w\w)(\w+?)": archived$/;
        next unless $filename;
        my $timestr  = "$yyyy,$mo,$dd,$hh,$mi,$ss";

        my $jrlentry = "$yeardir:$first$second$third$filename";

        if (! $exportentries) {
            if ($startentry eq $jrlentry) {
                $exportentries = 1;
                $Log->write("Start export at entry $startentry",3);
                next;   # start at next entry
            };
        }
        next unless $exportentries;

        my $filepath = "/$boxdir/$first/$second/$third/$filename";
        $filepath =~ s!//!/!gm;

        eval {
            -d  "/$boxdir"                         or mkdir "/$boxdir/" or die $!;
            -d  "/$boxdir/$first/"                 or mkdir "/$boxdir/$first/"or die $!;
            -d  "/$boxdir/$first/$second/"         or mkdir "/$boxdir/$first/$second/"or die $!;
            -d  "/$boxdir/$first/$second/$third/"  or mkdir "/$boxdir/$first/$second/$third/"or die $!;
        };
        if ($@) {
            die "Cannot create subdir structure for $filepath: $@";
        }

        $Log->write("Fetch $jrlentry",2)    if $verbose;
        unless ($simulate) {
            open my $exh, ">$filepath"          or die "Cannot write $filepath: $!\n";
            print $exh $RC->fetch($jrlentry)    or die "Cannot fetch S3 mail: $jrlentry: $!\n";
            close $exh;
        }

        # set mtime of file
        my ($atime,$mtime) = (stat $filepath)[8,9];
        my $epoch_g = timegm_posix($ss,$mi,$hh,$dd,$mo-1,$yyyy-1900);
        utime($atime, $epoch_g, $filepath);
        
        $Log->write("  $jrlentry",1);
        $n++;

        if ($numstop && ($n >= $numstop)) {
            $Log->write("STOPPED AFTER \" -s $numstop\" entries",2);
            exit 2;
        }
    }
    close $jh;
}

$Log->write("Export of $n entries from $boxdir successful",2) if $verbose;
$Log->write(current_time().": Export of $n entries from $boxdir successful",1);


### SUBS ###

### fetch password from config file
sub fetchSecret
{   
    my $sfile    = shift;
    my $secret;

    if (open SF, $sfile or die "Cannot open $sfile: $!\n") {
        $secret = <SF>;
        chomp $secret; 
        close SF;   
        return $secret; 
    }

    return $secret; 
} 

sub journalfiles
{
    my ($boxdirpath) = @_;
    my @journalfiles;

    my $journaldir = "$boxdirpath/journal";
    if (-d $journaldir) {
        opendir(my $dh, $journaldir) || die "Can't opendir $journaldir: $!";
        while (my $journalfile = readdir $dh) {
            next if $journalfile =~ /^\./;

            my $filepath = $boxdir.'/journal/'.$journalfile;
            push @journalfiles,$filepath;
        }
    }

    return sort @journalfiles;     # !!sort sorts array in place
}

sub current_time
{
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime();
    $year += 1900;
    $mon  += 1; $mon = sprintf("%02d",$mon);
    $mday = sprintf("%02d",$mday);
    $hour = sprintf("%02d",$hour);
    $min  = sprintf("%02d",$min);
    $sec  = sprintf("%02d",$sec);

    return "$mday.$mon.$year $hour:$min:$sec";
}


sub help_exit
{   
    print "Usage: $0 [-h] -C <containername> -j <journalfile|boxdir> [<arguments>]\n";
    print "Version $main::VERSION\n";
    print "\n";
    print "    -C <containername>   name of the container to export\n";
    print "    -j <journalfile>     journal file to process (eg. /srv/benno/archive/repo/2023/journal/current.journal)\n";
    print "    -j <boxdir>          box directory with journal entries (e.g. /srv/benno/archive/repo/2023)\n";
    print "\n";
    print "    -H <resthost>        hostname of benno-rest (default: localhost)\n";
    print "    -l <logfile>         logfile\n";
    print "    -n <num>             stop after num entries (for debug purposes)\n"; 
    print "    -s <BennoID>         start at journal entry (eg. 2023:4BA3B...00)\n"; 
    print "    -r                   resume from last entry of logfile\n"; 
    print "    -L                   list journalfiles to process and exit\n";
    print "    -R <runser>          Run as user <runuser> (default benno)\n";
    print "    -S                   simulate - no writes to logfile\n";
    print "";
    print "REST password will be fetched from /etc/benno/rest.secret";
    print "\n";
    
    exit 1;
}   


### EOP ###
1;  

package Logfile;

sub new
{
    my ($class,$logfile,$simulate) = @_;
    my $self = { 'loghandle' => undef };
    if ($logfile and not $simulate) {
        open my $loghandle, ">>$logfile" or die "Cannot write logfile  \"$logfile\": $!\n";
        $self->{loghandle} = $loghandle;
    }
    bless $self, $class;

    return $self;
}


# ($msg,$target)
#  targets
#    0: logfile
#    1: logfile
#    2: STDERR
#    3: logfile + STDERR

sub write
{
    my ($self,$msg,$target) = @_;

    $target = 1 unless $target;

    $msg .= "\n";
    print STDERR $msg if $target & 0b10;

    return unless $self->{loghandle};

    my $loghandle = $self->{loghandle};
    print $loghandle $msg if $target & 0b01;
}


sub getLastId
{
    my ($class,$logfile) = @_;
    
    my $lastline;
    open my $loghandle, "$logfile" or die "Cannot read logfile  \"$logfile\": $!\n";
    foreach my $logline (<$loghandle>) {
        next unless $logline =~ /^\s\s(\S{4}:\S{66})/;
        $lastline = $1;
    }
    chomp $lastline;

    return $lastline;   # return last line whith id
}

1; ### EOP ###


package RESTClient;
use MIME::Base64;
use HTTP::Tiny;

sub new
{
    my ($class,$hosturi,$secret,$container) = @_;

    my $bennouser = 'benno2';

    my $self = {
        uri         => $hosturi.'/mail/',
        UA          => HTTP::Tiny->new,
        authstring  => encode_base64($bennouser.':'.$secret,''),

        params      => { archive => $container,
                         format  => 'raw',
                         header  => 'true',
                         skipUTF8Recode => 'true',
        },
    };
    bless $self, $class;

    return $self;
}

sub fetch
{
    my ($self,$mailid) = @_;

    $self->{params}->{id} = $mailid;

    my $options->{headers} = {
        Authorization  => 'Basic '.$self->{authstring},
    };

    if ($ENV{DEBUG}) {
        print 'Send POST request to '.$self->{uri}."\n";
        print "Params: \n";
        foreach my $p (keys %{$self->{params}}) {
            print "  $p: ".$self->{params}->{$p}."\n";
        }
    }
    
    my $response = $self->{UA}->post_form($self->{uri},$self->{params},$options);
    my $filecontent;
    if ($response->{success}) {
        $filecontent = $response->{content};
    }
    else {
        die 'ERROR '.$response->{status}.' '.$response->{reason}."\n";
    }


    return $filecontent;
}


1; ### EOP ###

package LWs::RunAs;

use strict;  

sub import { 
    my ($package,$user) = @_;
    unless( $user ){
        print STDERR __PACKAGE__." must be imported with user to run as.\n";
        exit 1;
    }
    if (($< == 0) || (getpwuid($<) eq $user)) {
        my ($uid,$gid) = (getpwnam($user))[2,3];
        $( = $gid;
        $) = $gid;
        $> = $uid;
        $< = $uid;
    }
    else {
        print STDERR "Program must be run as $user or root.\n";
        exit 2;
    }
}   
    
### EOP ###
1;
